# 상품 배치 트랜잭션 ## 문제 - 배치를 통해 상품을 5분에 한 번씩 전체 삭제하고 insert 하는데 배치가 돌 떄마다 메인 페이지에서 상품이 보이지 않는 현상이 발생함. ### 해결책 - 트랜잭션 격리수준 변경 - SERIALIZABLE로 변경? 어떤 사이드이펙트가 있을까... ```java package fnf.wp.online_store_batch.sub.category.infrastructure.batch; import fnf.wp.online_store_batch.sub.category.core.batch.GoodsImage; import fnf.wp.online_store_batch.sub.category.core.batch.ToBeGoodsDsGateway; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; import java.util.stream.Collectors; import javax.sql.DataSource; import lombok.NonNull; import lombok.val; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.retry.annotation.Backoff; import org.springframework.retry.annotation.EnableRetry; import org.springframework.retry.annotation.Retryable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; @Service @EnableRetry @Retryable( retryFor = ConcurrencyFailureException.class, backoff = @Backoff(random = true, delay = 1000, maxDelay = 5000), maxAttempts = 3 ) @Transactional( transactionManager = "subTransactionManager", isolation = Isolation.READ_COMMITTED, timeout = 30 ) public class ToBeGoodsDataMapper implements ToBeGoodsDsGateway { private final JdbcTemplate jdbcTemplate; public ToBeGoodsDataMapper(@Qualifier("subDataSource") DataSource subDataSource) { this.jdbcTemplate = new JdbcTemplate(subDataSource); } @Override public void deleteImages(@NonNull List<String> erpGoodsNos) { val placeholders = erpGoodsNos.stream().map(n -> "?").collect(Collectors.joining(",")); val sql = "delete from simple_goods_image where type != 'VIDEO' and erp_goods_no in (" + placeholders + ")"; jdbcTemplate.update(sql, erpGoodsNos.toArray()); } @Override public void deleteImages() { jdbcTemplate.execute("delete from simple_goods_image"); } @Override public void insertImages(@NonNull List<GoodsImage> asIsImages) { jdbcTemplate.batchUpdate( "insert into simple_goods_image(erp_goods_no, url, sequence, cut, type, asis_composed_key)" + " values (?, ?, ?, ?, ?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, asIsImages.get(i).erpGoodsNo()); ps.setString(2, asIsImages.get(i).url()); ps.setLong(3, asIsImages.get(i).sequence()); ps.setString(4, asIsImages.get(i).cut().name()); ps.setString(5, asIsImages.get(i).type().name().replace("THNAIL", "THUMBNAIL")); ps.setString(6, asIsImages.get(i).asIsComposedKey()); } @Override public int getBatchSize() { return asIsImages.size(); } }); } @Override public void deleteSizes(@NonNull List<String> erpGoodsNos) { val placeholders = erpGoodsNos.stream().map(n -> "?").collect(Collectors.joining(",")); val sql = "delete from simple_goods_size where erp_goods_no in (" + placeholders + ")"; jdbcTemplate.update(sql, erpGoodsNos.toArray()); } @Override public void deleteSizes() { jdbcTemplate.execute("delete from simple_goods_size"); } @Override public void insertSizes(@NonNull List<GoodsSize> asIsSizes) { jdbcTemplate.batchUpdate( "insert into simple_goods_size(erp_goods_no, name, status, sku_no)" + " values (?, ?, ?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, asIsSizes.get(i).erpGoodsNo()); ps.setString(2, asIsSizes.get(i).name()); ps.setString(3, asIsSizes.get(i).status().name()); ps.setString(4, asIsSizes.get(i).skuNo()); } @Override public int getBatchSize() { return asIsSizes.size(); } }); } @Override public void deleteGoods() { jdbcTemplate.execute("delete from simple_goods"); } @Override public void insertGoods(@NonNull List<Goods> asIsGoods) { jdbcTemplate.batchUpdate( "insert into simple_goods(" + "erp_goods_no, " + "design_group_no, " + "pc_name, " + "mobile_name, " + "sale_status, " + "retail_price, " + "customer_price, " + "color_code, " + "color_name, " + "registration_date, " + "is_display, " + "approval_code, " + "sale_code, " + "is_new, " + "new_start_date, " + "new_end_date, " + "color_group_code, " + "color_group_name, " + "color_group_hex, " + "tag_name, " + "tag_color, " + "mall_id, " + "is_reservation_sales_goods" + ") values (" + "?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?)", new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, asIsGoods.get(i).erpGoodsNo()); ps.setString(2, asIsGoods.get(i).designGroupNo()); ps.setString(3, asIsGoods.get(i).pcName()); ps.setString(4, asIsGoods.get(i).mobileName()); ps.setString(5, asIsGoods.get(i).saleStatus().name()); ps.setInt(6, asIsGoods.get(i).retailPrice()); ps.setInt(7, asIsGoods.get(i).customerPrice()); ps.setString(8, asIsGoods.get(i).colorCode()); ps.setString(9, asIsGoods.get(i).colorName()); ps.setDate(10, Date.valueOf(asIsGoods.get(i).registrationDate().toLocalDate())); ps.setBoolean(11, asIsGoods.get(i).isDisplay()); ps.setString(12, asIsGoods.get(i).approvalCode().name()); ps.setString(13, asIsGoods.get(i).saleCode().name()); ps.setBoolean(14, asIsGoods.get(i).isNew()); ps.setDate(15, Date.valueOf(asIsGoods.get(i).newStartDate())); ps.setDate(16, Date.valueOf(asIsGoods.get(i).newEndDate())); ps.setString(17, asIsGoods.get(i).colorGroupCode()); ps.setString(18, asIsGoods.get(i).colorGroupName()); ps.setString(19, asIsGoods.get(i).colorGroupHex()); ps.setString(20, asIsGoods.get(i).tagName()); ps.setString(21, asIsGoods.get(i).tagColor()); ps.setString(22, asIsGoods.get(i).mallId().name()); ps.setBoolean(23, asIsGoods.get(i).isReservationSalesGoods()); } @Override public int getBatchSize() { return asIsGoods.size(); } }); } } ```