JPA
[JPA] JPQL에서 limit절을 사용해보자
들어가기전 상황은 이렇다. 예매한 영화의 정보 중에 제일 많이 예약을 한 영화를 카운트를 하고 내림차순으로 카운트를 기준으로 정렬 한 다음 첫 번째 행의 데이터를 가져오려는 상황이다. 즉, 예매를 가장 많이 한 영화를 가져오는 기능이다. jpql로 limit query를 사용하여 query를 작성하였다. 하지만 컴파일 에러가 발생한다. nativeQuery를 사용하면 limit 절이 제공이 되지만 jpql에서는 제공이 안된다. 구글링을 통해 jpql에서 limit 키워드를 지원하지 않는다고 한다. 대신에 JPA query method의 결과를 제한하는 기능이 있다. 아래 링크를 참조하겠습니다. https://docs.spring.io/spring-data/jpa/docs/current/reference/..
[JPA] @OneToMany 단방향 매핑 이슈
일 대 다 관계의 단방향 매핑 후 save시 insert문과 update문이 추가로 나가는 이슈에 대해 정리한 글입니다. 이슈 상황 Reservation.class @Entity public class Reservation { // 생략 @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "RESERVATIONS_ID") private List seats = new ArrayList(); // 생략 } Seat.class @Entity public class Seat { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "SEATS_ID..
[Jpa] 테스트는 어느 부분까지 해야할까?
웹 서비스를 만드는데 항상 CRUD를 접한다. 기존 레거시 코드에서는 DAO 를 직접 작성을 해서 테스트는 진행하지만 안정성을 보장해 줄 수 없다. 하지만 Spring data JPA 를 사용하여 우리는 CRUD를 정말 편하게 작성할 수 있다. Spring이 Bean으로 등록된 객체들을 관리하여 DI 를 직접 해주고 아주 편리해졌다. 오늘은 이 부분에 대해 테스트를 어디까지 진행해야 할 까? 라는 주제를 정해 글을 써보겠습니다. DAO 테스트 작성은 간단하다 하지만 귀찮다! DAO를 작성하여 직접 사용하시는 분은 CRUD를 가지고 테스트 하시는 일은 매우 간단한 일이다. 테스트도 매우 간단하게 할 것이다. 저장이라는 행위를 테스트 한다고 가정해보면 해당 엔티티 데이터를 생성하여 데이터베이스에 접근하고 데..
[Jpa] 단일 테이블 전략의 상속 관계 매핑 이슈
Item 테이블을 단일 테이블 전략으로 만들어 발생한 이슈에 대해 포스팅 해보겠습니다. Book, Movie, Album 이라는 엔티티의 공통 속성을 뽑아 Item 이라는 엔티티를 상위 타입으로 만들었다. 여기서 save() 를 하는 코드에서 문제가 발생한다. Item이 상위 타입이므로 하위 타입이 들어와도 저장이 되지만 web 계층에서 dto를 보내어 저장할 때가 애매모호 하다. 처음 구현 한 코드는 아래와 같다. 각각의 아이템 마다 조건문으로 타입 체크를 하고 dto에서 엔티티로 변환하여 save() 하는 코드이다. 해당 아이템의 타입 체크를 하여 타입에는 문제가 되지 않는다. 하지만 아이템 종류들이 추가 될 때 마다 saveItem() 안에 조건문과 save() 메소드가 추가 되어야 한다. 아이템의..
[Jpa] 대댓글 계층구조 연관관계 메소드 이슈
게시판 대댓글을 구현하는데 @OneToMany와 @ManyToOne 을 사용하여 양방향 매핑을 하여 구현하는 것을 적어보려 합니다. 먼저 대댓글의 구조는 아래와 같을 것이다. - 댓글 |- 댓글 |- 댓글 |- 댓글 |- 댓글 |- 댓글 |- 댓글 |- 댓글 이러한 계층 구조로 구현을 해야 한다. 처음 구현한 방법은 아래와 같다. @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "PARENT_ID") private Comment parent; @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval..
[Jpa] Cascade
Cascade 개념 보통 엔티티 관계는 다른 엔티티의 존재에 의존한다. 대상 엔티티에 대해 일부 작업을 수행하면 연결된 엔티티에 동일한 작업이 적용된다. 즉 , 한쪽이 삭제가되면 해당 엔티티를 의존한 엔티티도 삭제가 되어야 한다. JPA Cascade 유형 all persist merge remove refresh detach CascadeType.ALL 모든 작업을 부모 엔티티에서 자식 엔티티로 전파한다. 예제 코드 연관관계 설정은 아래와 같이 하였다. Review @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "MEMBERS_ID") @ToString.Exclude private Member member; Member Member 엔티티의 pk 값을 ..
[Jpa] Transaction Propagation
JPA 를 사용하여 @Transactional 에노테이션을 메소드에 설정하면 트랜잭션이 시작하는 시점과 commit 되는 시점이 메소드의 처음과 끝이라는 것을 알 것이다. 우리가 Repository에 save 하는 과정에서도 트랜잭션 처리가 되어 있다. 만약 이 save() 를 한 줄, 한 줄 호출을 한다면 별도의 트랜잭션을 사용하여 동작이 되는 것이겠죠. 지금부턴 트랜잭션의 전파(Propagation)에 대해 간단히 포스팅 해보겠습니다. 우리는 Member라는 정보를 Repository에 저장하고 수정하는 로직에 더티 체킹이라는 것을 알고 있다. 더티 체킹은 트랜잭션 단위에서 영속성 컨텍스트 내부에서 1차 캐시의 스냅샷을 기준으로 변경된 값을 비교하는 동작을 더티 체킹이라 하는데 이러한 동작은 어떻게 ..
[Jpa] Transaction Scope and Isolation
트랜잭션은 어떤 작업을 하는데의 단위 묶음이라 한다. Jpa에서 중요한 역할을 하는데 Jpa에서 쿼리가 나가는 시점은 트랜잭션이 시작되고 끝나는 시점에 커밋이 되는 시점에서 쿼리가 발생한다. 또한 강제로 flush() 를 하면 쓰기 지연 sql저장소에 들어 있던 쿼리가 비워지면서 쿼리를 발생시킨다. 영속성과 트랜잭션의 관계 @Test @DisplayName("member save") @Transactional @Rollback(value = false) void memberSave() { Member member = new Member("이기영", "기영@naver.com", Gender.MALE); em.persist(member); } memberSave() 메서드를 한 작업의 묶음으로 설정하고 me..
[Jpa] Deprecated 된 getById() 대안 getReferenceById()
기존의 getById() Spring 2.5 이전 버전에선 getOne()과 getById()란 메서드가 있었다. getOne()과 getById()는 findById()와 같은 동작을 하지만 분명한 차이점이 있다. 먼저 메서드 명으로 동작을 유추해보자. getOne() 은 하나를 가져온다? 라고 해석이 된다. 뭔가 메서드 명에서 재사용 하기에 부족한 것 같은 느낌이 든다. 찾아 보니 deprecated가 되었다. getById() 는 Id 로 부터 가져온다.? 라고 해석이 된다. findById() 는 Id 로 부터 찾아온다.? 라고 해석이 된다. 즉, getById() 는 영속성 컨텍스트에 올라가 있는 프록시 객체를 lazy 로딩으로 가져온다. findById() 는 직접 DB를 조회하여 where ..
[Jpa] Dirty Checking
엔티티를 수정하고 repository에서 save를 안했는데 update 쿼리가 발생하는 현상을 본적이 있을 것이다. 이를 더티 체킹이라 하는데 트랜잭션 단위에서 엔티티를 가져와 영속 상태로 만들고 엔티티의 update메서드를 이용해 속성을 변경해준 다음 트랜잭션이 끝나는 시점에 변경을 감지하여 update 쿼리가 동작한다. 다음 코드는 더티 체킹이 동작하는 코드다. @Transactional public PostSaveResponseDto updateByIdPost(Long postId, PostUpdateRequestDto postUpdateRequestDto) { Post post = getEntity(postId); post.updateEntity(postUpdateRequestDto); retu..
[Jpa] Projections
아래와 같은 엔티티의 조회 시도를 하려고 한다. select 문을 날리면 해당 엔티티의 속성들이 모두 조회가 될 것이다. 속성들이 이보다 더 많다면 아마 성능에 문제가 되지 않을까 싶다. 그래서 해당 속성만 뽑아내려고 시도 해봤다. public class Post { private Long id; private String userName; private String title; private String description; private LocalDateTime createDateTime; private LocalDateTime updateDateTime; } 당연히 받는 타입은 PostDto로 title 과 description 만 받게 해 놓았다. @Query("select p.title, p...
[Spring JPA] 트랜잭션은 언체크, 체크 예외에 대해 어떻게 커밋과 롤백을 처리할까?
언체크 예외 발생 시 예외 처리를 했는데도 RollBack 이 되어 데이터베이스에 반영이 안되고 체크 예외 발생 시 예외 처리를 하고 결과를 보면 Commit이 되어 데이터베이스에 반영이 된다. 이게 어떻게 동작하는 걸까? 동작을 알아보기 전에 먼저 언체크 예외와 체크 예외에 대해 개념을 살펴보자. 언체크 예외는 RuntimeException 클래스를 상속한 예외를 말한다. 컴파일러가 에러 난 부분을 체크 하여 알려주지 않아 언체크 예외라고 생각하면 될 것이다. 체크 예외는 언체크 예외를 제외한 예외를 말한다. 컴파일러가 체크를 한 예외여서 체크 예외라고 생각하면 된다. 언체크, 체크 개념을 알아보았으니 트랜잭션 단위에서 예외 처리를 진행해 커밋과 롤백의 동작을 테스트 해보자. 예외를 던지다 @Trans..
[Spring JPA] CASCADE 는 무엇일까?
앞서 영속성 컨텍스트에 대해 정리하고 fetch의 대해 정리 해보았다. CASCADE 란 종속이란 단어인데 DB 쿼리 에서도 보았을 것이다. JPA에서는 A라는 엔티티에 연관된 엔티티들도 영속성 상태로 만들고 싶을때 사용하는 속성이다. @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) CascadeType 타입에서도 여러가지가 있다. 주로 ALL, PERSIST 을 설정하여 사용한다. 주의! 속성을 지정할땐 연관된 엔티티의 소유자가 단 하나일때 만 사용해야한다. 소유자가 여러개이면 한쪽에서 변경할 시 다른 한쪽에서도 변경된 값으로 영향을 받으므로 사용하지 않는 것을 권장한다. 고아 객체 관리 말 그대로 풀어서 설명해보면 소유자가 없는 객체 즉,..
[Spring JPA] N + 1의 문제점이 무엇이고 어떻게 해결 해야할까?
N + 1의 문제점에 대해 알아보기 전에 지연로딩 과 즉시로딩 을 알아야한다. 글 뜻대로 풀어서 생각해보면 지연로딩은 쿼리를 지연시켜서 로딩시킬 것이고 즉시로딩은 쿼리를 즉시 로딩시킬것이라고 생각이 된다. JPA 에 적용 시켜 즉시로딩과 지연로딩을 알아보자. Member 엔티티에 @ManyToOne N : 1 의 관계를 맺은 Team 엔티티가 존재한다 가정해보자. Member @Getter @NoArgsConstructor @Entity @Table(name = "members") public class Member { @Id @GeneratedValue @Column(name = "member_id") private Long id; private String name; @ManyToOne @JoinCo..
[Spring JPA] 프록시 객체는 어떻게 동작할까?
기존의 조회하는 기능을 사용하면 select 쿼리가 바로 나갔다. 하지만 getReference() 로 호출하면 쿼리가 안나가고 생성된 엔티티를 사용할 시(getName()) 에 select 쿼리가 나간다. 쿼리가 지연되는 상황이 보여진다. 이게 어떻게 동작이 되는 걸까? 먼저 프록시 객체라는 것을 알아야 한다. 하이버네이트가 만들어주는 프록시 객체는 Entity 타입의 target 참조변수가 존재 한다. 이 객체를 사용시에 영속성 컨텍스트에 초기화를 요청하고 영속성 컨텍스트는 DB에 원본 엔티티를 조회하고 생성 한다. 다음으로 프록시 객체가 원본 엔티티를 생성하여 getName() 로 호출 하여 사용한다. 위의 내용을 그림으로 보면 이해할 것이다. 프록시 객체의 특징을 살펴보자 프록시 객체는 처음 사용..