批量保存 Jpa saveAll() 和 JdbcTemplate batchUpdate()效率对比

本文对比了使用Jpa saveAll()与JdbcTemplate batchUpdate()进行大批量文章分词保存的效率,发现后者效率远高于前者,适用于大规模数据处理。

要对两万多篇文章做分词,制作成标签云,所以涉及到批量保存的问题

1、使用Jpa saveAll()方法
<S extends T> List<S> saveAll(Iterable<S> entities);
// 例子:
List<IK> ikList = new ArrayList<>();
ikDao.saveAll(ikList);

效率奇低,按每篇文章大约500~1000个分词计算,保存需要10秒左右。2万多篇,20000*10/3600/24=…

2、使用JdbcTemplate batchUpdate()

效率高出太多了,按每篇文章大约500~1000个分词计算,保存需要1秒左右。

public Integer batchInsertIk(List<IK> ikList) {
	String sql = "insert SWJ_IK1(name,count) values(?,?)";
	jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
		public void setValues(PreparedStatement ps, int i) throws SQLException {
			String name = ikList.get(i).getName();
			int count = ikList.get(i).getCount();
			ps.setString(1, name);
			ps.setInt(2, count);
		}
		public int getBatchSize() {
			return ikList.size();
		}
	});
	return 0;
}

public Integer batchUpdateIk(List<IK> ikList) {
	String sql = "update SWJ_IK1 set count=? where name=?";
	jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
		public void setValues(PreparedStatement ps, int i) throws SQLException {
			int count = ikList.get(i).getCount();
			String name = ikList.get(i).getName();
			ps.setInt(1, count);
			ps.setString(2, name);
		}
		public int getBatchSize() {
			return ikList.size();
		}
	});
	return 0;
}

// 也可以自定义字段对应,但是要注意Object[]中元素的位置
public Integer batchInsertUsers(List<IK> list) {
    String sql = "insert SWJ_IK1(name,count) values(?,?)";
    jdbcTemplate.batchUpdate(sql,setParameters(list));
    return 0;
}
private List<Object[]> setParameters(List<IK> list){
    List<Object[]> parameters = new ArrayList<Object[]>();
    for (IK ik : list) {
        parameters.add(new Object[] { ik.getName(),ik.getCount()});
    }
    return parameters;
}
<think>嗯,用户之前问过关联表批量插入的问题,现在又具体问主表流程表如何关联。我需要仔细回顾之前的回答,确保这次回答保持一致且更深入。 用户可能是在实际开发中遇到了主表子表关联插入的问题,特别是批量操作时的效率外键关联。他们可能对JPA或MyBatis这样的ORM工具不太熟悉,或者在处理大量数据时遇到了性能问题。 之前的回答里提到了分阶段插入,先主表后子表,利用生成的主键ID来关联流程表。这次需要更详细解释关联机制,特别是外键的设置如何在代码中维护这种关联。 可能用户对事务控制、批量优化配置还有疑问,需要强调这些技术点。同时,他们可能担心数据一致性性能,比如主表插入后如何确保子表正确引用,以及如何处理大量数据时的内存速度问题。 需要检查是否有遗漏的关键点,比如使用JPA的级联操作与显式插入的对比,或者如何在没有ORM的情况下使用JDBC直接批量插入。还要考虑常见问题,如主键冲突、关联丢失,以及不同数据量级的解决方案推荐。 最后,确保语言清晰,步骤明确,让用户能一步步理解实现主表与流程表的关联插入。可能需要补充示例代码,特别是如何在循环中建立对象间的关联,以及事务管理的重要性。</think>以下是关联主表与流程表进行批量插入的详细技术方案,通过**分阶段处理 + 关联绑定**实现高效操作: --- ### $\textcolor{green}{核心实现步骤}$ #### $\textcolor{orange}{步骤1:主表批量插入(生成主键)}$ ```java // 主表实体 public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long orderId; // 自增主键 private String orderNo; // 其他字段... } // 批量插入主表 List<Order> orders = rawDataList.stream() .map(raw -> new Order(raw.getOrderNo())) .collect(Collectors.toList()); orderRepository.saveAll(orders); // 批量插入 orderRepository.flush(); // 强制生成ID ``` $\textcolor{red}{关键点:}$ - 必须执行`flush()`使数据库立即生成主键ID - 使用`IDENTITY`主键策略时,Hibernate会禁用批量插入(需特殊处理) --- #### $\textcolor{orange}{步骤2:建立关联关系}$ ```java // 流程表实体 public class OrderProcess { @ManyToOne @JoinColumn(name = "order_id") // 外键关联 private Order order; private String processType; // 其他字段... } // 构建流程表数据 List<OrderProcess> processes = new ArrayList<>(); for (int i=0; i<rawDataList.size(); i++) { Order order = orders.get(i); // 获取对应主表对象 RawData raw = rawDataList.get(i); // 添加多个流程记录 processes.add(createProcess(order, "PAYMENT", raw.getPaymentData())); processes.add(createProcess(order, "APPROVAL", raw.getApprovalData())); } private OrderProcess createProcess(Order order, String type, Data data) { OrderProcess process = new OrderProcess(); process.setOrder(order); // 核心关联设置 process.setProcessType(type); process.setStartTime(data.getTimestamp()); return process; } ``` $\textcolor{red}{关联绑定原理:}$ 1. 通过内存中已保存的主表对象直接设置关联 2. 依赖ORM框架(如JPA)自动处理外键关系 3. 最终生成SQL时自动填充`order_id`字段 --- ### $\textcolor{green}{性能优化方案}$ #### $\textcolor{orange}{方案A:JPA级联操作(适合小数据量)}$ ```java @Entity public class Order { @OneToMany(mappedBy = "order", cascade = CascadeType.PERSIST) // 级联保存 private List<OrderProcess> processes; } // 使用示例 orders.forEach(order -> order.addProcess(new OrderProcess("SHIPPING")) ); orderRepository.saveAll(orders); // 自动级联保存 ``` $\textcolor{blue}{特点:}$ - 开发简单但性能较差 - 不适合超过1000条的批量操作 #### $\textcolor{orange}{方案B:JDBC批量插入(10万+数据量)}$ ```java String SQL = "INSERT INTO order_process(order_id, process_type) VALUES (?,?)"; jdbcTemplate.batchUpdate(SQL, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) { Order order = orders.get(i); ps.setLong(1, order.getOrderId()); // 绑定主表ID ps.setString(2, processTypes.get(i)); } @Override public int getBatchSize() { return orders.size(); } }); ``` $\textcolor{blue}{优势:}$ - 比JPA快5-10倍 - 可配合分页处理(每批5000条) --- ### $\textcolor{green}{关联关系验证方法}$ #### $\textcolor{orange}{验证1:内存对象校验}$ ```java // 检查主键是否生成 orders.forEach(order -> { if (order.getOrderId() == null) { throw new IllegalStateException("主表ID未生成"); } }); // 检查关联完整性 processes.forEach(process -> { if (process.getOrder() == null) { throw new IllegalArgumentException("缺失主表关联"); } }); ``` #### $\textcolor{orange}{验证2:数据库外键约束}$ ```sql ALTER TABLE order_process ADD CONSTRAINT fk_order_id FOREIGN KEY (order_id) REFERENCES order(order_id) ON DELETE CASCADE; ``` $\textcolor{red}{注意:}$ - 生产环境建议开启外键约束 - 测试环境可关闭约束提升插入速度 --- ### $\textcolor{green}{不同场景选型建议}$ | 场景特征 | 推荐方案 | 数据量参考 | |---------|---------|-----------| | 强事务要求 | JPA分阶段插入 | < 5000条 | | 高并发写入 | JDBC批量+连接池 | 1万~50万条 | | 超大数据量 | 文件导入+存储过程 | > 100万条 | | 多表复杂关联 | MyBatis批量模板 | 任意量级 | --- $\textcolor{red}{常见问题排查}$: 1. **出现重复主键** - 检查主表是否配置了正确的`@GeneratedValue` - 确认没有在代码中手动设置主键值 2. **外键约束失败** ```sql -- 查询孤儿记录 SELECT * FROM order_process WHERE order_id NOT IN (SELECT order_id FROM order) ``` - 使用数据库校验工具检查数据一致性 3. **批量插入速度慢** - 在JDBC URL添加参数: `rewriteBatchedStatements=true&allowPublicKeyRetrieval=true` - 调整事务提交频率(每5000条提交一次)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值