이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- DB: H2 Database

- IDE: IntelliJ

 

 

1. 기본키 직접 할당

1
2
3
4
5
6
7
@Entity
public class Member {
 
    @Id // PK 지정 필수, PK 직접 할당
    private Long id;
}
 
 

 

2. 기본키 자동 생성: @GeneratedValue

    2.1. AUTO

    - Default

    - DB 방언에 맞춰 Value 자동 생성

 

    2.2. IDENTITY

1
2
3
4
5
6
7
8
@Entity
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}
 
 
 

    - PK 생성을 DB에 위임(예: MySQL: AUTO_INCREMENT)

    - PK에 null값을 넘기면 DB에서 자동으로 생성해줌

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            System.out.println("==========");
            em.persist(member);
            System.out.println("==========");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - JPA는 tx.commit() 시점에 INSERT SQL 실행

1
2
3
4
5
6
7
8
9
10
11
==========
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (null)
==========
 
 

    - IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행

    - 영속성 컨텍스트에 의해 관리되기 위해서 (Key, Value)로 (PK, 객체) 값이 필요한데, 영속성 컨텍스트에 관리되는 시점인 persist 시, PK값을 알 수 없으므로 IDENTITY 전략만 예외적으로 persist에서 DB insert query 발생

 

    2.3. SEQUENCE

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@SequenceGenerator(
       name = "MEMBER_SEQ_GENERATOR"// Java에서의 SEQ 이름
       sequenceName = "MEMBER_SEQ"// DB에서의 SEQ 이름
       allocationSize = 3
)
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
 
 
 

    - Sequence 객체를 생성해서 생성한 Sequence 객체에서 값을 가져와 PK값에 세팅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member1 = new Member();
            System.out.println("=====11=====");
            em.persist(member1);
            System.out.println("=====22=====");
 
            Member member2 = new Member();
            Member member3 = new Member();
            Member member4 = new Member();
            Member member5 = new Member();
            Member member6 = new Member();
            Member member7 = new Member();
            Member member8 = new Member();
 
            em.persist(member2);
            em.persist(member3);
            em.persist(member4);
 
            System.out.println("=====33=====");
            em.persist(member5);
            em.persist(member6);
            em.persist(member7);
 
            System.out.println("=====44=====");
            em.persist(member8);
 
            System.out.println("=====55=====");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - em.persist() 실행 시, 영속성 컨텍스트에 의해 관리되기 위해서 IDENTITY 전략과 마찬가지로 PK 값을 필요로 함

    - 그러나, SEQUENCE 객체도 DB에 의해 관리되므로 PK를 알 수 없음

    - 그러므로, em.persist(); 시 SEQ 객체로부터 PK값을 얻어옴

  ⭐ Insert 시점은 commit 시점으로 query buffer 기능 사용 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
=====11=====
Hibernate: 
    call next value for MEMBER_SEQ
Hibernate: 
    call next value for MEMBER_SEQ
=====22=====
=====33=====
Hibernate: 
    call next value for MEMBER_SEQ
=====44=====
Hibernate: 
    call next value for MEMBER_SEQ
=====55=====
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (?)
...
 
 
 

    - 처음에 call next value가 2번 호출되는 이유

        - SEQ 객체 초기 값이 -2로 call next value를 호출하여, 1부터 시작될 수 있도록 setting

        - allocationSize에 따라 메모리에 미리 seq값을 저장해두기 위해 호출

        - 그 이후서부터는 미리 저장해둔 seq를 다 사용하면 call next value

 

    2.4. TABLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Entity
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ",
        allocationSize = 1
)
public class Member {
 
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
}
 
 
 

    - 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 사용하는 것처럼 하는 전략

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
 
        EntityManager em = emf.createEntityManager();
 
        EntityTransaction tx = em.getTransaction();
        tx.begin();
 
        try {
            Member member = new Member();
            System.out.println("==========");
            em.persist(member);
            System.out.println("==========");
 
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
    }    
}
 
 
 

    - 테이블을 직접 생성하므로 모든 데이터베이스에 적용 가능하나, 최적화 등 성능 문제가 발생할 수 있음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Hibernate: 
    
    create table Member (
       id bigint not null,
        primary key (id)
    )
Hibernate: 
    
    create table MY_SEQUENCES (
       sequence_name varchar(255not null,
        next_val bigint,
        primary key (sequence_name)
    )
Hibernate: 
 
    insert into MY_SEQUENCES(sequence_name, next_val) values ('MEMBER_SEQ',0)
 
=====11=====
Hibernate: 
    select
        tbl.next_val 
    from
        MY_SEQUENCES tbl 
    where
        tbl.sequence_name=? for update
            
Hibernate: 
    update
        MY_SEQUENCES 
    set
        next_val=?  
    where
        next_val=
        and sequence_name=?
 
Hibernate: 
    select
        tbl.next_val 
    from
        MY_SEQUENCES tbl 
    where
        tbl.sequence_name=? for update
            
Hibernate: 
    update
        MY_SEQUENCES 
    set
        next_val=?  
    where
        next_val=
        and sequence_name=?
=====22=====
Hibernate: 
    /* insert jpa_basic.Member
        */ insert 
        into
            Member
            (id) 
        values
            (?)
 
 
 

 

 

 

📚 참고 자료

 

엔티티 매핑

- 객체와 테이블 매핑 : @Entity, @Table - 필드와 컬럼 매핑 : @Column - 기본키 매핑 : @Id, @GeneratedValue 객체와 테이블 매핑, 필드와 컬럼 매핑, 기본키 매핑의 방법을 간단하게 알아보고 각 애노테이션의

programmingrecoding.tistory.com

 

 

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- IDE: IntelliJ

 

JPA에서는 DDL을 애플리케이션 실행 시점에 자동 생성해주는 기능을 보유

1
2
<property name="hibernate.hbm2ddl.auto" value="" />
 
 
 

 

데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<!--
    create table Member (
       id bigint not null,
        age integer not null,
        name varchar(255),
        primary key (id)
    )
-->
 
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle12cDialect"/>
<property name="hibernate.hbm2ddl.auto" value="create" />
<!--
    create table Member (
       id number(19,0) not null,
        age number(10,0) not null,
        name varchar2(255 char),
        primary key (id)
    )
-->
 
 
 

 

⭐ 이렇게 생성된 DDL은 개발 장비에서만 사용

생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

 

hibernate.hbm2ddl.auto 옵션

- create: 기존테이블 삭제 후 다시 생성 (DROP + CREATE)

1
2
<property name="hibernate.hbm2ddl.auto" value="create" />
 
 
 

 

- create-drop: create와 같으나 종료시점에 테이블 DROP

1
2
<property name="hibernate.hbm2ddl.auto" value="create-drop" />
 
 
 

 

- update: 변경분만 반영

1
2
<property name="hibernate.hbm2ddl.auto" value="update" />
 
 
 

Entity에서 필드 추가 시, alter DDL이 자동 생성되어 column이 생성되지만 필드를 삭제한다고 column이 삭제되진 않음

 

- validate: 엔티티와 테이블이 정상 매핑되었는지만 확인

1
2
<property name="hibernate.hbm2ddl.auto" value="validate" />
 
 
 

* 테이블에 매칭되지 않는 필드 존재 시, 오류 발생

(반대의 경우로, 엔티티에 매칭되지 않는 컬럼 존재할 경우에는 오류 발생 X)

 

- none: 엔티티와 테이블이 정상 매핑되었는지만 확인

1
2
<property name="hibernate.hbm2ddl.auto" value="none" />
 
 
 

* none 대신 위의 4가지 옵션값이 아닌 값을 입력할 경우, hibernate.hbm2ddl.auto 실행 X

 

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] 객체 지향 모델링  (0) 2023.08.08
[JPA_Basic] 기본키 매핑  (0) 2023.08.05
[JPA_Basic] Persistence Context 장점  (0) 2023.07.29
[JPA] @PrePersist  (0) 2023.06.25
[JPA] JOIN Null값 처리  (0) 2023.06.20

이 글은 김영한의 [자바 ORM 표준 JPA 프로그래밍 - 기본편] 수강하며 정리한 글입니다.

 

👉 기본 환경

- Language: Java

- IDE: IntelliJ

 

1. 1차 캐시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member = new Member();
            member.setId(2L);
            member.setName("HelloB");
            em.persist(member); // 1차 cache 저장
 
            tx.commit(); // transaction 종료 후, 1차 캐시에 있던 데이터를 DB에 저장
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

2. 동일성 보장

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member findMember1 = em.find(Member.class, 101L);
            Member findMember2 = em.find(Member.class, 101L);
            // 1차 cache에 저장된 동일한 PK로 값을 조회하므로 DB에 Select를 사용하지 않고도 findMember가 가능
 
            System.out.println("Result: " + (findMember1==findMember2));
            // 같은 Transaction 안에서 같은 객체를 조회할 경우, == 비교 성립
 
            tx.commit();
            // Commit 후, 영속성 context에 있는 데이터에 대해 DB에 Query 송부
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

3. 쓰기 지연

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member member1 = new Member(150L, "A");
            Member member2 = new Member(160L, "B");
 
            em.persist(member1);
            em.persist(member2);
 
            tx.commit();
            // 쓰기 지연을 통해서 commit 시, batch_size만큼 SQL 구문 모아서 처리
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

4. 변경 감지(Dirty Checking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        // EntityManagerFactory: Application Loading 시점에 1개만 생성(DB당 1개)
 
        EntityManager em = emf.createEntityManager();
        // EntityManager: DB에 저장되는 transaction 단위마다 생성
 
        // JPA의 모든 데이터 변경은 transaction 안에서 실행
        EntityTransaction tx = em.getTransaction();
        tx.begin(); // transaction 시작 선언
 
        try {
            Member findMember = em.find(Member.class, 1L);
            findMember.setName("HelloJPA");
 
            tx.commit();
            // Update를 작성하지 않아도 JPA를 통해서 Entity를 가져올 경우, JPA가 관리
            // 내용의 변경이 있을 경우, JPA가 자동 update query 생성
 
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
 
        emf.close();
        // 트랜잭션 단위로 관리되는 Entity Manager는 Tx가 종료되면 close가 되어야하지만, emf는 Application 종료 시, close되어야 함
 
    }
}
 
 
 

 

5. 지연 로딩

 

[SpringBoot_JPA_1] FetchType.LAZY, EAGER

이 글은 김영한의 [실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발]을 수강하며 정리한 글입니다. 🟦 기본 환경: IDE: IntelliJ, Language: Java FetchType.EAGER(즉시 로딩): 데이터 조회 시 연관 데이

hj0216.tistory.com

 

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] 기본키 매핑  (0) 2023.08.05
[JPA_Basic] 데이터베이스 스키마 자동 생성  (0) 2023.07.31
[JPA] @PrePersist  (0) 2023.06.25
[JPA] JOIN Null값 처리  (0) 2023.06.20
[SpringBoot_JPA_Basic] JPA 1차 cache  (0) 2023.06.16

🌿 기본 환경: IDE: STS4, Language: Java

 

 

JPA에서 Custom ID를 부여하기 위해서는 테이블 작업 이후, 추가적인 과정이 필요

 

MySQL에서는 Sequence를 제공하지 않으므로 그에 대한 대안으로, sequence 테이블을 만들고 trigger와 연동시키는 과정이 필요

1. Sequence table 생성

1
2
3
4
CREATE TABLE tempSeq (
  sequence_value INT AUTO_INCREMENT PRIMARY KEY
);
 
 
 

 

2. Trigger를 선언하여 seq를 사용하는 테이블과 연결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
DELIMITER //
 
CREATE TRIGGER tempSeqTrigger BEFORE INSERT ON tempUser
FOR EACH ROW
BEGIN
  DECLARE sequence_value INT;
  
  INSERT INTO tempSeq VALUES (NULL);
  SET sequence_value = LAST_INSERT_ID();
  SET NEW.id = CONCAT(NEW.type, LPAD(sequence_value, 6'0'));
END//
 
DELIMITER ;
 
 
 

 * CREATE TRIGGER tempSeqTrigger BEFORE INSERT ON tempUser

- tempUser(seq를 사용하고자하는 entity)에 insert하기 전 사용할 trigger 선언

 * DECLARE sequence_value INT

- SEQ 테이블의 seq column값 선언

 * INSERT INTO tempSeq VALUES (NULL)

- SEQ 테이블에 null값 insert

 * SET sequence_value = LAST_INSERT_ID()

- 직전에 삽입된 행의 자동 증가 값(시퀀스 값)을 반환하는 함수

 * SET NEW.id = CONCAT(NEW.type, LPAD(sequence_value, 6, '0'));

- tempUser 테이블의 type과 sequence_value값을 결합

- LPAD: 지정한 길이(6)만큼 값이 부여되지 않을 경우 지정한 값('0')으로 채움

 

3. JPA에서 insert 작업 전 @PrePersist 선언

1
2
3
4
5
6
    @PrePersist
    public void prePersist() {
        String uuid = UUID.randomUUID().toString();
        this.id = uuid;
    }
 
 
 

 * @PrePersist

- Entity가 저장되기 전에 자동으로 호출되며, UUID 값을 생성하고 id 필드에 할당

 

⭐ INSERT 발생 시, @PrePersist 실행 후, Trigger 동작으로 custom id 부여

 

 

 

추가 자료

@PrePersist를 선언해야하는 이유

 

[해결 방법] java.sql.SQLIntegrityConstraintViolationException: Column '...' cannot be null

🌿 기본 환경: IDE: STS4, Language: Java JPA entity로 생성한 테이블에 trigger를 통해 데이터를 INSERT하고자 할 경우, 1 2 3 4 5 6 7 8 9 10 11 12 13 14 DELIMITER // CREATE TRIGGER tempSeqTrigger BEFORE INSERT ON tempUser FOR EACH ROW

hj0216.tistory.com

 

🟦 기본 환경: IDE: IntelliJ, Language: Java

 

1
2
3
4
5
6
7
8
@Repository
public interface TestDAO extends JpaRepository<TestOne, String> {
 
    @Query("SELECT t1, t2 FROM TestOne t1 LEFT JOIN ue.testTwo t2")
    List<Object[]> findAllWithTestOneTwo();
 
}
 
 
 

다음 소스코드를 실행하게 될 경우,

TestTwo entity에 저장된 데이터가 없더라도 null값으로 조회가 가능(LEFT JOIN)

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    public List<SampleDTO> testList() {
        List<Object[]> queryResult = testDAO.findAllWithTestOneTwo();
 
        List<SampleDTO> resultList = new ArrayList<>();
 
        for (Object[] result : queryResult) {
            TestOne testOne = (TestOne) result[0];
            TestTwo testTwo = (TestTwo) result[1];
 
            SampleDTO sampleDTO;
            if (userLike == null) {
                sampleDTO = new SampleDTO(testOne);
            } else {
                sampleDTO = new SampleDTO(testOne, testTwo);
            }
            resultList.add(sampleDTO);
 
        }
 
        return resultList;
    }
 
 
 

단, TestTwo의 데이터가 null값으로 넘어올 경우 sampleDTO에 값을 주입하기 위해 TestTwo 내부 필드에 대해 getter를 호출하게 되고 이에 NullPointerException이 발생하므로, null값인 경우에 대한 추가적인 로직이 필요

 

'Java > JPA' 카테고리의 다른 글

[JPA_Basic] Persistence Context 장점  (0) 2023.07.29
[JPA] @PrePersist  (0) 2023.06.25
[SpringBoot_JPA_Basic] JPA 1차 cache  (0) 2023.06.16
[JPA_Basic] JPA persist, find, remove, update  (1) 2023.06.15
[SpringBoot_JPA_1] @PathVariable  (0) 2023.06.03