当前位置:首页 > 问答 > 正文

记录下用Java怎么一次性往数据库里加好多条数据,省事又高效的那种方法分享

这个问题问得特别好,尤其是在处理大量数据,比如导入Excel、批量初始化数据或者从其他系统拉取一堆数据存到自己数据库的时候,一条一条地往数据库里插,那速度简直能急死人,想象一下,你要往一个本子上抄写1000个电话号码,你是抄一个数字就合上本子一次,还是连续抄完20个再合上本子一次快呢?肯定是后者嘛,往数据库里插数据也是同样的道理,在Java里,我们有好几种“省事又高效”的方法来干这事儿,下面我就直接跟你说说最常用、最实在的几种。

(来源:基于常见的JDBC编程实践和框架使用经验)

最基础也是最核心的方法,就是利用JDBC本身提供的批处理功能,哪怕你不用任何花哨的框架,用最原生的JDBC也能搞定,它的核心思想就是“攒一波,送一波”,具体怎么做呢?你不是要执行很多条INSERT语句吗?普通的做法是每准备好一条语句,就立刻让数据库执行一条,而批处理是,你先创建一个Statement或者PreparedStatement对象,然后在一个循环里,反复设置好每条数据的参数,但先不执行,而是调用addBatch()方法,把这条语句“添加到一个批次里”,等你觉得攒得差不多了,比如攒了100条或者500条,再一次性调用executeBatch()方法,让数据库把这一批语句一口气执行掉,最后别忘了调用clearBatch()清空一下,准备下一批。

(来源:Oracle官方JDBC教程及相关技术博客)

记录下用Java怎么一次性往数据库里加好多条数据,省事又高效的那种方法分享

这样做为什么快呢?主要是因为大大减少了网络通信的开销和数据库事务开启关闭的次数,你想啊,数据库每执行一条SQL,它内部都要做一些检查、解析、优化的工作,如果你发1000条单独的INSERT,这个准备工作就要重复1000次,但如果你把1000条语句打包成一个批次发送,数据库可能只需要做一次准备工作,效率自然就上来了,不过这里有个小细节要注意,默认情况下,JDBC是自动提交事务的,也就是每条语句都是一个独立的事务,为了批处理能达到最佳效果,你最好在开始批处理之前,先调用connection.setAutoCommit(false)关闭自动提交,等所有批次都执行完了,再手动connection.commit()提交一次大事务,这样更快。

(来源:数据库事务处理相关原理及性能优化建议)

直接用JDBC写批处理代码有点繁琐,得自己处理连接、语句、异常啥的,在实际项目中,我们更常用一些现成的持久层框架,它们把这些脏活累活都封装好了,用起来更省事,这里就必须提一下MyBatis这个家伙了。

记录下用Java怎么一次性往数据库里加好多条数据,省事又高效的那种方法分享

(来源:MyBatis官方文档及社区实践)

MyBatis提供了非常方便的批量操作支持,主要有两种方式让你选,第一种是直接在Mapper的XML映射文件里,写一个foreach循环标签,你可以把你的数据,比如一个List集合,直接传给MyBatis,然后在XML里,你写一条INSERT语句,但不是写死值,而是用<foreach>标签把List遍历出来,生成一条像INSERT INTO table (a, b) VALUES (1,2), (3,4), (5,6)...这样的巨型SQL语句,这种方法相当于把成百上千条数据合并成一条SQL,数据库只需要执行一次,对于数据量不是特别巨大的情况,效率非常高,可以说是最简单粗暴又有效的方法。

(来源:MyBatis官方文档中关于动态SQL foreach的章节)

记录下用Java怎么一次性往数据库里加好多条数据,省事又高效的那种方法分享

这种“一条大SQL”的方式有个潜在问题,就是如果一次要插入的数据量实在太大了,比如几十万条,生成的SQL字符串可能会非常长,甚至超出数据库对单条SQL长度的限制,这时候,MyBatis的第二种方式就派上用场了,那就是用SqlSession的批处理执行器,你在获取SqlSession的时候,可以指定一个执行类型为ExecutorType.BATCH,这样,你通过这个SqlSession获取Mapper接口,然后多次调用Mapper的插入方法,MyBatis在背后会自动帮你把这些操作积攒起来,形成批处理,而不是每次调用都立刻执行,等你全部调用完之后,再调用一下sqlSession.commit(),它才真正一次性发送给数据库,这其实就相当于帮你实现了我们最开始说的那个JDBC批处理的过程,但是代码写起来清爽多了,就像平常写单条插入一样。

(来源:MyBatis官方文档中关于SqlSessionFactory和执行器的说明)

除了MyBatis,另一个流行的框架Spring Data JPA也有自己的批处理妙招,JPA通常是通过操作实体对象,然后由框架自动生成SQL来持久化的,要实现批处理,你需要在配置文件中开启批处理支持,设置一个合适的批次大小,比如hibernate.jdbc.batch_size=50,你在代码里,在一个事务范围内,循环将实体对象persist(持久化)到持久化上下文中,Hibernate(JPA的一个常用实现)会在背后盯着,当持久化上下文中的对象数量达到你设置的批次大小时,它会自动触发一个批处理INSERT操作,这种方式对程序员来说更“隐形”,你几乎感觉不到批处理的存在,只需要关注业务逻辑和配置就好了。

(来源:Hibernate官方文档关于批量处理的指南)

简单总结对比一下,如果你的项目很单纯,不想引入复杂框架,直接用JDBC批处理是基本功,值得了解,如果项目用了MyBatis,数据量适中,用XML里的foreach最快最直接;如果数据量波动大或者很大,用BATCH模式的SqlSession更稳健,如果用JPA,那就好好配置批次大小,利用其自动批处理能力,选择哪种方法,看你项目的具体情况和个人喜好,但无论哪种,都比傻乎乎的一条一条插要高效得多,绝对能帮你省下大把的等待时间。