Mybatis批量操作

在项目开发的过程中,一般的操作都是单条数据操作,插入一次提交一次事务;但是往往会有大批量类似的数据需要插入,此时用循环单条插入的方式去操作也可以,但是频繁的请求数据库资源很耗费系统资源,有没有更好的方式呢?

发现问题后就要尝试查找资料去解决问题,去看 MyBatis 的源码?一大堆不知道从那看起?那就去看Github。众多周知, Mybatis 源码等相关信息是托管在 Github 上的,可以看看他的 wiki 里有没有相应的方案,在它的 FAQ(Frequently Asked Questions)的中有关于批量插入的示例:
首先,创建一个简单的插入语句:

<insert id="insertName">
  insert into names (name) values (#{value})
</insert>

然后在 Java 代码中执行批量操作:

List<String> names = new ArrayList<String>();
names.add("Fred");
names.add("Barney");
names.add("Betty");
names.add("Wilma");

SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
  NameMapper mapper = sqlSession.getMapper(NameMapper.class);
  for (String name : names) {
    mapper.insertName(name);
  }
  sqlSession.commit();
} finally {
  sqlSession.close();
}

示例中的操作是 insert,同理 update、delete 等的操作也一样,要点是事务提交一次。

目前的数据库有多种,各个数据库批量插入的方式不一样,但是对于单条插入然后去循环的方式是都支持的,所以 MyBatis 做了一个共通的实例,将执行类型设置ExecutorType.BATCH,批量去提交事务。

示例中的核心在于创建一个操作批量事物的SqlSession,在纯 MyBatis 中 SqlSession 的获取方式为:

InputStream in=Resources.getResourceAsStream("applicationContext.xml");  
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(in);   
SqlSession sqlsession=factory.openSession();  

查看 Mybatis 中可看到默创建的事务为简单事务: ExecutorType.SIMPLE,批量插入要修改事务类型。

Java 程序处理

在后续的开发中,MyBatis 常与其他框架一起使用,最常见的是 SSM(Spring+SpringMVC+Mybatis) 的整合,通常对数据库事务的操作就交给Spring 去处理了。此时如果有大量数据需要插入,利用循环去 mapper.inset(entity) 也可以解决问题,但是每执行一次都需要开启一个新的事务,对资源的消耗比较大。所以,要采用其他的更优化的方案。

在 Spring 中不用关心 SqlSession 是如何创建销毁的,在applicationContext.xml中配置响应的 SqlsessionFactory 即可。

<!-- 配置SqlSessionFactoryBean -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
  <property name="configLocation" value="classpath:mybatis.xml"/>
</bean>

如果要在 Java 中去控制事务类型,可通过注入 SqlSeesionFactory 的方式显示的获取 SqlSession,

@Autowired
private SqlSessionFactoryBean sqlSessionFactory;
……
SqlSession sqlSession=sqlSessionFactory.getObject().openSession(ExecutorType.BATCH);

获取到 SqlSession 之后在仿照示例中的代码写就可以了。

SQL 拼接处理

另一种方式是通过动态拼接 SQL 的形式去批量插入,这是的主要工作是在数据库那一端进行的。各个数据库批量插入的方式不一样,以下举最常见的两个数据库 MySQL 与 Oracle,其他的数据库欢迎补充。

Dao 中的代码:

public interface userDao{
  public void insertBatch(@Param("userList") List userList);
}

MySQL

MySQL 支持如下类型的 SQL 语句:

insert into users(username,age) values('zhangsan',18),('liming',20),('fengyu',23);

上述 SQL 是 MySQL 特有的,可在*.xml 文件中去执行拼接,

<insert id="insertBatch" parameterType="java.util.List">  
  insert into users(username,age) values
  <foreach collection="userList" item="item" index="index"  separator=",">  
   (#{item.username},#{item.age})
  </foreach>  
</insert> 

Oracle

Oracle 不支持 MySQL 那种写法,但是他也有自己的其他语句来实现这样的功能:

Oracle 中可以使用 insert all,insert first ,union all 等语句去执行批量的事务操作。

union all:

<insert id="insertBatch" parameterType="java.util.List">  
  insert into users(username,age) 
  <foreach collection="freeNumberList" item="item" index="index"  separator="union all">  
    select #{item.username},#{item.age} from dual
  </foreach>  
</insert> 

insert all:

<insert id="insertBatch" parameterType="java.util.List">  
  insert all 
  <foreach collection="freeNumberList" item="item" index="index"> 
   into users(username,age) values(#{item.username},#{item.age})
  </foreach>  
  select 1 from dual
</insert> 

insert first 与 insert all 区别在于insert all 只要符合条件就都插入,insert first有一个条件判断,可以在 sql 中加上 where 条件。

具体使用是使用 Java 代码的方式,还是使用 SQL 拼接的方式,要根据具体的项目定了。

目前还没有做关于这两种方式的批量插入的效率问题,后续再补。