스터디/JPA 스터디

[JPA] 엔티티 매핑

제이온 (Jayon) 2021. 12. 4.

jpa-study에서 스터디를 진행하고 있습니다.

@Entity

@Entity이 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라 부른다.

 

속성

 

주의 사항

  • 기본 생성자는 필수다.
  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안 된다.

 

@Table

@Table은 엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.

 

속성

 

데이터베이스 스키마 자동 생성

hibernate.hbm2ddl.auto 속성

 

HBM2DDL 주의 사항

개발 환경에 따른 추천 전략은 다음과 같다.

 

  • 개발 초기 단계는 create 또는 update
  • 초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI 서버는 create 또는 create-drop
  • 테스트 서버는 update 또는 validate
  • 스테이징과 운영 서버는 validate 또는 none

 

이름 매핑 전략 변경 (Spring boot 기준)

Java 언어는 관례 상 카멜 케이스를 사용하지만, 데이터베이스는 관례 상 스네이크 케이스를 사용한다. 만약 카멜 케이스나 기타 다른 방식으로 객체 변수를 데이터베이스에 매핑하고 싶다면 아래와 같이 spring.jpa.hibername.naming.physical-strategy 값을 변경하면 된다.

 

# 기본 변수 이름을 그대로 이용한다. 
spring.jpa.hibernate.naming.physical-strategy = org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
# camel case를 snake-case로 변경 (default)
spring.jpa.hibernate.naming.physical-strategy = org.hibernate.boot.model.naming.SpringPhysicalNamingStrategy

 

기본 키 매핑

JPA가 제공하는 데이터베이스 기본 키 생성 전략은 다음과 같다.

 

  • 직접 할당: 기본 키를 애플리케이션에서 직접 할당한다.
  • 자동 생성: 대리키 사용 방식
    • IDENTITY: 기본 키 생성을 데이터베이스에 위임한다.
    • SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
    • TABLE: 키 생성 테이블을 사용한다.
    • AUTO: IDENTITY, SEQUENCE, TABLE 전략 중 자동으로 선택한다.

 

기본 키 직접 할당 전략

@Id
private Long id;

 

@Id 적용 가능 자바 타입은 다음과 같다.

  • Primitive 타입
  • Wrapper 타입
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger

 

기본 키 직접 할당 전략은 em.persist() 로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방식이다. 주의할 점은 식별자 값이 없으면 예외가 발생한다.

 

IDENTITY 전략

IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. MySQL의 AUTO_INCREMENT 기능은 데이터베이스가 기본 키를 자동으로 생성해 준다. 테이블을 생성할 때 해당 기능이 붙은 ID 컬럼을 비워두면 데이터베이스가 순서대로 값을 채워준다.

JPA에서는 @GeneratedValue의 속성으로 IDENETITY를 명시해 주면 된다.

 

@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

 

이 전략을 사용하면 JPA는기본 키 값을 얻어 오기 위해 데이터베이스를 추가로 조회한다.

 

private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    System.out.println("board.id = " + board.getId());
}
// 출력: board.id = 1

 

위 코드를 보면 em.persist() 를 호출해서 엔티티를 저장한 직후에 할당된 식별자 값을 출력했다. 기본 키를 직접 할당하는 방식에서 식별자 값이 없으면 예외가 발생한다고 했는데, 위 코드는 어떻게 정상적으로 ID 값이 1이 되는 것일까?

 

em.persist() 를 호출하는 즉시 식별자 값을 확인하고, 해당 식별자가 비어 있다면 IDENETITY 전략인지 확인하여 맞다면 데이터베이스에 Insert 쿼리를 날려서 데이터를 실제로 저장을 한다. 그리고 해당 엔티티를 영속성 컨텍스트에 저장한다. 즉, 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

 

SEQUENCE 전략

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다. SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다. 이 전략은 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.

먼저 시퀀스를 만들어 보자.

 

CREATE TABLE BOARD (
    ID BIGINT NOT NULL PRIMARY KEY,
    DATA VARCHAR (255)
)

// 시퀀스 생성
CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;

 

그리고 다음과 같이 시퀀스를 매핑할 수 있다.

 

@Entity
@SequenceGenerator (
    name = "BOARD_SEQ_GRNERATOR",
    sequenceName = "BOARD_SEQ", // 매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
    private Long id;

 

이제부터 id 식별자 값은 BOARD_SEQ_GENERATOR 시퀀스 생성기가 할당한다.

 

private static void logic(EntityManager em) {
    Board board = new Board();
    em.persist(board);
    System.out.println("board.id = " + board.getId());
}
// 출력: board.id = 1

 

위와 같이 IDENTITY 전략과 SEQUENCE 전략이 같지만 내부 동작 방식은 다르다. SEQUENCE 전략은 em.persist() 를 호출할 때 먼저 데이터베이스 시퀀스를 사용해서 식별자를 조회한다. 그리고 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 엔티티를 데이터베이스에 저장한다. 반대로 이전에 설명했던 IDENTITY 전략은 먼저 엔티티를 데이터베이스에 저장한 후에 식별자를 조회해서 엔티티의 식별자를 할당한다.

 

@SequenceGenerator 속성 정리

 

TABLE 전략

TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다. 이 전략은 테이블을 사용하므로 모든 데이터베이스에 적용할 수 있다.

먼저 테이블을 만들어 보자.

 

create table MY_SEQUENCES (
    sequence_name varchar (255) not null,
    next_val bigint,
    primary key (sequence_name)
)

 

그리고 다음과 같이 TABLE을 매핑할 수 있다.

 

@Entity
@TableGenerator(
    name = "BOARD_SEQ_GENERATOR",
    table = "MY_SEQUENCES",
    pkColumnValue = "BOARD_SEQ", allocationSize = 1)
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
    private Long id;

 

TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작 방식이 유사하다. 다만 아래에서 예시를 보겠지만, 성능이 떨어진다는 단점이 있어서 사용을 자제하는 편이 좋다.

 

@TableGenerator 속성 정리

 

AUTO 전략

선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. 예를 들어 오라클을 선택하면 SEQUENCE, MySQL을 선택하면 IDENTITY를 사용한다. 주로 기본 키 생성 전략이 정해지지 않은 개발 초기 단계나 프로토타입 개발 시에 편리하게 사용할 수 있다. 다만 Hibernate 5 부터 MySQL에서의 기본 키 매핑 전략을 AUTO로 가져가면 TABLE을 기본 전략으로 선택하는 것을 주의해야 한다.

 

기본 키 매핑 정리

  • 직접 할당: em.persist() 를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다. 만약 식별자 값이 없으면 예외가 발생한다.
  • SEQUENCE: 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • TABLE: 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

 

기타 주의 사항

  • 자연 키보다는 대리 키를 권장한다.
    • 주민등록번호 같은 자연 키보다는 auto_increment 기능과 같이 자동으로 만들어진 대리 키를 사용해야 유지 보수에 좋다.
  • 기본 키는 변하면 안 된다. (setId() 금지)

 

필드와 컬럼 매핑: 래퍼런스

@Column

컬럼을 매핑한다.

 

속성

 

@Enumrated

자바의 enum 타입을 매핑한다. 속성은 value가 있는데 2가지 값 중 하나를 넣을 수 있다.

 

  • EnumType.ORDINAL: enum 순서를 데이터베이스에 저장한다. (Default)
  • EnumType.STRING: enum 이름을 데이터베이스에 저장한다.

 

이 중 후자를 선택하는 것이 좋다.

 

@Temporal

날짜 타입을 매핑한다. 속성은 value가 있는데 [TemporalType.DATE](http://TemporalType.Date) , TemporalType.TIME,  TemporalType.TIMESTAMP 중 선택해서 쓰면 된다.

 

@Lob

속성은 없고, 필드 타입에 따라 CLOB, BLOB 타입으로 매핑한다.

 

  • CLOB: String, char[], java.sql.CLOB
  • BLOB: byte[], java.sql.BLOB

 

@Transient

특정 필드를 데이터베이스에 매핑하지 않는다.

 

@Access

JPA가 엔티티에 접근하는 방식을 지정한다. @Access를 설정하지 않으면 @Id의 위치를 기준으로 접근 방식이 결정된다.

 

  • 필드 접근: AccessType.FIELD로 지정한다. 필드 접근 권한이 private이어도 접근할 수 있다.
  • 프로퍼티 접근: AccessType.PROPERTY로 지정한다. 접근자 (Getter)를 사용한다.

 

@Entity
public class Member {

    @Id
    private String id

    @Transient;
    private String firstName;

    @Transient
    private String lastName;

    @Access(AccessType.PROPERTY)
    public String getFullName() {
        return firstName + lastName;
    }

 

위의 예시에서 @Id가 필드에 있으므로 기본은 필드 접근을 사용하고, getFullName()

만 프로퍼티 접근 방식을 사용한다. 따라서 회원 엔티티를 저장하면 회원 테이블의 FULLNAME 컬럼에 firstName + lastName 의 결과가 저장된다.

 

기타 알게 된 사항

saveAll() 또는 batchInsert시 주의할 점

Spring Data JPA에서 기본 키 매핑 전략을 IDENTITY 혹은 AUTO로 설정하면, saveAll() 을 수행하더라도 데이터의 개수만큼 save() 를 반복한다. 즉, 한꺼번에 여러 개의 데이터를 넣는 것이 아니라 하나씩 넣게 된다. 만약 한꺼번에 Insert 또는 Update 해야 되는 엔티티의 경우 기본 키 매핑 전략을 SEQUENCE 또는 TABLE을 고려하는 것이 좋다.

하지만 이 중 Table 매핑 전략은 다음과 같이 번호 하나를 딸 때마다 쿼리를 2개씩 날리므로 비효율적이다.

 

2021-12-03 08:47:33.226  INFO 21652 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@429bffaa testClass = UserRepositoryTest, testInstance = qna.repository.UserRepositoryTest@58695725, testMethod = findById@UserRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@5403f35f testClass = UserRepositoryTest, locations = '{}', classes = '{class qna.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@5656be13, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@73a1e9a9, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@e4acf030, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3c46e67a, [ImportsContextCustomizer@483f6d77 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@489115ef, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c6908d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@7cd3e0da]; rollback [true]
Hibernate: 
    select
        tbl.next_val 
    from
        hibernate_sequences tbl 
    where
        tbl.sequence_name=? for update

Hibernate: 
    update
        hibernate_sequences 
    set
        next_val=?  
    where
        next_val=? 
        and sequence_name=?
2021-12-03 08:47:33.424  INFO 21652 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@429bffaa testClass = UserRepositoryTest, testInstance = qna.repository.UserRepositoryTest@58695725, testMethod = findById@UserRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@5403f35f testClass = UserRepositoryTest, locations = '{}', classes = '{class qna.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@5656be13, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@73a1e9a9, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@e4acf030, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3c46e67a, [ImportsContextCustomizer@483f6d77 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@489115ef, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c6908d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

 

참고로 SEQUENCE 매핑 전략은 다음과 같이 하나의 쿼리만 날린다.

 

2021-12-03 08:48:05.515  INFO 32772 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@429bffaa testClass = UserRepositoryTest, testInstance = qna.repository.UserRepositoryTest@58695725, testMethod = findById@UserRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@5403f35f testClass = UserRepositoryTest, locations = '{}', classes = '{class qna.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@5656be13, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@73a1e9a9, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@e4acf030, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3c46e67a, [ImportsContextCustomizer@483f6d77 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@489115ef, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c6908d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@524f5ea5]; rollback [true]
Hibernate: 
    call next value for hibernate_sequence
2021-12-03 08:48:05.657  INFO 32772 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@429bffaa testClass = UserRepositoryTest, testInstance = qna.repository.UserRepositoryTest@58695725, testMethod = findById@UserRepositoryTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@5403f35f testClass = UserRepositoryTest, locations = '{}', classes = '{class qna.Application}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@5656be13, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@73a1e9a9, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@e4acf030, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3c46e67a, [ImportsContextCustomizer@483f6d77 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@489115ef, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c6908d7, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@0], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.event.ApplicationEventsTestExecutionListener.recordApplicationEvents' -> false]]

 

그래서 SEQUENCE 전략을 가져갈 수 없는 데이터베이스일 때, 많은 양의 데이터를 한 번에 넣어야 하고 싶다면 Spring Date JDBC를 사용하는 것이 바람직하다.

@Repository
@RequiredArgsConstructor
public class ItemJdbcRepositoryImpl implements ItemJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    @Value("${batchSize}")
    private int batchSize;

    @Override
    public void saveAll(List<ItemJdbc> items) {
        int batchCount = 0;
        List<ItemJdbc> subItems = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            subItems.add(items.get(i));
            if ((i + 1) % batchSize == 0) {
                batchCount = batchInsert(batchSize, batchCount, subItems);
            }
        }
        if (!subItems.isEmpty()) {
            batchCount = batchInsert(batchSize, batchCount, subItems);
        }
        System.out.println("batchCount: " + batchCount);
    }

    private int batchInsert(int batchSize, int batchCount, List<ItemJdbc> subItems) {
        jdbcTemplate.batchUpdate("INSERT INTO ITEM_JDBC (`NAME`, `DESCRIPTION`) VALUES (?, ?)",
                new BatchPreparedStatementSetter() {
                    @Override
                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                        ps.setString(1, subItems.get(i).getName());
                        ps.setString(2, subItems.get(i).getDescription());
                    }
                    @Override
                    public int getBatchSize() {
                        return subItems.size();
                    }
                });
        subItems.clear();
        batchCount++;
        return batchCount;
    }
}

출처

김영한 - 자바 ORM 표준 JPA 프로그래밍

https://semtax.tistory.com/94

https://homoefficio.github.io/2020/01/25/Spring-Data에서-Batch-Insert-최적화/

'스터디 > JPA 스터디' 카테고리의 다른 글

[JPA] 고급 매핑  (0) 2021.12.15
[JPA] 다양한 연관 관계 매핑  (0) 2021.12.11
[JPA] 연관 관계 매핑 기초  (0) 2021.12.09
[JPA] 영속성 관리  (0) 2021.11.28
[JPA] JPA 소개  (0) 2021.11.28

댓글

추천 글