16. [JPA] Bulk Insert

프로그래밍|2017. 12. 14. 19:50

한번에 대량의 데이터를 저장해야 하는 경우가 있을 것이다.

예를 들어 엑셀 업로드 하면 해당 데이터들을 DB에 저장해야 한다.

데이터가 3만 건 존재한다고 했을 때 어떻게 처리 해야 할 것인가?


먼저 주의점에 대해서 나열해보자.

  • 3만 건의 데이터를 영속화 한 뒤 flush 하는 방법은 과연 괜찮을까? 메모리가 충분하다면 3만 건 데이터는 문제 없이 처리될 것이다. 다만, 그 이상의 데이터를 영속화 하게 된다면 out of memory 예외를 만나게 된다.
  • 많은 데이터를 영속화해서 flush 하면 메모리 오류가 나니~ 건건이 영속화하고 flush 하면 되겠네? 라고 생각하면 이 또한 큰 오산이다. 처리 속도도 오래 걸릴 뿐더러 데이터 양만큼의 통신이 발생하기 때문에 비효율적이다.

그럼 방법이 뭔데?

의외로 해답은 단순하다.

200개씩 데이터를 영속화하고 flush 를 반복해 주면 된다.

이와같은 방법은 처리 속도, 통신 비용 감소, 메모리 오류 방지를 할 수 있다.


테스트 해보자.

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
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class BulkTest {
 
    @PersistenceContext
    private EntityManager em;
 
    @Value("${spring.jpa.properties.hibernate.jdbc.batch_size}")
    private int batchSize;
 
    @Test
    public void batch사이즈() {
        assertThat(20, is(batchSize));
    }
 
    @Test
    @Transactional
    public void bulkInsert() {
 
        for (int i = 0; i < 10000; i++) {
            BulkMember member = new BulkMember();
            member.setId(i);
            member.setName("nklee");
            em.persist(member);
 
            if (i % batchSize == 0) {
                // Flush a batch of inserts and release memory.
                em.flush();
                em.clear();
            }
        }
 
        em.flush();
        em.clear();
    }
}
 
 
@Data
@Entity
@Table(name = "BULK_MEMBER")
class BulkMember {
 
    @Id
    private int id;
 
    @Lob
    private String name;
 
}
cs


한 가지 주의해야 할 점이 있다.

엔티티 기본키 생성 전략을 @GeneratedValue(strategy = GenerationType.IDENTITY) 로 사용하면 의도한 대로 bulk insert 처리가 되지 않는다.

1
2
3
@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
cs


왜냐하면 IDENTITY 전략은 DB의 auto_increment 를 사용하기 때문이다.

무슨 말이냐하면 엔티티 @Id 에 값이 채워지기 위해서는 DB에 insert를 해야지만 그 값을 알 수 있다는 것이다.

즉, 다음과 같이 동작하기 때문에 bulk insert 처리가 의미 없어지게 된다.

DB에 일단 저장 -> auto_increment로 생성된 값을 가져온 후 -> 엔티티를 영속화

댓글()