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 |