
다량의 데이터를 한 번에 삽입하는 방법입니다.
INSERT INTO user (name, email) VALUES ('User A', 'a@test.com');
INSERT INTO user (name, email) VALUES ('User B', 'b@test.com');
INSERT INTO user (name, email) VALUES ('User C', 'c@test.com');
INSERT INTO user (name, email) VALUES
('User A', 'a@test.com'),
('User B', 'b@test.com'),
('User C', 'c@test.com');
개별 Insert의 경우 각 쿼리 별로 DB I/O를 해야하지만, Batch Insert는 하나의 쿼리문으로 여러 데이터를 처리하기 때문에 성능이 뛰어납니다.
보통 JPA, MySQL을 사용할 때 위 코드처럼 IDENTITY 전략을 선택하여 PK 값을 자동으로 증가시키는 방식을 사용합니다.
@Entity
@NoArgsConstructor
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
JPA의 구현체인 Hibernate는 IDENTITY 전략을 사용할 때 Batch Insert가 동작하지 않습니다.
Hibernate는 엔티티를 식별자(ID) 기반으로 영속성 컨텍스트에 관리하는데, IDENTITY 전략은 INSERT를 수행해야만 ID를 알 수 있어 INSERT를 지연하거나 모아둘 수 없기 때문입니다.
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
...
}
@Test
@Transactional
void 하이버네이트_테스트() throws InterruptedException {
Member member = new Member("testUser");
memberRepository.save(member);
System.out.println("before first sleep");
sleep(10000);
TestEntity testEntity = new TestEntity("aaaa");
testEntityRepository.save(testEntity);
System.out.println("before second sleep");
sleep(10000);
}

이와 같이 ID 생성 전략을 IDENTITY로 한 경우엔 쓰기 지연이 동작하지 않고 INSERT 쿼리가 바로 나가는 것을 확인할 수 있습니다.
SEQUENCE인 경우에는 DB
select next value for test_entity_seq
위 쿼리로 ID 값만 받아와서 엔티티에 채워 넣고, 1차 캐시에 저장합니다. 그 후 트랜잭션이 끝날 때, INSERT 쿼리가 수행됩니다.
IDENTITY가 아닐 경우에는 옵션을 통해 Batch Insert를 사용할 수 있습니다.
spring.jpa.properties.hibernate.jdbc.batch_size=N
MySQL에서 Batch Insert를 구현하기 전 DB URL에 'rewriteBatchedStatements=true' 파라미터를 추가해야 합니다. true로 설정하지 않으면 Insert 쿼리가 단건으로 수행됩니다.
String sql = "INSERT INTO user (name, email) VALUES (?, ?)";
jdbcTemplate.batchUpdate(sql,
users,
1000, // batch size
(ps, user) -> {
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
}
);
String sql = "INSERT INTO user (name, email) VALUES (?, ?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
User user = users.get(i);
ps.setString(1, user.getName());
ps.setString(2, user.getEmail());
}
@Override
public int getBatchSize() {
return users.size();
}
});
두가지 방식으로 Batch Insert를 구현할 수 있습니다.