基于Spring Batch实现订单对象向多表写入的技术问询
使用Spring Batch CompositeItemWriter实现多表批量写入
嗨,我来帮你搞定这个多表写入的问题!你提到的CompositeItemWriter正是Spring Batch专门用来处理一个Item需要分发给多个Writer处理的场景,正好匹配你把Order对象拆分到主订单表、商品表、配送表、账单表的需求。下面我给你一步步讲清楚怎么配置和使用,附带完整代码示例。
核心思路
CompositeItemWriter的本质是把同一个Item按顺序传递给多个子Writer执行。针对你的场景,我们需要:
- 先写主订单表,并确保拿到生成的订单ID(用来关联子表)
- 再把Order里的子对象(items、shipping、billing)分别写入对应表,同时关联主订单ID
步骤1:定义各个子表的ItemWriter
假设你的Order对象结构是这样的(简化版):
public class Order { private Long id; // 主订单ID,数据库自增 private String prop1; private String prop2; // ... 其他主订单字段 private List<OrderItem> items; private ShippingInfo shipping; private BillingInfo billing; // getter/setter省略 }
1.1 主订单表Writer
这个Writer负责写入主订单数据,关键要配置回填自增ID,这样后续子表才能拿到关联ID:
@Bean public JdbcBatchItemWriter<Order> orderMainWriter(DataSource dataSource) { return new JdbcBatchItemWriterBuilder<Order>() .dataSource(dataSource) .sql("INSERT INTO orders (prop1, prop2) VALUES (:prop1, :prop2)") // 替换成你的实际字段 .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) // 配置获取数据库生成的自增ID,回填到Order的id字段 .generatedKeysColumns("id") .generatedKeysRowMapper(new BeanPropertyRowMapper<>(Order.class)) .build(); }
1.2 订单商品表Writer
专门处理Order.items列表,注意SQL里要包含order_id关联字段:
@Bean public JdbcBatchItemWriter<OrderItem> orderItemsWriter(DataSource dataSource) { return new JdbcBatchItemWriterBuilder<OrderItem>() .dataSource(dataSource) .sql("INSERT INTO order_items (order_id, product_id, quantity) VALUES (:orderId, :productId, :quantity)") .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) .build(); }
1.3 配送/账单信息Writer
和商品表Writer逻辑一致,只需替换对应的SQL和实体类:
// 配送信息Writer @Bean public JdbcBatchItemWriter<ShippingInfo> shippingInfoWriter(DataSource dataSource) { return new JdbcBatchItemWriterBuilder<ShippingInfo>() .dataSource(dataSource) .sql("INSERT INTO shipping_info (order_id, address, phone) VALUES (:orderId, :address, :phone)") .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) .build(); } // 账单信息Writer @Bean public JdbcBatchItemWriter<BillingInfo> billingInfoWriter(DataSource dataSource) { return new JdbcBatchItemWriterBuilder<BillingInfo>() .dataSource(dataSource) .sql("INSERT INTO billing_info (order_id, card_no, amount) VALUES (:orderId, :cardNo, :amount)") .itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>()) .build(); }
步骤2:自定义Writer处理子表写入
因为子对象需要先关联主订单ID才能写入,我们可以写一个自定义Writer,封装所有子表的写入逻辑:
@Component public class MultiPartOrderWriter implements ItemWriter<Order> { private final JdbcBatchItemWriter<OrderItem> orderItemsWriter; private final JdbcBatchItemWriter<ShippingInfo> shippingInfoWriter; private final JdbcBatchItemWriter<BillingInfo> billingInfoWriter; // 构造注入各个子Writer public MultiPartOrderWriter(JdbcBatchItemWriter<OrderItem> orderItemsWriter, JdbcBatchItemWriter<ShippingInfo> shippingInfoWriter, JdbcBatchItemWriter<BillingInfo> billingInfoWriter) { this.orderItemsWriter = orderItemsWriter; this.shippingInfoWriter = shippingInfoWriter; this.billingInfoWriter = billingInfoWriter; } @Override public void write(List<? extends Order> orders) throws Exception { for (Order order : orders) { // 给子对象设置关联的订单ID if (order.getItems() != null) { order.getItems().forEach(item -> item.setOrderId(order.getId())); } if (order.getShipping() != null) { order.getShipping().setOrderId(order.getId()); } if (order.getBilling() != null) { order.getBilling().setOrderId(order.getId()); } // 写入各个子表 if (order.getItems() != null && !order.getItems().isEmpty()) { orderItemsWriter.write(new ArrayList<>(order.getItems())); } if (order.getShipping() != null) { shippingInfoWriter.write(Collections.singletonList(order.getShipping())); } if (order.getBilling() != null) { billingInfoWriter.write(Collections.singletonList(order.getBilling())); } } } }
步骤3:组合成CompositeItemWriter
现在把主订单Writer和自定义的子表Writer组合起来,注意顺序必须是先写主订单,再写子表:
@Bean public CompositeItemWriter<Order> compositeOrderWriter(JdbcBatchItemWriter<Order> orderMainWriter, MultiPartOrderWriter multiPartOrderWriter) { CompositeItemWriter<Order> compositeWriter = new CompositeItemWriter<>(); compositeWriter.setDelegates(Arrays.asList(orderMainWriter, multiPartOrderWriter)); return compositeWriter; }
步骤4:配置Step使用组合Writer
最后把这个CompositeItemWriter配置到你的Batch Step里:
@Bean public Step orderProcessingStep(StepBuilderFactory stepBuilderFactory, ItemReader<Order> orderXmlReader, // 你已经实现的XML解析Reader CompositeItemWriter<Order> compositeOrderWriter) { return stepBuilderFactory.get("orderProcessingStep") .<Order, Order>chunk(100) // 按批次处理,根据数据量调整大小 .reader(orderXmlReader) .writer(compositeOrderWriter) .build(); }
关键注意事项
- 事务一致性:Spring Batch的Step默认是一个事务,如果任何一个Writer写入失败,整个批次都会回滚,保证主表和子表的数据一致性。
- 主键生成:如果不用数据库自增ID,也可以在XML解析后的ItemProcessor里提前生成全局唯一ID(比如UUID),这样主订单和子表可以直接使用这个ID,无需等待数据库回填。
- 批次大小:chunk值不要设置太大,避免内存溢出,建议根据你的数据量和服务器配置调整。
内容的提问来源于stack exchange,提问作者Pinaki Mukherjee
相关产品推荐
相关产品推荐

