Database&Jpa

[DB] EntityManager Multirow Insert

향각산 2024. 7. 30. 20:00

EntityManager를 통해서 Multirow Insert를 사용하는 두 가지 방법입니다.

JPA에 다음과 같은 ID 전략을 사용할때 사용하기 좋습니다.

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

 

IDENTITY 전략을 사용하면, 배치인서트가 원하는대로 동작하지 않을 가능성이 큽니다.

또한 Auditing이나 외래키 등 생각하지 못한 에러가 발생하는 경우도 많습니다.

그래서 EntityManager를 통해 마이그레이션 한 경험을 정리합니다.

 

1. NativeQuery 사용하기

쿼리로만 작성하게 되면, 빈값에 대한 NULL처리를 신경써줘야 합니다.

또한 '이나 "같은 문자에 들어가는 특수문자도 신경써서 처리해줘야 합니다.

 

@SpringBootTest(properties = {
        "spring.profiles.active=dev"
})
public class ProductMigrationTest {

    @PersistenceUnit
    private EntityManagerFactory emf;

    @Test
    void productMigration() throws Exception {
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();
        
        // 마이그레이션 코드 구현
        
        transaction.commit();
        em.close();
    }

 

기존적인 코드 구조는 위와 같습니다. JPA를 사용하는게 아니라, 직접 트랜잭션을 관리해줘야 합니다.

밑에서 소개하는 QueryParameter도 기본적인 구조는 같습니다.

 

다음은 구현코드입니다.

예를 들어 대량의 Product를 한번에 넣으려고 할때, 즉 List<Product> 가 있다고 가정하겠습니다.

  @Test
    void productMigration() throws Exception {
        EntityManager em = emf.createEntityManager();
        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        // 마이그레이션 코드 구현
        List<Product> products = new ArrayList<>();
        
        Query query = em.createNativeQuery(createQuery(products));
        query.executeUpdate();

        transaction.commit();
        em.close();
    }

    private String createQuery(List<Product> products) {
        StringBuilder sql = new StringBuilder(
                "INSERT INTO product (name, price) VALUES ");

        for (int i = 0; i < products.size(); i++) {
            Product product = products.get(i);
            sql.append("('")
                    .append(StringUtils.hasText(product.getName()) ? "'" + product.getName() + "'" : "NULL").append(", ")
                    .append(product.getPrice()).append(", ");
            if (i < products.size() - 1) {
                sql.append(", ");
            }
        }
        return sql.toString();
    }

 

이런식으로 작성하면 multi row 쿼리를 작성할 수 있으며, 한번에 멀티로우로 들어가는것을 볼 수 있습니다.

만약 products 가 너무 크다면, 특정개수만큼 쿼리를 실행하는것이 좋습니다.

 

 

2. NativeQuery + QueryParameter 사용하기

QueryParameter를 사용하면, NULL처리나 특수기호 처리를 안해줘도 되는 장점이 있습니다.

다만 쿼리부터 다르게 생겼기때문에 더 복잡합니다.

  private StringBuilder createSql(List<Product> list) {
        StringBuilder sql = new StringBuilder(
                "INSERT INTO product (name, price) VALUES "
        );

        for (int i = 0; i < list.size(); i++) {
            sql.append("(:name").append(i).append(", :price").append(i).append(")");
            if (i < list.size() - 1) {
                sql.append(", ");
            }
        }
        return sql;
    }


구조는 비슷합니다.

실제 작성되는 쿼리는 다음과 같이 생겼습니다.

INSERT INTO product (name, price) VALUES (:name1, :price1), (:name2, :price2) ...

이런식으로 파라미터와 매핑할 데이터를 먼저 작성해줍니다.

 

그리고 List<Product>를 기준으로 파라미터를 추가해줘야 합니다.

StringBuilder sql = createSql(list);
Query query = em.createNativeQuery(sql.toString());
for (int i1 = 0; i1 < list.size(); i1++) {
    Product content = list.get(i1);
    query.setParameter("name" + i1, product.getName());
    query.setParameter("price" + i1, product.getPrice());
}
query.executeUpdate();

 

마지막에 transaction.commit()과 em.close()는 동일하게 해주면 됩니다.

 

 

'Database&Jpa' 카테고리의 다른 글

[DB] WITH AS 구문 사용하기  (0) 2024.07.25
Mysql GenerationType.AUTO 에러  (1) 2023.11.09
Mongodb 다운로드  (0) 2022.08.02
expected "identifier"; SQL statement:  (0) 2021.12.28
[MariaDB] 한글 깨짐  (0) 2020.09.11
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함