[Spring Data JPA] Batch Insert로 한번에 데이터 삽입하기

이동엽·2023년 7월 18일
2

spring

목록 보기
8/21

프로젝트 와사비는 WiSoft 연구실의 기술 블로그를 제작하는 프로젝트이다.

요구사항에는 게시글에는 여러 사진이 포함될 수 있다가 존재하며, 아래와 같이 코드를 작성 할 수 있다.



💡 초기 설계

BoardService

@Transactional
public WriteBoardResponse writeBoard(final WriteBoardRequest request) {

		...

		final Board board = ... //게시글 생성 코드
		boardRepository.save(board); //게시글 저장

		final String[] images = request.imageUrls();
		if (images != null) {
		    Arrays.stream(images)
		        .forEach(image -> {
								final BoardImage boardImage 
										= BoardImage.createBoardImage(image, board);
								boardImageRepository.save(boardImage); //게시글 이미지 저장
								});
		}

	    return boardMapper.entityToWriteBoardResponse(board);
}

현재 게시글 저장 & 게시글 이미지 저장 로직이 각각 호출되는 상황이다.

이를 하나로 합칠 수는 없을까? ex) 게시글이 저장되면.. 게시글 이미지가 저장되도록..



💡 1차 개선

Board.java : cascade 설정 변경

@Entity
public class Board {

		...

		@OneToMany(mappedBy = "board", **cascade = CascadeType.PERSIST**)
	    private Set<BoardImage> boardImages = new HashSet<>();
}

수정된 BoardService

@Transactional
public WriteBoardResponse writeBoard(final WriteBoardRequest request) {

		final Board board = ... // 게시글 생성 코드

		boardRepository.save(board); //게시글 저장
	    saveImages(board, request);  //이미지 저장

    	return boardMapper.entityToWriteBoardResponse(board);
}

private void saveImages(final Board board, final WriteBoardRequest request) {

		final String[] images = request.imageUrls();

  	    if (images != null) {
		    Arrays.stream(images) //매핑 후 저장 로직을 호출하지 않음.
		       .map(image -> BoardImage.createBoardImage(image, board))
	           .toList();
		}
}

CascadeType.PERSIST 로 설정함으로써 게시글이 저장될 때, 연관된 BoardImage도 같이 저장이 된다.

코드는 줄였으나.. 실제 로직을 호출할 때 실행되는 쿼리는 어떨까?



💡 동작 확인

Request Body에는 게시글에 2개의 이미지를 첨부. (”hihi”, “hoho”)

{
    "memberId": 1,
    "title": "title",
    "content": "content",
    "tags": [
        "hihi"
    ],
    "imageUrls": [
        "hihi",
        "hoho"
    ]
}

실제 호출된 쿼리

2023-07-18T18:16:42.744+09:00 DEBUG 13435 --- [io-8080-exec-10] org.hibernate.SQL : 
    insert 
    into
        board
        (content,created_at,member_id,title,updated_at,views,id) 
    values
        (?,?,?,?,?,?,default)
2023-07-18T18:16:42.745+09:00 DEBUG 13435 --- [io-8080-exec-10] org.hibernate.SQL : 
    insert 
    into
        board_image
        (board_id,url,id) 
    values
        (?,?,default)
2023-07-18T18:16:42.746+09:00 DEBUG 13435 --- [io-8080-exec-10] org.hibernate.SQL : 
    insert 
    into
        board_image
        (board_id,url,id) 
    values
        (?,?,default)

쿼리 수를 보면 총 3개로, 게시글 저장 (1개) & 이미지 저장 (2개)가 날라가고 있다.

JPA Batch Insert를 이용해 쿼리를 하나로 날릴 수는 없을까?



💡 JPA Batch Insert

Insert문을 작성할 때, 아래와 같이 여러 값을 한번에 입력하는 방식으로, 하나의 트랜잭션으로 본다.

INSERT INTO payment_back (amount, order_id)
VALUES 
       (1, 2),
       (1, 2),
       (1, 2),
       (1, 2)

JPA에서 제공하는 Batch Insert는 어떤 방식으로 동작할까?

  • JPA의 특징인 쓰기 지연을 사용해, 저장소에 쿼리를 모아두었다가 한번에 실행!
  • 쓰기 지연 동작 그림


💡JPA Batch Insert 도입

application.yml

spring:
    jpa:
        database: mysql
        properties:
            hibernate.jdbc.batch_size: 50
            hibernate.order_inserts: true
            hibernate.order_updates: true
            hibernate.dialect: org.hibernate.dialect.~~~
            hibernate.show_sql: true

		datasource:
		        url: jdbc:~~://localhost:{port}/~~?&rewriteBatchedStatements=true
		        driver-class-name: com.~~.jdbc.Driver

설정할 값들은 아래와 같다.

  • batch_size : 한 번에 트랜잭션에서 다룰 size를 지정
  • order_inserts : 데이터 일괄 입력시 사용 (default는 false)
  • order_updates : 데이터 일괄 수정시 사용 (default는 false)
  • rewriteBatchedStatements : batch true


💡 다만 중요한 부분이 있는데, 바로 엔티티의 id 생성 방식 @GeneratedValue 이다.

눈치가 빠르다면 알아챘겠지만.. JPA Batch Insert는 쓰기 지연을 사용해 동작한다.
→ 이는 데이터베이스에 저장된 뒤 id가 할당되는 IDENTITY 전략을 사용하면 사용할 수 없다는 의미!!



💡 역시는 역시나

쎄한 느낌은 틀리지 않았다. 현재 와사비에서는 아래와 같이 id 생성 전략을 IDENTITY로 지정해놓았다.

@Entity
public class Board {

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

		...
}

IDENTITY 전략을 택했는가?

해당 전략의 특징은 주로 PostgreSQL에서 사용되는 전략으로, 데이터베이스에 위임할 수 있는데,
시퀀스보다 간편하다는 느낌에 선택했던 것 같다. (깊이 생각해보지 않은 것 같다.)



그렇다고 성능 향상을 위한 Batch Insert를 위해 id 생성 전략을 바꾸는 게 맞을까?

배보다 배꼽이 큰 경우인지는 않은지.. 팀원들과 논의를 해보아야겠다.



* To be continue *




참고자료

profile
백엔드 개발자로 등 따숩고 배 부르게 되는 그 날까지

0개의 댓글