server:
port: 8081
spring:
jpa:
hibernate:
ddl-auto: update
// 여기밑에 설정해줘야, Flush, Insert 갯수 카운트 가능
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
generate_statistics: true
show-sql: true
@Test
@DisplayName(value = "트랜잭션 (X), saveAll(X), 배치 (X), 벌크 (X)")
public void test1() {
Long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
Users user = new Users(
"username" + i,
"password" + i,
"email" + i + "@gmail.com"
);
userRepository.save(user);
}
Long end = System.currentTimeMillis();
log.info(" --- ");
log.info("test1 실행시간: {}", (end - start));
log.info(" --- ");
jpaQueryCount();
}
2.2초가 소요된 모습이다.

트랜잭션환경으로 묶여있지않기 떄문에, 영속성 컨텍스트에 데이터가 쌓이지않고, 개별적으로 처리되어 1000개의 FLUSH가 호출 되는 것을 알 수 있다

Flush가 총 1000번 일어난 모습이다.
@Test
@DisplayName(value = "트랜잭션 (O), saveAll(X), 배치 (X), 벌크 (X)")
@Transactional
@Commit
public void test2() {
Long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
Users user = new Users(
"username" + i,
"password" + i,
"email" + i + "@gmail.com"
);
userRepository.save(user);
}
Long end = System.currentTimeMillis();
log.info(" --- ");
log.info("test1 실행시간: {}ms", (end - start));
log.info(" --- ");
jpaQueryCount();
}
0.6초가 소요된 모습이다.

1000번의 쿼리가 하나의 트랜잭션으로 묶여있기떄문에, 커밋되는 시점에 한번에 DB에 저장되면서 네트워크 I/O비용이 1번만 발생한다.

@Test
@DisplayName(value = "트랜잭션 (O), saveAll(O), 배치 (X), 벌크 (X)")
@Transactional
@Commit
public void test3() {
Long start = System.currentTimeMillis();
List<Users> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Users user = new Users(
"username" + i,
"password" + i,
"email" + i + "@gmail.com"
);
users.add(user);
}
userRepository.saveAll(users);
Long end = System.currentTimeMillis();
log.info(" --- ");
log.info("test1 실행시간: {}ms", (end - start));
log.info(" --- ");
}
0.6초 소모로 2번 테스트케이스와 유사한 시간 속도를 보여준다

saveall역시 트랜잭션환경 안에서 호출되니 1번의 FLUSH만 호출하는 모습을 확인 할 수 있다


@Test
@DisplayName(value = "트랜잭션 (O), saveAll(O), 배치 (O), 벌크 (X)")
@Transactional
@Commit
public void test4() {
Long start = System.currentTimeMillis();
List<Users> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
Users user = new Users(
"username" + i,
"password" + i,
"email" + i + "@gmail.com"
);
users.add(user);
}
userRepository.saveAll(users);
Long end = System.currentTimeMillis();
log.info(" --- ");
log.info("test 실행시간: {}ms", (end - start));
log.info(" --- ");
}
server:
port: 8081
spring:
datasource:
# url에 설정 추가
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&rewriteBatchedStatements=true&profileSQL=true&logger=Slf4JLogger&maxQuerySizeToLog=999999
username: user
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
jdbc:
# 여기 밑에 배치 설정 추가
batch_size: 100
generate_statistics: true
show-sql: true
data:
redis:
host: redis
port: 6379
-
배치 Insert가 정상적으로 적용된 모습이다

1 FLUSH가 호출 되었다

배치사이즈 100에서, 1000개로 바꾸었을때의 속도는 하기와 같다.
@Test
@DisplayName(value = "트랜잭션 (O), saveAll(X), 배치 (X), 벌크 (O)")
@Transactional
@Commit
public void test5() {
Long start = System.currentTimeMillis();
// 벌크 Insert 쿼리 준비
StringBuilder bulkInsertQuery = new StringBuilder("INSERT INTO users (username, password, email, id) VALUES ");
List<Users> users = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
users.add(new Users("username" + i, "password" + i, "email" + i + "@gmail.com"));
}
for (int i = 0; i < users.size(); i++) {
Users user = users.get(i);
bulkInsertQuery.append(String.format("('%s', '%s', '%s', DEFAULT)", user.getUsername(), user.getPassword(), user.getEmail()));
if (i < users.size() - 1) {
bulkInsertQuery.append(", ");
}
}
// 네이티브 쿼리 실행
em.createNativeQuery(bulkInsertQuery.toString()).executeUpdate();
Long end = System.currentTimeMillis();
log.info(" --- ");
log.info("벌크 Insert 실행시간: {}ms", (end - start));
log.info(" --- ");
}
0.06초가 소요된 모습이다.

실행된 쿼리문

네이티브 쿼리 기반이다보니, flush count는 되지 않는 모습이다.

https://dev-coco.tistory.com/169
https://dkswnkk.tistory.com/682