JPA 공부 메모

박상준·2024년 10월 6일
1
post-custom-banner
  1. merge() 는 준영속 상태에서 영속상태로 전환해준다

    1. 하지만, 엄연히 준영속 상태에서 → 영속상태로 변환이 아니라,
    2. 준영속 상태에서 가지고 있던 식별자 ID 에 대하여 1차 캐시에서 조회 실패 → DB조회 방식으로 조회를 수행한다.
  2. 1차 캐싱은 하나의 트랜잭션 단위에서만 유효하다

    1. 만약 트랜잭션 단위외의 부분에서 캐싱을 수행하려면 2차 캐싱이 필요하며,
      1. 2차 캐싱의 종류에는
        1. memcache 등이 있다.
  3. @Enumerated(EnumType.STRING) 은 실무에서 권장되지 않는다

    1. 만약 enum 명이 변경되는 경우 DB에 영향을 미칠 수 있다
    2. 커스텀 매핑을 사용하는것이 낫다
    3. ADMIN 이라면 ADMIN(”ADMIN”) 으로 별도의 이름을 설정해주자.
  4. JPA GenerationType.IDENTITY 은 쓰기 지연을 지원하지 않는다

    1. 만약 IDENTITY 엔티티에서 별도로 save 를 수행하는 경우 즉시 DB에 INSERT 를 실행한다.
    2. 대량 데이터 삽입의 경우
      1. JPA 는 엔티티를 DB에 저장할 때마다 기본 키를 조회해야하기에 성능적인 문제가 발생한다.
  5. 한 서비스 트랜잭션 내에서 IDENTITY 전략으로 save() 를 수행 롤백되는 경우 어떻게 되는지

    1. 그래도 원자성은 유지한다
    2. 하나의 스레드에서 save → id 1번에 저장
    3. 다른 스레드에서 동시에 save → id 2번에 저장
    4. 첫 번쨰 롤백 → id 1번에 저장된 내역이 롤백
    5. id은 그대로 유지된다.
      1. 롤백된 트랜잭션에서 소모된 식별자 값은 재사용되지 않는다.
      2. 중간에 식별자가 비어있을순 있다.
    6. 기본적으로 MySQL 의 경우
      1. 기본 격리단계는 READ_COMMIT 으로 커밋되지 않은 데이터는 읽지 않음.
      2. 다른 스레드에서 조회도 불가능하다.
  6. DateCalendar 의 경우 @Temporal 이 필요하다

    1. Mysql 의 경우 datetime 으로 매핑된다
    2. 하지만 LocalDateTime 의 경우 별도의 매핑 어노테이션은 필요없이 그냥 사용하기만 하면 된다.
    3. 만약 타임존을 고려해야하는 경우
      1. ZonedDateTime 또는 OffsetDateTime을 사용하는 것이 좋다.
      2. localdatetime 은 그 클래스 이름과 동일하게 로컬타임만 기준으로 한다.
  7. fetch type 은 항상 LAZY 로

    1. EAGER 설정해야할 필요는 거의 없음.
    2. 필요하면 그때 변경하도록
  8. 연관관계는 왠만하면 단방향으로 둔다

    1. @ManyToOne 을 사용하자
  9. cascadeType 의 경우

    1. 보통 비즈니스 로직상 같이 저장되어야 하는 경우에는 cascadeType PERSIST 를 통하여
      1. 설정하는데..
    2. PERSIST 말고는 솔직히 쓸일이 있을까? 생각된다.
    3. REMOVE 타입의 경우 삭제인데
    4. 보통 하드 딜리트보다 소프트 딜리트를 사용한다. 데이터를 날리는 케이스가 많은지 고민된다..
  10. BaseEntity 의 경우

    1. 일반적으로
      1. @IdcreatedDate updatedDate 등의 공통적인 값들을

        @MappedSuperclass
        public abstract class BaseEntity {
            @Id @GeneratedValue
            private Long id;
            
            private String name;
        }
        
        • 이러한 추상 클래스를
        @Entity
        public class Member extends BaseEntity {
            private String email; // 이메일 속성 추가
        }
        
        @Entity
        public class Seller extends BaseEntity {
            private String shopName; // 상점 이름 속성 추가
        }
        
        • 요런식으로 내려서 공통화할 때 한다고 함.
        • AttributeOverrides 를 통해 공통밸류에 대한 재정의가 가능함
        @Entity
        @AttributeOverrides({
            @AttributeOverride(name = "id", column = @Column(name = "MEMBER_ID")),
            @AttributeOverride(name = "name", column = @Column(name = "MEMBER_NAME"))
        })
        public class Member extends BaseEntity {
            private String email;
        }
        
      • @MappedSuperClass 의 경우 테이블에 매핑되는 값이 아님.
        • 인스턴스화가 필요없기에 추상 클래스로 둔다고 한다.
  11. 비식별과 식별 관계

    • 비식별관계인 경우 간단하게 연관
    • 식별 인 경우
      • 부모 테이블의 ID 가 자식 테이블의 FK 와 자식의 PK 2개의 값으로 복합키를 구성하는 케이스에서는
        • MapsId 가 사용됨.
        • 유지보수 하기가 까다로움
    • 비식별은
      • 객체 지향적인 모델링에서 자주씀
      • ORM 에서는 비식별 관계를 더 추천한다고함
        • 비식별 관계는 대리키로 테이블 간의 관계를 유연하게 만듬.
        • 식별 관계의 경우 비즈니스 요구사항이 변경되는 경우 테이블에 영향을 받음.
    • 식별은
      • 부모 - 자식 관계 데이터 무결성이 강하게 요구되는 경우 사용
  12. 엔티티 조회와 DTO 조회

    • 갑자기 생각이 났는데 김영한 강의에서는 DB 성능이 요즘 워낙에 좋아서 엔티티 조회하는걸로도 사실 크게 차이가 안난다고함.
    • DTO 조회는 정말 많은 리스트를 들고올때나 사용하는게 의미있을거같음,
  13. JPA 의 경우 페치타입을 EAGER 로 설정하는 경우

    • 기본적으로 외부조인을 사용한다
    • 외부조인을 사용해야 FK 가 null 인 케이스도 조회가 가능하다
    • 내부 조인을 사용하고자 하는 경우
      @ManyToOne(fetch = FetchType.EAGER)
      @JoinColumn(name = "TEAM_ID", nullable = false)  // 내부 조인 사용
      private Team team;
      • 이런식으로 연관관계에 null 이 사용될 수 있음을 나타내면 된다.
  14. CascadeType.REMOVE 와 고아객체 제거의 차이점

    1. REMOVE
      • 부모 엔티티가 삭제되는 경우 연관된 자식 엔티티도 함께 삭제되는 옵션이다.
    • 고아객체 제거
      • 부모가 자식을 더 이상 참조하지 않는 경우 → 컬렉션에서 자식을 제거하는 경우 자식이 자동으로 삭제됨.

        @Entity
        public class Parent {
            @Id @GeneratedValue
            private Long id;
        
            @OneToMany(mappedBy="parent", orphanRemoval = true)
            private List<Child> children = new ArrayList<>();
        }
        
        에서
        
        Parent parent1 = em.find(Parent.class, id);
        parent1.getChildren().remove(0);  // 자식 엔티티를 컬렉션에서 제거 -> 자식 자동 삭제 쿼리가 수행
  15. 값 타입의 경우 객체의 참조값을 그대로 사용하는 오류를 범하지 않아야 한다

    public class Address {
        private String city;
    
        public Address(String city) {
            this.city = city;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    }
    
    public class Member {
        private Address homeAddress;
    
        public Member(Address homeAddress) {
            this.homeAddress = homeAddress;
        }
    
        public Address getHomeAddress() {
            return homeAddress;
        }
    
        public void setHomeAddress(Address homeAddress) {
            this.homeAddress = homeAddress;
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            // 회원 1 생성
            Member member1 = new Member(new Address("OldCity"));
            
            // 회원 1의 주소 객체 참조
            Address address = member1.getHomeAddress();
    
            // 회원 2에 동일한 주소 객체를 할당 (참조만 전달됨)
            Member member2 = new Member(address);
    
            // 회원 2의 주소값 변경
            member2.getHomeAddress().setCity("NewCity");
    
            // 회원 1의 주소값도 변경되었음을 확인
            System.out.println(member1.getHomeAddress().getCity()); // 출력: NewCity
        }
    }
    
    • 회원1의 값타입을 그대로 회원 2로 넣는 경우, 회원 1의 Address 도 변경되어버림.
    • 값 타입은 해당 객체를 복사해서 사용해야한다
    • 새로 만들던가
             // 회원 1의 주소 객체를 복사하여 새로운 객체 생성
              Address newAddress = member1.getHomeAddress().clone();
  16. 값 타입의 경우

    1. 값 타입을 변경하거나 삭제하는 경우

    2. 값 타입은 엔티티에서의 식별자가 없기에

      1. 해당 타입에 대한 값을 전부 삭제한다.
      @Entity
      public class Member {
          @Id
          @GeneratedValue
          private Long id;
      
          @ElementCollection
          @CollectionTable(name = "ADDRESS_HISTORY", joinColumns = @JoinColumn(name = "MEMBER_ID"))
          private List<Address> addressHistory = new ArrayList<>();
      }
      
      @Embeddable
      public class Address {
          private String city;
          private String street;
          private String zipcode;
      
          // equals, hashCode 메서드 정의 (필수)
      }
      
      Member member = em.find(Member.class, 1L);
      List<Address> addressHistory = member.getAddressHistory();
      
      // 강남의 주소를 변경하고 싶다면 기존 주소를 삭제하고 새로운 주소를 추가해야 함
      addressHistory.remove(new Address("서울", "강남", "123-456"));  // 기존 주소 삭제
      addressHistory.add(new Address("새로운 도시", "신도시", "111-222"));  // 새로운 주소 추가
      
      em.persist(member);
      
      DELETE FROM ADDRESS_HISTORY WHERE MEMBER_ID = 1;
      
      INSERT INTO ADDRESS_HISTORY (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '서울', '강북', '789-101');
      INSERT INTO ADDRESS_HISTORY (MEMBER_ID, CITY, STREET, ZIPCODE) VALUES (1, '새로운 도시', '신도시', '111-222');
      • 이런식으로 값 객체는 어떤 식별자로 값을 지우고 수정해야하는지 모름
        • 그래서 다 지우고 새로 넣음;;
        • @ElementCollection 라서 새 테이블을 생성한다고 하네요..
          • 그래서 Address 라는 값 객체를 효율적으로 사용하려면
      @Entity
      public class AddressEntity {
          @Id
          @GeneratedValue
          private Long id;  // 식별자 추가
          @Embedded
          private Address address;
      }
      • 이런식으로 엔티티로 만들어서 사용해야 한다고 함.
      • 그러면 AddressEntity 안의 id 로 수정이나 삭제가 가능해짐
      Member member = em.find(Member.class, 1L);
      
      // AddressEntity 수정
      AddressEntity addressEntity = member.getAddressHistory().get(0);  // 특정 주소를 찾아서 수정
      addressEntity.setAddress(new Address("새로운 도시", "신도시", "111-222"));
      
      em.persist(member);
      
      ---
      
      UPDATE ADDRESS_HISTORY SET CITY = '새로운 도시', STREET = '신도시', ZIPCODE = '111-222'
      WHERE ID = 1;  -- 특정 ID에 대한 업데이트
      
      • 이런식으로 된다고 함.
  17. spring data jpa 에서의 save()

    • save 내부 소스
      	@Transactional
      	@Override
      	public <S extends T> S save(S entity) {
      
      		Assert.notNull(entity, "Entity must not be null.");
      
      		if (entityInformation.isNew(entity)) {
      			em.persist(entity);
      			return entity;
      		} else {
      			return em.merge(entity);
      		}
      	}
      • 해당 엔티티에서 해당 엔티티가 새로운 값 즉 ID 가 배정되지 않은 상태인 경우, 영속되지 않은 상태로 생각한다. ( 비영속 )
        • 영속화를 진행함.
      • 아니라면 merge 를 통해서 다시 영속화를 시킨다.
        • 준영속상태인 경우 merge 를 통한다.
  18. getOne 과 findOne( findById.. )

    • getOne 은 spring data jpa 3 이상부터는 depreciated 됨.
    • getOne → getReferenceById 로 이름이 변경됨,.
      • getOne 은 프록시에 해당 객체가 있는 경우 그것을 반환하고 없으면 DB에서 조회하는 LAZY LOADING 의 기능을 수행한다.
      • 아마 이름에서 오는 헷갈림때문에 추상메서드의 이름이 변경된게 아닌가 .. 싶다
    • findOne 의 경우
      • 그냥 DB에서 조회한다.
  19. JPQL 의 경우 update 와 delete 의 경우 @Modifying 이 필수적이다.

    1. Spring Data JPA 에서는 update 나 delete 쿼리의 경우 별도의 쿼리 힌트를 어플리케이션단에서 제공하지 않는다면

    2. 해당 쿼리를 SELECT 쿼리로 간주해버린다. → 예외가 발생한다.

      javax.persistence.TransactionRequiredException: Executing an update/delete query
    3. 해당 어노테이션은 JPQL 이 DB 상태를 변경하는 쿼리임을 알려주는 역할을 하게됨.

    4. Modifying 의 경우 내부적으로 2가지 옵션이 있다.

      1. clearAutomatically ( 쿼리 수행 후 )
        • 영속성 컨텍스트 clear 자동수행
        • 벌크 연산시의 경우 1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 쿼리를 수행함.
          • DB의 값이 변경되는 경우에도
          • 영속성 컨텍스트에서는 해당 변경사항을 반영하지 못했기에
          • DB와 1차 캐시간의 데이터의 싱크가 맞지 않는 치명적인 문제가 발생함.
      2. flushAutomatically ( 쿼리 수행 전 )
        • 해당 쿼리를 수행하기 전에 영속성 컨텍스트의 변경사항을 flush 수행
        • 수정이전에 해당 데이터가 DB에 반영됨을 보장

  20. Pageable의 경우 페이지를 1부터 시작하고 싶은 경우

    PageableHandlerMethodArgumentResolver 를 빈으로 등록하고 setOneIndexedParameters(true)
    
    를 설정하면 페이지 인덱스가 1부터 시작함.
  21. JPA 는 락 경합의 관점에서 이득이 크다.

    • 원래의 sql 문은 update 나 select .. for update 등의 비관이나 낙관적락을 수행한 시점부터 락이 걸려 비즈니스 로직의 시작과 끝 시점이 길어진다면 그만큼의 성능적인 단점을 안고 가야한다.
    • 하지만 JPA 는 플러시 이후에 쿼리가 수행되기에, 락을 건 시점과 커밋 시점이 매우 가까워지게 된다.
      • 그 만큼 경합 타이밍이 좁아지게 되는 것이다.
  22. JPA 에서 락 사용

    • JPA 에서 제공하는 락의 경우
      • 추천하는 전략은
      • READ COMMITED + 낙관적 버전 관리
  23. 쿼리 캐싱

    1. hibernate.cache.use_query_cache
      1. true
    2. 왠만하면 사용안하는게 이슈가 적을 것 같음.
profile
이전 블로그 : https://oth3410.tistory.com/
post-custom-banner

0개의 댓글