SPRING의 가장 강력한 점은 IOC, DI에 있다.
DI란, 스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로,
객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식이다.
@Bean 어노테이션과 연결지어 생각하는 것이 좋다. 우리는 @Bean을 만들어놓고 그 안에 방법2처럼 setter() 또는 생성자 등을 이용한다.
연결지어 Bean은 위 사진처럼 객체를 만들어서 활용하는 틀로 생각하면 좋다.
Bean은 SingleTon으로 생성되는 것으로, Spring Framework에서 딱 한 개가 만들어진다.
그래서 우리는 이 틀 안에서 특성 값을 '특정' 해놓으면 안된다. 그런 방법보다 값을 생성자 또는 setter로 '할당'하자.
IoC(제어의 역전)은, SPRING에서 조립된 코드의 최종 호출은 개발자에 의해서 제어되는 것이 아니라, 프레임워크의 내부에서 결정된 대로 이뤄지게 된다는 것이다.
위에서 말했던 Bean이 SingleTon이 될 수 있는 것이 바로 IoC 때문이다.
예를들어 템플릿 메서드 패턴에서도 제어의 역전 개념이 적용된다. 아래에 설명이 나오겠지만, MemberRepository를 상속한 서브 클래스는 getConnection()을 구현한다. 하지만 이 메서드가 언제 어떻게 사용될지 자신은 모른다. 일단 DB 커넥션을 만든다는 기능만 구현해놓으면, 템플릿 메서드 add(), get() 등에서 필요할 때 호출해서 사용하는 것이다. 즉, 제어권을 상위 템플릿 메서드에 넘기고 자신은 필요할 때 호출되어 사용되도록 한다는 개념이다.
폴더 구조와 각 구조별 기능을 이해하자.
config - responseTemplate(exception들이 enum형태로 표현되어 있다.) 또는 JWT 를 처리해주는 공간으로 활용한다.
controller - Post/Get 경로 등이 표기되어 있으며, Request를 처리하고 Response 해주는 곳. Service에 함수를 넘겨주고 결과를 받아온다.
domain - Member, LoginType(소셜/일반) .. 등 entity가 모여 있는 곳
service - 비즈니스 로직을 직접적으로 처리하는 곳으로, 코드가 일일히 짜여 있다.
dto - model이라고 보면 된다
repository - 객체의 상태를 관리하는 저장소이다. 즉, Entity을 저장하고 불러오는 역할을 한다.
데이터베이스에 연결하여 입력/수정/삭제/조회 등 작업 수행.
방법은 2개가 존재한다.
1. Jpa로 쿼리 쉽게 짜기
2. jdbc template
구조에 따른 RESPONSE와 REQUEST의 이동 경로는 어떻게 될까?
Request -> Route("/user") -> Controller(GettMapping("/user")) -> DTO -> SERVICE -> Repository -> DB
DB -> DTO -> Repository -> Service -> Controller -> Route -> Response
jdbc template으로 구현해 보았다.
jdbc template에 쓰이는 함수는 여기서 참고할 수 있습니다.
JPA로 구현해보고 싶다는 욕심에 열심히 배웠는데, 시간적 한계로 (아직 java 어린이..) 일단 직관적인 것부터 공부하는 전략을 세웠다. 그래서 폴더 구조부터 이해한 후 평소 알고 있던 jdbc template에서 직접 query를 짜서 구현하였다. 이후 마이페이지 부분은 JPA로 구현해보기로!
회원 한명을 조회하기 위한 코드는 다음과 같다. (구조별 기능을 이해하는 목적으로 공유한 코드입니다. 많은 것들이 생략되어 있음.)
MemberRepository
한 개의 회원정보를 얻기 위한 jdbcTemplate 함수(Query, 객체 매핑 정보, Params)의 결과를 반환한다.
아래에서 특정 member_id를 parameter로 받아서 조회하는 쿼리문을 볼 수 있다.
public Member getUser(Long userIdx) {
String getUserQuery = "select * from Member where member_id = ?";
Long getUserParams = userIdx;
return this.jdbcTemplate.queryForObject(getUserQuery,
(rs, rowNum) -> new Member(
rs.getString("email"),
rs.getString("pw"),
rs.getString("nick_name"),
rs.getString("image_url"),
rs.getInt("tier"),
rs.getInt("login_type"),
rs.getBoolean("comments_alarm_permission"),
rs.getBoolean("voice_permission"),
rs.getBoolean("event_permission"),
rs.getBoolean("report_status")
),
getUserParams);
}
LoginService
public Member getUser(Long userIdx) throws ResponseException {
try {
Member getUserRes = memberRepository.getUser(userIdx);
return getUserRes;
} catch (Exception exception) {
throw new ResponseException(DATABASE_ERROR);
}
}
MemberController
@ResponseBody
@GetMapping("/{memberId}")
public ResponseTemplate<Member> getUser(@PathVariable("memberId") Long memberId) {
try {
Member getUserRes = loginService.getUser(memberId);
return new ResponseTemplate<>(getUserRes);
} catch (ResponseException exception) {
return new ResponseTemplate<>((exception.getStatus()));
}
}
User
@Getter @Setter @Entity
@NoArgsConstructor @AllArgsConstructor
@Table(name="Member")
public class Member extends BaseTimeEntity {
@Id //Primary Key
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="memberId")
private Long id;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String pw;
@Column(nullable = false, length=30)
private String nickName;
@Column(nullable = true)
private String imageUrl;
@Column(nullable = false)
private int tier ;
private int loginType; //일반 로그인 또는 소셜로그인
@OneToMany(mappedBy = "userId", cascade = CascadeType.ALL)
private List<Script> scripts = new ArrayList<>();
//이하 생략
Trouble Shooting
✅ User부분을 맡게 되면서 회원가입/로그인 또한 구현해야 했는데, 이 과정에서 jwt를 사용하는 것이 쉽게 와닿지 않았다.
JWT는 다음 파트에서 한 번 제대로 써볼 예정이다.!
✅ EC2를 사용하기 전에 각자 mysql로 구현했는데 DB에서는 자동으로 memberId를 member_id로 바꿔서 저장한다. 이것 때문에 위에 repository 부분에서 camelcase를 다 고쳐야 했다. 다음번엔 jpa로 다시 시도해봐야지!
✅ jdbc로 받아올 때 계속 new Member로 할당하는 문제 때문에 계속 쿼리를 날려 조회하는데 새 객체가 할당되는 문제가 발생한다.
-> 쿼리문을 날려주는 과정에서 @id의 annotation이 GeneratedValue일 경우 생성자에 id가 null이면 자동 auto increment 되어 새로운 객체를 만들지만, 생성자에 id를 특정한 값으로 넣어주면 그대로 조회하게 된다.! 해결!
😂
✅ 일대 다 관계에서 cascade = CascadeType.ALL 어노테이션을 달아주지 않아 문제가 생겼다.
object references an unsaved transient instance - save the transient instance before flushing 에러
flush는 '영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업을 의미한다.' - 김영한 JPA p101 참고
다음 에러는 부모 엔티티에 포함된 FK가 아직 영속되지 않아 생긴 영속성 전이(CASCADE) 때문에 생긴 오류이다. 위에서 말했던 entity manager로 쉽게 설명해보면 script와 member entity를 동시에 em.persist 안에 넣어줄 수 있는 것이 영속성 전이이다. 참고
✅ yml 파일에 대한 이해로 시간을 많이 날렸다...
yml 파일에서 jpa 와 관련된 설정은 spring: 아래에 종속되어 있어야 한다. spring과 같은 레벨에 두어 오류가 많이 났다..
jdbc를 사용하는 과정에서 yml 파일에서 datasource라고 써야 하는데(다른 곳에서 datasource라는 이름으로 템플릿을 불러오기 때문에) database라고 써서 애를 먹었다... 역시 왜 쓰는지 알고 써야 해
'BackEnd > JAVA\SPRING' 카테고리의 다른 글
[Spring] Spring으로 JWT 구현하기2 - Spring Security 라이브러리 (0) | 2023.02.11 |
---|---|
[SPRING] Swagger, 프론트와의 소통을 편하게 하는 자동 툴 (2) | 2023.02.11 |
[SPRING] Spring으로 JWT 구현하기1 - jwt-java 라이브러리 (2) | 2023.02.09 |
[JAVA] 우테코 2주차 과제 - 숫자 야구 게임 (0) | 2022.11.08 |
[JAVA] 우아한테크코스 5기 지원 및 1주차 과제 (1) | 2022.10.30 |