但凡使用mybatis,同时与spring集成使用时,接下来要说的这个问题是躲不了的。众所周知,mybatis的SqlSessionFactory在获取一个SqlSession时使用默认Executor或必须要指定一个Executor,这样一来,在同一个SqlSession的生命周期中,要想切换Executor是不可能的,比如在一个复杂业务中:

sqlSession.insert("insertMainOrder", mainOrder); // -----(1)
for(OrderInfo childOrder : childOrderList){ // -----循环插入是可以的
sqlSession.insert("insertChildOrder", childOrder);
}

但是使用MyBatisBatchItemWriter是不行的,因为它使用了SqlSessionTemplate的batch属性,官方解释如下:

ItemWriter that uses the batching features from SqlSessionTemplate to execute a batch of statements for all itemsprovided.

以下是xml配置文件实现,也可以代码实现:

<!--结果写入库-->
<bean id="pickUpWriter" class="org.mybatis.spring.batch.MyBatisBatchItemWriter" scope="step">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="statementId" value="com.cwenao.cc.basic.dao.NoticeInfoDao.insertSelective"/>
</bean>

如果sqlSession使用ExecutorType.SIMPLE open出来的话,(2)处如果是用Jdbc batch操作将是不可能的,当然(2)处如果你再新open一个ExecutorType.BATCH的新的SqlSession的话:A、如果整个业务在无事务环境下运行的话,则不会报错,但是底层会使用多个不同的Connection,浪费资源,最重要的是无法保持在同一个事务中。B、如果整个业务在一个事务中运行的话(如propagation=Propagation.REQUIRED),则会在mybatis-spring框架中报错:TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"),也就是标题中的错误,究其原因是因为在mybatis-spring框架中在有事务情况下SqlSession是通过sessionFactory与当前线程绑定的,新open出来的SqlSession会与上一个使用的SqlSession的ExecutorType进行比较,如果ExecutorType改变了,则直接报错。

下面是stackoverflow的解释:

Because it sais it: you can't change the executor type inside the transaction.

it looks like you've tried to batch-write something as the part of more broad transaction that includes other SQL operations, but that transaction was started with SIMPLE (default) or REUSE executor type.

It's obvious, that batch-write requires BATCH executor type, though once the transaction started, it's executor type can not be changed. So, perform your batch operations in separate transaction, or run nested transaction, if your RDBMS allows it.

首先了解下相关知识,mybatis的执行器有三种类型:

  • ExecutorType.SIMPLE

这个类型不做特殊的事情,它只为每个语句创建一个PreparedStatement。

  • ExecutorType.REUSE

这种类型将重复使用PreparedStatements。

  • ExecutorType.BATCH

这个类型批量更新,性能更优,但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id,这在某型情形下是不符合业务要求的,而且假如有一条sql语句报错,则整个事务回滚,虽然这条sql语句不是太重要。注意:在同一事务中batch模式和simple模式之间无法转换。

使用方式:

java代码中,创建模板的时候:

new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);
或者
SqlSession session = getSqlSessionFactory().openSession(ExecutorType.BATCH);

xml文件配置:

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
<constructor-arg index="1" value="BATCH"/>
</bean>

下面是具体执行方法:

1,ExecutorType.SIMPLE:可以返回自增键,自增键会在事务提交后,自动设置到传入的user对象中,只需要在mapper文件中,增加属性: useGeneratedKeys="true" keyProperty="productId",在外部java代码中添加循环语句,xml中就是单条数据插入:

    <!-- 插入一个user -->
<insert id="insertUser" parameterType="User"
statementType="PREPARED" useGeneratedKeys="true" keyProperty="userId">
INSERT
INTO user (
<include refid="userColumns" />
, create_time,
update_time)
VALUES
(#{email}, #{pwd},#{nickname},
#{phone}, #{sign}, #{age},
#{birthday},
#{createTime},
now())
</insert>

2,ExecutorType.SIMPLE,借助foreach动态sql语句,使用Insert values(...),(...),(...) 的方式,这种方式无法取到自增键,外部java代码中就不需要循环,在xml中使用循环,但是需要注意的是,该SQL语句不能在实现ItemWriter接口的类中调用,不然会报异常:Cannot change the ExecutorType when there is an existing transaction:

    <!-- 批量插入user -->
<insert id="insertUsers" parameterType="map" useGeneratedKeys="true"
keyProperty="userId">
INSERT
INTO user (
<include refid="userColumns" />
, create_time,
update_time)
VALUES
<foreach collection="users" item="userCommand" index="index"
separator=",">
(#{userCommand.email},
#{userCommand.pwd},#{userCommand.nickname},
#{userCommand.phone},
#{userCommand.sign}, #{userCommand.age},
#{userCommand.birthday},
#{userCommand.sex},
#{userCommand.createTime},
now())
</foreach>
</insert>

或者这样写在代码中,不需要xml配置文件:

@Component("ledgerWriter")
public class LedgerWriter implements ItemWriter<Ledger> { @Resource
private NamedParameterJdbcTemplate jdbcTemplate;
private static final String sql = "insert into tableA(a,b) values (:col1,:col2)";
/**
* 写入数据
*
* @param ledgers
*/
public void write(List<? extends Ledger> ledgers) throws Exception {
//将userDtoList转化成BeanPropertySqlParameterSource[]数组
List<BeanPropertySqlParameterSource> userSourceList = new ArrayList<BeanPropertySqlParameterSource>();
for (UserDto userDto : userDtoList) {
  userSourceList.add(new BeanPropertySqlParameterSource(userDto));
}
BeanPropertySqlParameterSource[] beanSources = userSourceList.toArray(new BeanPropertySqlParameterSource[userSourceList.size()]);
jdbcTemplate.batchUpdate(sql, beanSources);
}
}

3,ExecutorType.BATCH,但是SqlSession的执行器类型一旦设置就无法动态修改,因为这个方法仍然需要包在事务中。所以如果在配置文件中设置了执行器为SIMPLE,当要使用BATCH执行器时,需要临时获取,只能在单独的事务中进行:

 SqlSession session = sqlSessionTemplate.getSqlSessionFactory()
.openSession(ExecutorType.BATCH, false);
try {
UserDao batchUserDao = session.getMapper(UserDao.class); for (UserCommand user : users) {
batchUserDao.insertUser(user);
}
session.commit();
// 清理缓存,防止溢出
session.clearCache(); // 添加位置信息
userLbsDao.insertUserLbses(users); } finally {
session.close();
}

4,ExecutorType.BATCH,全部改成batch模式。但是没有办法获取到自增的id,spring事务一起使用,将无法回滚,必须注意,最好单独使用。需要用到一个类:MyBatisBatchItemWriter,它是批量执行更新操作。

MyBatisBatchItemWriter Cannot change the ExecutorType when there is an existing transaction的更多相关文章

  1. Mybatis SqlSessionTemplate 源码解析

    As you may already know, to use MyBatis with Spring you need at least an SqlSessionFactory and at le ...

  2. mybatis--MapperProxy事务

    上篇 详细分析了org.mybatis.spring.mapper.MapperScannerConfigurer 和 org.mybatis.spring.SqlSessionFactoryBean ...

  3. Mybatis-Spring SqlSessionTemplate 源码解析

    在使用Mybatis与Spring集成的时候我们用到了SqlSessionTemplate 这个类. <bean id="sqlSession" class="or ...

  4. Mybatis源码分析-SqlSessionTemplate

    承接Mybatis源码解析-MapperRegistry注册mapper接口,本文将在前文基础上讲解持久层的生成 SqlSessionFactory生成 在spring中,SqlSessionFact ...

  5. spring事务源码分析结合mybatis源码(三)

    下面将结合mybatis源码来分析下,这种持久化框架是如何对connection使用,来达到spring事务的控制. 想要在把mybatis跟spring整合都需要这样一个jar包:mybatis-s ...

  6. spring中的mybatis的sqlSession是如何做到线程隔离的?

    项目中常常使用mybatis配合spring进行数据库操作,但是我们知道,数据的操作是要求做到线程安全的,而且按照原来的jdbc的使用方式,每次操作完成之后都要将连接关闭,但是实际使用中我们并没有这么 ...

  7. mybatis与hibernate常用的持久化类,及sqlsession和sqlsessionTemplate区别

    首先, 通过翻阅源码,我们来整理一下mybatis进行持久化操作时重要的几个类:SqlSessionFactoryBuilder:build方法创建SqlSessionFactory实例.SqlSes ...

  8. Mybatis 源码分析之事物管理

    Mybatis 提供了事物的顶层接口: public interface Transaction { /** * Retrieve inner database connection * @retur ...

  9. Mybatis 源码分析之一二级缓存

    一级缓存 其实关于 Mybatis 的一级缓存是比较抽象的,并没有什么特别的配置,都是在代码中体现出来的. 当调用 Configuration 的 newExecutor 方法来创建 executor ...

随机推荐

  1. POJ 1815 Friendship (Dinic 最小割)

    Friendship Time Limit: 2000MS   Memory Limit: 20000K Total Submissions: 8025   Accepted: 2224 Descri ...

  2. centos6.4安装GitLab

    参考文章: http://www.pickysysadmin.ca/2013/03/25/how-to-install-gitlab-5-0-on-centos-6/ yum安装redis的方法: h ...

  3. 关于ps cs5的一些问题

    一.photoshop cs5 默认在窗口中浮动方法 1.打开“编辑>首选项>界面”在“面板和文档”里把“以选项卡方式打开图像”的勾选去掉 2.点击菜单栏“窗口>排列>使所有内 ...

  4. Spring Boot修改内置Tomcat端口号

    spring Boot 内置Tomcat默认端口号为8080,在开发多个应用调试时很不方便,本文介绍了修改 Spring Boot内置Tomcat端口号的方法. 一.EmbeddedServletCo ...

  5. Java Nashorn--Part 4

    Nashorn 和 javax.script 包 Nashorn 并不是第一个在 Java 平台上运行的脚本语言.在Java 6 就提供了 javax.script java 包,它为脚本语言引擎提供 ...

  6. Codeforces Round #207 (Div. 1) B. Xenia and Hamming(gcd的运用)

    题目链接: B. Xenia and Hamming 题意: 要求找到复制后的两个字符串中不同样的字符 思路: 子问题: 在两串长度是最大公倍数的情况下, 求出一个串在还有一个串中反复字符的个数 CO ...

  7. cygwin完全安装步骤方法(过程图解)

    cygwin完全安装步骤方法(过程图解) 我们可以到Cygwin的官方网站下载Cygwin的安装程序,地址是: http://www.cygwin.com/ 或者直接使用下载连接来下载安装程序,下载连 ...

  8. nexus7 1代 刷4.2.2+root[转]

    下面和大家分享一下刷机方法.(该刷机方法根据论坛有小改动)刷机前记得备份...刷机前准备:一.准备工具1.N7电脑驱动(usb_driver_r06_windows.zip)2.刷机工具(N7 fas ...

  9. vs 2017打包安装包(印象深刻)

    Visual Studio Install 打包安装项目2017 以下是具体步骤 一.安装环境 1.下载:链接地址 2.安装vs2017的时候需要安装依赖 .NET framework 4.6 .下载 ...

  10. Windows系统创建符号链接文件

    源文件夹:E:\深海 创建新硬链接文件夹:D:\微云同步盘\719179409\4-工作资料\深海   使用快捷键Win + X 打开以下菜单,选择命令提示符(管理员) 敲入以下命令:   创建成功后 ...