꽤 오랜만에 JPA를 사용하는 프로젝트를 진행중인데

얼마나 시간이 지났다고 연관관계 mapping하는 방법이 가물가물하다; (예전에 그렇게 학을 떼어놓고는..)

살짝 헷가리는 어노테이션 정리하고 렛츠고!

 

mappedBy와 @JoinColumn은 JPA에서 엔티티 간 관계를 매핑할 때 사용하는 어노테이션입니다.

둘 다 관계 매핑에 필요한 정보를 제공하고 있지만, 다음과 같은 차이가 있습니다.

  • mappedBy: 양방향 관계에서 "다(N)" 쪽의 엔티티에서 사용되며, "일(1)" 쪽의 엔티티에 대한 매핑 정보를 지정합니다. 이를 통해 연관된 엔티티 사이에 양방향 참조를 설정할 수 있습니다.
  • @JoinColumn: "일(1)" 쪽의 엔티티에서 사용되며, 조인 컬럼 이름을 지정하여 연관된 엔티티의 외래 키 컬럼을 매핑합니다. 이를 통해 단방향 관계에서 연관된 엔티티의 외래 키를 지정할 수 있습니다.

즉, mappedBy는 양방향 관계에서만 사용되며, 연관된 엔티티 사이의 관계를 맺을 때 사용하고, @JoinColumn은 단방향 관계에서도 사용 가능하며, 외래 키 매핑에 사용됩니다.

그러나 두 어노테이션은 함께 사용되기도 합니다. 예를 들어, @ManyToOne으로 매핑된 엔티티 클래스에서 @JoinColumn 어노테이션을 사용하여 조인 컬럼을 지정하면, 이에 대응하는 @OneToMany으로 매핑된 엔티티 클래스에서는 mappedBy를 사용하여 역방향 참조를 설정하는 경우가 많습니다.

 

예를 들어, Team과 Member가 일대다 관계를 가지고 있다고 가정해봅시다. 이때 Team엔티티에서 Member엔티티의 리스트를 매핑하고, Member엔티티에서는 Team엔티티와의 관계를 역방향 매핑하려면 다음과 같이 mappedBy를 사용할 수 있습니다.

@Entity
public class Member {
    @Id
    @Column(name = "MEMBER_ID")
    private String id;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    // getters, setters
}

@Entity
public class Team {
    @Id
    @Column(name = "TEAM_ID")
    private Long id;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
    
    // getters, setters
}

프로젝트에서 Hikari 옵션을 아래와 같이 정의하고 사용키로 하였다. 

        driver-class-name: org.postgresql.Driver
        auto-commit: true
        connection-init-sql: select 1
        minimum-idle: 3
        maximum-pool-size: 20
        connection-timeout: 600000
        validation-timeout: 120000
        idle-timeout: 108000000 # 600000*10*18 = 18hours
        max-lifetime: 0 # controlled by idle-timeout
        cachePrepStmts: true
        prepStmtCacheSize: 350
        prepStmtCacheSqlLimit: 2048
        useServerPrepStmts: true


**autoCommit:**
auto-commit설정 (default: true)
**connectionTimeout:**
pool에서 커넥션을 얻어오기전까지 기다리는 최대 시간, 허용가능한 wait time을 초과하면 SQLException을 던짐. 설정가능한 가장 작은 시간은 250ms (default: 30000 (30s))
**idleTimeout:**
pool에 일을 안하는 커넥션을 유지하는 시간. 이 옵션은 minimumIdle이 maximumPoolSize보다 작게 설정되어 있을 때만 설정.
pool에서 유지하는 최소 커넥션 수는 minimumIdle (A connection will never be retired as idle before this timeout.). 최솟값은 10000ms (default: 600000 (10minutes))
**maxLifetime:**
커넥션 풀에서 살아있을 수 있는 커넥션의 최대 수명시간. 사용중인 커넥션은 maxLifetime에 상관없이 제거되지않음. 사용중이지 않을 때만 제거됨. 풀 전체가아닌 커넥션 별로 적용이되는데 그 이유는 풀에서 대량으로 커넥션들이 제거되는 것을 방지하기 위함임. 강력하게 설정해야하는 설정 값으로 데이터베이스나 인프라의 적용된 connection time limit보다 작아야함. 0으로 설정하면 infinite lifetime이 적용됨(idleTimeout설정 값에 따라 적용 idleTimeout값이 설정되어 있을 경우 0으로 설정해도 무한 lifetime 적용 안됨). (default: 1800000 (30minutes))
**minimumIdle:**
아무런 일을 하지않아도 적어도 이 옵션에 설정 값 size로 커넥션들을 유지해주는 설정. 최적의 성능과 응답성을 요구한다면 이 값은 설정하지 않는게 좋음. default값을 보면 이해할 수있음. (default: same as maximumPoolSize)
**maximumPoolSize:** 
pool에 유지시킬 수 있는 최대 커넥션 수. pool의 커넥션 수가 옵션 값에 도달하게 되면 idle인 상태는 존재하지 않음.(default: 10)


PreparedStatement란:
데이터베이스 관리 시스템(DBMS)에서 동일하거나 비슷한 데이터베이스 문을 
높은 효율성으로 반복적으로 실행하기 위해 사용되는 기능을 말할다.


**cachePrepStmts (cachePreparedStatements)**
default: false, recommend: true
MySQL은 PreparedStatement Caching을 비활성화하고 있기 때문에, 이 옵션을 허용해줘야 아래의 옵션값들이 실제 DB에 영향을 줄 수 있다.

**prepStmtCacheSize (preparedStatementsCacheSize)**
default: 25, recommend: 250 ~ 500
MySQL 드라이버가 Connection마다 캐싱할 PreparedStatement의 개수를 지정하는 옵션이다. HikariCP에서는 250 ~ 500개 정도를 추천한다.

**prepStmtCacheSqlLimit (preparedStatementsCacheSqlLimit)**
default: 256, recommend: 2048
MySQL 드라이버가 캐싱할 PreparedStatement의 최대 길이를 지정하는 옵션이다. HikariCP 개발자들의 경험상, Hibernate와 같은 ORM framework를 사용하는 경우에 특히 이 기본값이 턱없이 모자란다고 한다.

**useServerPrepStmts (useServerSidePreparedStatements)**
default: false, recommend: true
Server-Side PreparedStatement를 사용하는 옵션이다. Server-Side Prepared



**PreparedStatement 동작방식**

1. 먼저 애플리케이션은 쿼리 틀을 만들고 이를 DBMS로 보낸다. 특정값은 지정하지 않은 채로 남겨진다 
      ex) INSERT INTO products (name, age) VALUES (?, ?);
2. 해당 쿼리를 컴파일하며(최적화 및 변환) 아직 실행하지 않고 결과만 저장한다.
3. 나중에 쿼리 틀의 변수에 값을 지정하면 DBMS는 (결과를 반환할 수도 있는) 문을 실행한다. 애플리케이션은 여러 값으로 원하는 횟수만큼 문을 실행할 수 있다. 
 
**Statement와 PreparedStatement차이**
Statement와 PreparedStatement의 아주 큰 차이는 바로 캐시(cache)사용여부이다.
Statement를 사용하면 매번 쿼리를 수행할 때마다 4단계를 거치게 되고(계속적으로 단계를 거치면서 수행)
PreparedStatement는 처음 한 번만 세 단계를 거친 후 캐시에 담아 재사용을 한다는 것이다. 

만약 동일한 쿼리를 반복적으로 수행한다면 PreparedStatment가 DB에 훨씬 적은 부하를 주며, 성능도 좋다.


출처
https://webstone.tistory.com/56
https://effectivesquid.tistory.com/entry/HikariCP-%EC%84%B8%ED%8C%85%EC%8B%9C-%EC%98%B5%EC%85%98-%EC%84%A4%EB%AA%85

 

 

JPA 트랜잭션 격리수준 

일관성이 없는 데이터를 허용하는 수준

트랜잭션이 보장해야 하는 ACID중 격리성과 관련된 내용인데 격리성을 완벽히 보장하려면 동시성 처리 성능이 매우 나빠진다. 이런 문제로 인해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의했다. 

A : Atomicity / C : Consistency / I : Isolation / D : Durability

 

트랜잭션 관리 포인트 3

1. Dirty Read : 변경된 데이터가 아직 미완성인데 다른 트랜잭션에서 읽어가는 것

2. Nonrepeatble Read : 트랜잭션이 수행중인데 다른 트랜잭션이 읽고 있는 데이터를 수정해서 쿼리 결과가 달라지는 것

3. Phantom Read : 동일한 쿼리가 다른 값을 반환하는 것 

 

 

아래로 내려 갈수록 동시성이 높아지는 대신 속도가 느려진다.  ( lv.0 -> lv.3)

위로 올라 갈수록 동시성이 떨어지는 대신 속도가 빨라진다.  (lv.3 -> lv.0)

 

격리수준

  1. READ UNCOMMITED (lv.0)
    • 커밋되지 않은 데이터도 읽을 수 있음
      • A트랜잭션에서 데이터를 변경하려다 에러가 발생해서 Rollback을 했다고 치면 A트랜잭션이 실행되는 동안 데이터를 요청한 B 트랜잭션은 잘못된 데이터를 읽고 있을 수가 있다. Dirty Read, Dirty Write이 가능함
      • Dirty Read는 방지 X, Noorepeatable read방지 X, Phantom Read 방지 X
  2. READ COMMITED (lv.1)
    • 커밋된 데이터만 불러온다. (= 반복해서 같은 데이터를 불러올 수 없다) 
      • SELECT 문장이 수행되는 동안 해당 데이터에 Shared Lock(읽기 가능, 변경 불가)이 걸리는 레벨
        • 어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터(B)에 접근할 수 없습니다.
      • Dirty Read 방지 O , Noorepeatable read 방지 X
  3. REPEATABLE READ (lv.2)
    • 트랜잭션 동안에는 한번 조회한 데이터를 계속 조회해도 같은 데이터가 나오지만, 만약 다른 트랜잭션에서 데이터를 추가한 경우 기존 트랜잭션에서 반복 조회하면 결과 집합이 새로 추가된 데이터를 포함한 결과를 가져오게 된다.  
      • Dirty Read 방지 O, Noorepeatable read 방지 O, Phantom Read 방지 X 
  4. SERIALIZABLE (lv.3)
    • 모든 트랜잭션을 순서대로 실행한다.
    • Dirty Read 방지 O, Noorepeatable read 방지 O, Phantom Read 방지 O

 

 

JPA를 사용하면 격리 수준을 READ_COMMITED로 가정하는데 만약 일부 로직에 더 높은 격리 수준이 필요하면 비관적 락과 낙관적 락 중에 선택을 하여 사용해야 한다. 

 

비관적 락

트랜잭션 간 충돌이 발생한다고 가정하여 우선 락을 건다, 데이터베이스가 제공하는 락 기능을 사용한다.  

데이터를 수정하는 즉시 트랜잭션 충돌을 감지하며 예외를 발생시킨다. 

비관적 락을 사용하면 락을 획득할 때까지 트랜잭션이 대기한다. 무한정 기다릴 수 없으므로 타임아웃 시간을 줄 수 있다. 

 

낙관적 락

트랜잭션 대부분은 충돌이 발생하지 않는 다고 가정하며, JPA가 제공하는 버전 관리 기능을 사용한다. (어플리케이션 단 레벨)

트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없다는 특징이 있다. 

JPA에서 제공하는 @Version 어노테이션을 사용하며, 최초 커밋만 인정되어 두번째 커밋에선 예외를 발생시킨다. 

 

JPA의 추천하는 전략은 READ_COMMITTED  + 낙관적 락 

 

 

 

참고.

https://kafcamus.tistory.com/48

사수님께서 Persistence Context에 관한 좋은 내용이 담긴 글을 공유해주셨다. 

https://msolo021015.medium.com/jpa-persistence-context-deep-dive-2f36f9bd6214

 

JPA Persistence Context Deep Dive

최근 저는 새로운 프로젝트를 진행하면서, JPA를 도입하였습니다.

msolo021015.medium.com

 

@Modifying 을 사용중인 나에게 꽤나 충격적이었음..

findById를 해도 update된 데이터가 아니길래 정말 한참을 헤맸었는데...

역시 개념에 대한 정확한 이해 없이 개발을 하면 이렇게 되나보다.

 

JPA를 공부하겠다고 산 두꺼운 서적을 다시 한 번 제대로 살펴봐야겠다.

영속성 컨텍스트를 정확하게 이해하지 않고 그 누가 JPA를 사용한다고 하랴..

 

그래서 다시 한번 중요 개념을 정리하고 넘어가보자 한다.

수없이 봤던 영속성 컨텍스트의 LifeCycle

  • EntityManager & EntityManagerFactory
    • JPA는 스레드 생성시 EntityManagerFactory에서 EntityManager를 생성하고, EntityManager는 DB 커넥션 풀을 사용해 DB에 접근한다. 

 

  • Persistence Context
    • 엔티티를 영구 저장하는 환경 
    • EntityManager.persist(엔티티)를 하게 되면 엔티티를 영속화하여 영속성 컨텍스트에 저장을 하게 되며, 영속성 컨텍스트에 접근하기 위해서는 EntityManager를 통해야 한다. 
    • DB에 저장하게 되는 것은 그 이후의 얘기다. 

 

  • LifeCycle
    • New 
      • 비영속 상태 - 영속성 컨텍스트와 전혀 관계가 없는 상태 
        • 예를 들면 Member m = new Meber(); 이렇게 객체만 생성한 상태 
    • Managed
      • 영속 상태 - 영속성 컨텍스트에 저장된 상태로 DB에 저장되진 않고, 트랜잭션 커밋 시점에 쿼리가 DB로~
      • EntityManager
        // 객체를 생성한 상태(비영속) 
        Member member = new Member();
        member.setId("member1");
        member.setUsername("회원1");
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        // 객체를 저장한 상태(영속) 
        em.persist(member)
    • Detached
      • 영속성 컨텍스트에 저장되었다가 분리된 상태
        • em.detach(member);
    • Removed
      • 삭제되고, DB에 쿼리 날라감
        • em.remove(member);

 

  • 왜 PersistenceContext (영속성 컨텍스트)를 사용하는가!
    • 1차 캐시
      • 영속성 컨텍스트에는 내부에 1차 캐시가 존재하고, @Id로 선언한 필드의 값과 엔티티를 Key,Value 형태로 캐시에 저장하여, 해당 Key를 갖는 엔티티를 조회할 때 캐시에서 가져다 쓰게 되므로 DB를 갔다 오는 수가 줄어들게 된다. 
      • 예를 들어 ~.findById(123);을 하게 되면 123이라는 key로 조회된 entity를 1차 캐시에 저장하게 된다. 해당 스레드에서 다시 123의 key값을 가지는 entity를 불러오게 되면 db에서 불러오는게 아니라 1차 캐시에서 가져오게 된다. 
    • 동일성 보장 
      • 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공하므로 같은 엔티티를 여러번 조회해도 같은 레퍼런스가 되어 동일성이 보장된다.
    • 쓰기 지연 SQL 저장소로 트랜잭션 지원
      • 엔티티들은 1차 캐시에 저장되고, INSERT같은 쿼리는 쓰기 지연 SQL 저장소에 쌓아서 commit()될 때 DB에 동시에 쿼리들을 보낸다. (이것이 flush())다.
    • Dirty Checking
      • 개인적으로 이것이 과연 이점일까 싶지만.... (당연한거 아닌가 하는 부분이라 해야할 까)
      • 영속성컨텍스트에 저장된 엔티티를 수정하면 (~.setName("홍길동)") commit() 혹은 flush()가 일어날 때 변경사항이 있음을 감지하고 DB에 UPDATE 쿼리를 보낸다.

 

  • Flush
    • 영속성 컨텍스트의 변경내용을 DB에 반영하여 싱크를 맞추는 작업
    • 커밋시, 영쓰기 지연 저장소에 쌓아놓은 쿼리들이 날라가 데이터베이스에 반영된다.
    • 호출시점 
      • entityManager.flush()로 직접 호출
      • 트랜잭션 커밋
      • JPQL 쿼리 실행
        • JPQL 실행 전에 무조건 flush()로 DB와의 싱크를 맞춘 다음에 JPQL 쿼리를 날리게끔 되어 있다.
    • 옵션
      • FlushModeType.COMMIT : 커밋할 때만
      • FlushModeType.AUTO : 기본 설정 값으로 커밋이나 쿼리를 실행할 때 
    • 플러시를 했다고 영속성 컨텍스트를 비우는 것은 아니고, 변경 내용을 데이터베이스와 동기화 하는 것이다.

 

  • Lazy Loading
    • 연관관계(@OneToMany 등)를 맺고 있는 엔티티를 검색할 때 연관관계가 있는 엔티티는 프록시 데이터로 채우고, 그것이 실제로 사용될 때만 검색하게 하여 불필요한 쿼리를 실행하지 않게 도와주는 역할 
    • 기본적으로 EAGER보다는 LAZY 관계를 맺게 해주어 추후에 일어날 성능상의 문제를 방지하도록 하자.

 

  • N+1 Problem 
    • 연관관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 개수만큼 연관관계의 조회 쿼리가 추가로 발생하는 문제 
    • Lazy냐 Eager냐 상관없이 발생하는 문제임...
    • 왜 발생하느냐?
      • jpaRepository에 정의된 메서드를 실행하면 JPQL이 싱행되게 된다. JPQL은 특정 SQL에 종속되지 않으면서 객체와 필드 이름을 가지고 쿼리를 하는데, 따라서 findAll()이란 메소드를 수행하게 되면 딱 그 엔티티에만 SELECT절을 날리게 되고, 연관관계인 엔티티는 별도로 호출하게 되는 것이다. 
    • 해결방법은?
      • FetchJoin을 사용하면 되는데 문제는 그렇게 되면 LazyLoading으로 해놓은 것이 무의미하게 되는 것, 연관관계를 맺고 있는 것을 한번에 다 가져오는 것이기 때문. (그것도 outer join으로..) 
      • 현재 운영중인 서비스에서는 획기적인 대안을 찾지 못했기 때문에 쿼리마다 특성을 파악하고 필요에 따라 FetchJoin을 사용하고 있는 상황이다.

 

 

 

 

참고

 https://ict-nroo.tistory.com/130 [개발자의 기록습관]

현재 개발하고 있는 프로젝트에서 사용하는 테이블들 간의 관계가 매우 복잡하다.

할 때 마다 까먹고, 정확히 사용하고 있는 건지 확신이 서지 않아 이번 기회에 다시 정리하기로 하였다.

 

먼저,

  • Cascade
    • @OneToMany, @ManyToOne 옵션 값 (parent-child 관계 일 때)
    • Entity의 상태가 변화했을 때 관계가 있는 Entity에도 상태 변화를 전파시키는 옵션 
    • Entity의 상태란?
      • Transient : 객체에 관해 아무것도 모르는 상태
      • persistent : 저장하여 JPA가 관리하는 상태로, 필요한 시점에 DB에 적용
      • detached : JPA가 관리하지 않는 상태 
      • removed : JPA가 관리는 하나, commit 시점에만 삭제
    • Type
      • type 명시하지 않을 시 모든 연관 관계 무시 
      • save-update 
        • save(), update() 하면 연관 관계 탐색해서 새로 생성된 비영속 인스턴스 저장, 준영속 인스턴스에서 발생한 변경사항 영속화
      • persist
        • 연관 관계에 있는 비영속 인스턴스를 영속화 하여, 해당 인스턴스를 호출 할때 연쇄 작용 일으킴
      • merge
        • 연관 관계에 있는 비영속 인스턴스를 영속 인스턴스로 병합
      • delete
        • delete(), remove()시 연관 관계에 있는 영속 인스턴스 삭제
      • remove
        • delete(), remove()시 연관 관계에 있는 영속 인스턴스 연쇄 삭제
      • lock
      • replicate
      • evict
      • refresh
      • all
        • 위 목록에 있는 모든 연쇄 옵션을 포함해 활성화 한다. 

 

 

수많은 @OneToMany의 향연 속에서 Parent가 삭제될 때 Child를 어떻게 할 것인가를 고민하다

Parent가 삭제되면 Child도 삭제하기로 결정. 그리고 구현방법을 찾아보다 아래 두가지 방법을 찾게 되었는데 

똑같아 보이는데 왜 다르지? 궁금증이 생김, 구글에 검색해서 내가 원하는 명확한 차이점을 찾았고(힘들었음..)

아래에 정리해 보았다. 

 

  • orphanRemoval=true  vs  CascadeType.REMOVE
    • Parent가 삭제 되었을 때 Child도 함께 삭제시키는 역할을 수행하는 점에선 동일
    • 다른점은,
      • 관계를 끊는 것에 대한 응답!
      • 만약 Parent의 Child 값을 null을 주었다고 가정
        • orphanRemoval=true는 관계가 끊어진 child를 자동으로 제거한다. 
        • CascadeType.REMOVE는 관계가 끊어진 child를 자동 제거하지 않는다.
          • 관계가 끊어졌다는 것을 제거로 보지 않기 때문에 

https://www.objectdb.com/java/jpa/persistence/delete#Orphan_Removal_

 

Using JPA to remove (delete) Java entity objects from the database

Existing entity objects can be deleted from the database either explicitly by invoking the removeremove(entity)EntityManager's methodRemove the entity instance.See JavaDoc Reference Page... method or implicitly as a result of a cascade operation. This page

www.objectdb.com

 

ParentId id1 = new ParentId();
id1.setId1("myId1");
id1.setId2("myId2");

ParentId id2 = new ParentId();
id2.setId1("myId1");
id2.setId2("myId2");

id1.equals(id2) ??????

id1.equals(id2) 의 결과값은 false이다.

왜냐하면 자바의 모든 클래스는 기본으로 Object 클래스를 상속받는데, 이 클래스가 제공하는 기본 equals()는 인스턴스 참조 값 비교인 ==비교(동일성비교)를 하기 때문이다. 

 

영속성 컨테스트는 엔티티의 식별자를 키로 사용해서 엔티티를 관리한다. 

그리고 식별자를 비교할 때 equals() 와 hashcode()를 사용한다. 

따라서, 식별자 객체의 동등성(equals)이 지켜지지 않으면 예상과 다른 엔티티가 조회되거나 엔티티를 찾을 수 없는 등 영속성 컨텍스트가 엔티티를 관리하는 데 문제가 발생한다. 

 

따라서 복합 키는 equals()와 hashCode()를 필수로 구현해야 한다. 

 

 

* 응용 프로그램은 세션 당 단일 스레드를 고수하는 한 비즈니스 오브젝트에서 동기화 할 필요가 없습니다. 세션 내에서 응용 프로그램은 ==를 사용하여 객체를 안전하게 비교할 수 있습니다.

 

* equals와 hashcode를 어떨 때  사용하는가 ?  -> 가장 이해하기 좋았던 사이트 

https://jojoldu.tistory.com/134

 

equals와 hashCode 사용하기 ( +lombok)

안녕하세요? 이번 시간엔 equasl & hashcode를 어떤 곳에서 사용할 수 있는지를 확인해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 G..

jojoldu.tistory.com

해당 인스턴스들이 갖고 있는 값들이 같을 경우 같은 인스턴스로 봐야할 때 equals를 오버라이딩

equals와 hashcode는 모두 VO(value object)에서만 사용하길, 값 나타내는 것 이외에 기능을 갖는 인스턴스에서 오버라이딩을 하면 문제가 발생할 수 있음. 롬복 사용시  @EqualsAndHashCode(exclude = {"id", "paymentMethod", "price"}) 와 같은 형태로 클래스 명 위에 입력해주면 된다. 

 

* 참고

-Book. 자바 ORM 표준 JPA 프로그래밍

-https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

 

4.3. Implementing equals() and hashCode()

4.3. Implementing equals() and hashCode() You have to override the equals() and hashCode() methods if you intend to put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and intend to use reattachment of d

docs.jboss.org

https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/transactions.html#transactions-basics-identity

public class Group {
    @Id
    @Column(name = "groupidx")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int groupIdx;
}

 

public class Project {
    @Id
    @Column(name = "projectidx")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int projectIdx;
    
    @OneToOne(mappedBy = "project", cascade = CascadeType.PERSIST)
    Apple apple;
    
    @OneToOne(mappedBy = "project", cascade = CascadeType.PERSIST)
    Samsung samsung;

    @ManyToOne(fetch = FetchType.EAGER)
    @JsonBackReference
    @JoinColumn(name = "groupidx", insertable=false, updatable=false)
    private Group group;
 }

 

public class Apple {
    @Id
    @Column(name = "id", unique = true, nullable = false)
    private String id;
 
    @Column(name = "projectidx", unique = true)
    private int projectIdx;
    
 	@MapsId("projectIdx")
    @OneToOne
    @JoinColumn(name = "projectidx")
    private Project project;
}
public class Samsung {
    @Id
    @Column(name = "id", unique = true, nullable = false)
    private String id;
 
    @Column(name = "projectidx", unique = true)
    private int projectIdx;
    
 	@MapsId("projectIdx")
    @OneToOne
    @JoinColumn(name = "projectidx")
    private Project project;
}

 

 

TestCode

    @Transactional
    @Test
    public void groupAndProjectTest() {
        Group group = groupRepository.findById(1).get();
        List<Project> project = group.getProject();
        
        assertNotNull(project.getProjectIdx);
        assertNotNull(project.get(0).getApple().getApple***());
    }

Group -> Project -> Apple or Samsung 

group을 가져오면 해당 그룹이 가지고 있는 project, apple or samsung 데이터를 확인할 수 있다. 

 

 

 

 

+ Recent posts