사용자 도구

사이트 도구


wiki:spring:jpa

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
wiki:spring:jpa [2022/06/24 21:48]
dhan
wiki:spring:jpa [2023/01/13 18:44] (현재)
줄 1: 줄 1:
 ====== Spring에 JPA 적용하기 ====== ====== Spring에 JPA 적용하기 ======
 <WRAP left notice 80%> <WRAP left notice 80%>
-  * description : +  * description : JPA 관련 내용 기술
   * author      : 도봉산핵주먹   * author      : 도봉산핵주먹
   * email       : hylee@repia.com   * email       : hylee@repia.com
줄 66: 줄 66:
     트랜잭션을 해야해야 쿼리가 실행됨     트랜잭션을 해야해야 쿼리가 실행됨
      
 +    실전 예제
 +    
 +
 +===== 연관관계 매핑 =====
 +  
 +  '객체지향 설계의 목표는 자율적인 객체들의 협력 공동체를 만드는 것이다.' 
 +  - 조영호(객체지향의 사실과 오해), 오브젝트
 +  
 +  JPA 포인터 역할
 +  양방향 연관관계의 주인
 +  
 +  객체와 테이블의 차이를 이해해야 됨
 +  테이블은 외래키만으로 양방향이 모두 적용됨(조인...)
 +  객체는 그렇지 않음
 +  
 +  양방향, 단방향... 양방향이 좋은가? 기본적으로 단방향으로 하는게 좋다.
 +  설계시에 단방향 매핑만으로 이미 연관관계 매핑 완료하자
 +  개발시에 꼭 필요하면 추가 하자. 
 + 
      
  
-===== 연관 관계 ===== +  * 연관관계의 주인을 정한다는 것은 외래 키 관리자를 선택하는 것이다. (외래키 등록 수정 가능), 하지만 가짜 매핑에도 넣어주자(양방향 매핑의 경우) 
-  * 연관관계의 주인을 정한다는 것은 외래 키 관리자를 선택하는 것이다. +  * 외래키가 있는 곳을 주인으로 정하자. 
-  * 연관관계의 주인은 외래키가 는 곳+    * 만일 외래키가 는 곳에서 주인으로 정하고 업데이트를 했는데, 업데이트를 하지 않은 곳의 테이블이 수정이 될 것이며, 이해하기(분석하기) 복잡함
   * 주인은 mappedBy 속성을 사용하지 않는다.   * 주인은 mappedBy 속성을 사용하지 않는다.
 +
   * 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.   * 주인이 아니면 mappedBy 속성을 사용해서 속성의 값으로 연관관계의 주인을 지정해야 한다.
 +  * mappedBy = 매핑되어진,  주인이 아님, 가짜 매핑, 여기에만 값을 넣으면 적용이 안됨
 +
   * 연관관계의 주인만이 데이터베이스의 연관관계와 매핑되고 외래 키를 관리할 수 있다.   * 연관관계의 주인만이 데이터베이스의 연관관계와 매핑되고 외래 키를 관리할 수 있다.
   * 주인이 아닌 곳은 읽기만 할 수 있다.   * 주인이 아닌 곳은 읽기만 할 수 있다.
-  * 데이터베이스 테이블의 다대일, 일대다 관계에서는 대부분 다 쪽이 외래 키를 가진다. (주인)+  * 데이터베이스 테이블의 다대일, 일대다 관계에서는 대부분 다 쪽이 외래 키를 가진다. (주인- 진짜 매핑) 
 +  * 자동차와 자동차 바퀴 
 +    * 자동차가 중요하지만, 주인은 자동차 바퀴 (1:n) 
 +    * 비즈니스 로직의 중요성으로 주인을 정하지 말고, 외래 키의 위치로 정하자!!
  
 +===== 양방향 연관관계 주의 =====
 +  * 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자.
 +  * 연관관계 편의 메소드를 생성하자
 +    * 양방향중에 한군데만 하자
 +    * <code java>
  
 +        // 양쪽값 
 + public void addMember(MemberHello member) {
 + member.setTeam(this);
 + members.add(member);
 + }
 +
 +        // 양쪽 값
 + public void changeTeam(Team team) {
 + this.team = team;
 + team.getMembers().add(this);    // 연관관계 편의 메소드
 + }
 +</code>
 +  * 양방향 매핑시에 무한 루프를 조심하자
 +    * 예 toString(), lombok, JSON 생성 라이브러리 
 +    * 양방향 호출이 됨
 +    * lombok로 toString을 사용하지 말자. 컨트롤러에서는 Entity를 반환하지 말자(Api 스펙 변경 문제(=> DTO 사용해야 됨), 무한 루프 문제)
 +  
 +
 +===== 다양한 연관단계 매핑 =====
 +  * 고려사항
 +    * 다중성
 +      * 다대일(@ManyToOne)
 +        * 다쪽에 외래키가 있고, 주인임, 멤버 - 팀에서 멤버가 주인임, @ManyToOne
 +        * 연관관계의 주인임 (속성에 mappedBy가 없음)
 +      * 일대다(@OneToMany)
 +        * 일이 연관관계의 주인, 엔티티가 관리하는 외래 키가 다른 테이블에 있음 - 업데이트문 실행
 +        * 실무에서는 거의 없지만, 가능하기는 함, 구지 한다면 다대일 양방향으로 하고 주인을 외래키가 있는 곳으로 지정
 +        * @JoinColumn을 사용해야 됨. 그렇지 않으면 중간 테이블(조인 테이블)이 생김, 성능, 운영상 관리 포인트 증가
 +        * 읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법(@JoinColumn(name="", insertable=false, updatable=false)
 +      * 일대일(@OneToOne)
 +        * 외래키에 유니크 제약조건이 있어야 됨
 +        * @JoinColumn을 넣자(디폴트값 지저분함)
 +        * 양방향도 가능(mappedBy(name = ""))
 +        * 대상 테이블에 외래 키 단방향은 지원 안됨
 +        * 자기테이블의 값은 자기가 관리하자
 +        * 설계시 외래키의 위치는 향후 장기적인 관점에서 판단하자
 +        * 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
 +      * 다대다(@ManyToMany)
 +        * 관계형 데이터베이스는 정규화된 테이블 2개로 표현을 할 수 없음
 +        * 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 함
 +        * 객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능
 +        * 중간 테이블에 필요한 추가 정보를 넣을 수 없음
 +        * 실무에서는 사용하지 말자!!
 +        * 실무에서는 연결 테이블을 엔티티로 승격하고 @ManyToMany -> @OneToMany, @ManyToOne로 변경
 +        * 단순한 다대다는 존재하지 않음
 +        * 조인테이블이라도 PK를 가지자, 될 수 있으면 의미없는 Pk를 쓰자, 향후 유연성이 있음
 +    * 단방향, 양방향
 +      * 테이블
 +        * 외래키하나로 양쪽 조인 가능
 +        * 방향 개념이 없음
 +      * 객체
 +        * 참조용 필드가 있는 쪽으로만 참조 가능
 +        * 한쪽만 참조하면 단방향
 +        * 양쪽이 서로 참조하면 양방향 -> 사실은 단방향이 두개임
 +
 +    * 연관관계의 주인 <- 양방향일 경우
 +      * 테이블은 외래키가 하나임, 객체는 참조가 2군데임
 +      * 객체에서 외래키를 관리할 곳을 정해야됨
 +      * 외래키를 관리하는 곳이 연관관계의 주인
 +      * 주인의 반대편은 단순 조회만 함
 +
 +===== 매핑 추가 정보 =====
 +  
 +  관계형 데이터베이스는 상속관계가 없다
 +  
 +  객체는 상속관계는 데이터 베이스에서는 조인, 단일 테이블, 각각테이블로 구현이 가능함
 +  기본 전략은 단일 테이블
 +  
 +  @MappedSuperclass - 자주 애용하자
 +  테이블과 관계가 없고 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할
 +  abstract(추상 클래스)로 사용
 +  
 +  주로 등록일, 수정일, 등록자, 수정자 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용
 +  
 +  @Entity 클래서는 엔티티나 @MappedSuperclass로 지정한 클래스만 상속 가능 
 +
 +===== 프록시와 연관관계 매핑 =====
 +
 +  
 +  멤버를 조회할때 팀도 함께 조회를 해야할까?
 +  em.getReference() // 
 +  
 +  프록시 클래스
 +    실제 클래스를 상속 받아서 만들어짐
 +    실제 클래스와 겉 모양이 같다.
 +    
 +  특징
 +    프록시 객체는 처음 사용할 때 한 번만 초기화(DB에서 가져와 
 +    프록시 객체를 초기화 할 때, 프록시 객체가 실제 엔티티로 바뀌는 것은 아니고, 초기화 이후 프록시 객체를 통해서 실제 엔티티에 접근 가능
 +  
 +    프록시 객체는 원본 엔티티를 상속받음, 따라서 타입 체크시 == 대신 instanceof 사용해야 됨
 +  
 +    영속성 컨텍스트에 엔티티가 있으면 em.getReference()를 호출해도 실제 엔티티 반환(프록시 아님)
 +    
 +    영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생
 +    
 +    JPA내에서는 프록시든, 객체든 항상 == true 임을 보장함
 +    
 +    프록시 인스턴스의 초기화 여부 확인
 +    PersistenceUnitUtil.isLoaded(Object entity)
 +    
 +    프록시 클래스 확인 방법
 +    entity.getClass().getName() 출력
 +    
 +    프록시 강제 초기화, JPA는 강제 초기화가 없음
 +    org.hibernate.Hibernate.initialize(entity);
 +    
 +    즉시 로딩(EAGER), 지연 로딩(LAZY)
 +    
 +    실무에서 가급적 모든 연관관계는 지연 로딩만 사용
 +    
 +    즉시 로딩은 JPQL에서 N+1 문제 발생
 +    
 +    @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
 +    
 +    @OneToMany, @ManyToMany는 기본이 지연 로딩
 +    
 +    @필요하면 Fetch join, annotation, 배치 사이즈로 사용함
 +    
 +    모든 연관관계에 지연 로딩을 사용하라
 +    실무에서 즉시 로딩을 사용하지 마라!, JPQL fetch 조인이나, 엔티티 그래프 기능을 사용해라!
 +    즉시 로딩은 상상하지 못한 쿼리가 나간다.
 +  
 +  영속성 전이(CASCADE)
 +    영속성 전이는 연관관계를 매핑하는 것과 아무 관련이 없음
 +    다만 연관된 엔티티도 함께 영속화하는 편리함을 제공할 뿐
 +    
 +    CASCADE.ALL or CASCADE.PERSIST를 주로 사용
 +    게시판 - 첨부 파일의 경우 처럼 거의 라이프사이클이 같을 때 유용하게 사용
 +    소유자가 하나일때 사용
 +    
 +  고아 객체(삭제는 언제나 조심해서 사용)
 +    orphanRemoval = true
 +    연결고리가 없어졌을때 삭제 됨
 +    참조하는 곳이 하나일 때 사용해야 됨
 +    특정 엔티티가 개인 소유할 때 사용 (@OneToOne, @OneToMany만 가능)
 +    
 +  CASCADE.ALL, orphanRemoval = true를 모두 사용하면
 +  부모 엔티티를 통해서 자식의 생명 주기를 관리할 수 있음(DDD의 Aggregate Root개념을 구현할 때 유용)
 +  
 +  실전: 
 +  
 +===== 값 타입 =====
 +  
 +  자바 기본값 타입은 공유가 불가능(복사)
 +  래퍼 클래스는 공유가 가능하지만 변경할 방법이 없음 
 +  
 +  임베디드 타입(클래스 생성 -> 복합값을 가짐)
 +    재사용 가능
 +    높은 응집도를 가짐
 +    엔티티가 생명 주기를 조절
 +    객체와 테이블을 아주 세밀하게 매핑하는 것이 가능( find-grained)
 +    잘 설계한 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다.
 +    
 +  동일한 타입을 중복으로 사용하면 중복필드로 오류가 남
 +    @AttributeOverrides, @AttributeOverride 사용
 +    
 +  
 +  임베디드 타입은 복사해서 사용해야 됨 (= <-레퍼런스(참조) 복사, new 사용해야 됨)
 +  공유로 인한 사이드 이펙트는 주의해야 한다.
 +  대신 불변 객체(immutable object)로 설계하거나 생성자로만 값을 설정하고, 수정자로는 변경이 안되게 함(불변객체, 없애거나 private로 함)
 +  변경하려면 new를 기존 값을 가져오고, 변경하고 싶은 것만 변경하면 됨(임베디드 타입 객체 전체를 바꿔야 함. 일부만 바꾸지 말자!!)
 +  
 +  실전: 임베디드 타입은 사용할 경우 불변 객체로 해서 사용하면 주석을 꼭 만들자!!
 +  
 +  기본 값타입이 인스턴스가 달라도 값이 같으면 같다.
 +  
 +  값 비교시
 +  동일성(identity) 비교: 인스턴스의 참조 값 비교, ==
 +  동등성(equivalence) 비교: 인스턴스의 값 비교, equals()
 +  
 +  객체 비교시 equals() override하여 전체 값을 일일이 동등성 비교를 해야 됨. (툴에서 자동으로 제공하는 것으로 사용, hashCode도 함께...)
 +  
 +  값타입 컬렉션(@ElementCollection, @CollectionTable 사용)
 +    엔티티로 컬렉션이 아닌 값타입으로 컬렉션을 사용
 +    데이터베이스로 봐서는 테이블이 분리된 것임
 +    테이블이 분리되었어도 생명주기가 엔티티에 의해 관리가 됨 즉 영속성 전이, 고아 객체 제거 기능을 필수로 가지는 것과 같음
 +    값 타입 컬렉션도 지연 로딩 전략을 사용함
 +    
 +    수정시에는 세터가 아닌 new를 사용하여 통으로 교체(side effect를 원천적으로 없앰)
 +    
 +    값 타입은 엔티티와 다르게 식별가 개념이 없다.
 +    값은 변경하면 추적이 어렵다.
 +    값 타입 컬렉션에 변경 사항이 발생하면, 주인 엔티티와 연관된 모든 데이터를 삭제하고,
 +    값 타입 컬렉션에 있는 현재 값을 모두 다시 저장한다.
 +    
 +    값 타입 컬렉션을 매핑하는 테이블은 모든 컬럼을 묶어서 기본키를 구성해야 함(null X, 중복 x)
 +    실무에서는 값타입을 엔티티로 승격하여 사용(일대다 관계)
 +    값 타입 컬렉션은 단순한 값 저장시에만 사용
 +  
 +===== 객체 지향 쿼리 언어 =====  
 +  * [[wiki:spring:jpa:jpql|JPQL]] \\
 + 
 ===== 적용파일 목록 ===== ===== 적용파일 목록 =====
  
줄 259: 줄 482:
 ===== Builder 사용하기 ===== ===== Builder 사용하기 =====
   * [[https://devfunny.tistory.com/423|[SpringBoot + JPA] Lombok @Builder 빌더패턴 적용기]]   * [[https://devfunny.tistory.com/423|[SpringBoot + JPA] Lombok @Builder 빌더패턴 적용기]]
-  + 
 +===== Troubleshooting ===== 
 +  * [[https://www.baeldung.com/java-hibernate-multiplebagfetchexception|A Guide to MultipleBagFetchException in Hibernate]] 
 +  * [[https://jojoldu.tistory.com/457|MultipleBagFetchException 발생시 해결 방법]] 
 + 
 +===== Ref ===== 
 +[[wiki:spring:jpa:querydsl|Querydsl]] \\ 
 {{tag>도봉산핵주먹 spring jpa 주레피 }} {{tag>도봉산핵주먹 spring jpa 주레피 }}
 +
/volume1/web/dokuwiki/data/attic/wiki/spring/jpa.1656074892.txt.gz · 마지막으로 수정됨: 2022/06/24 21:48 저자 dhan