如果一些老旧的系统需要兼容hibernate/mybatis两套orm数据库工具,则事务管理器就不能用Hibernate的事务管理器,而要用spring自带的事务管理器,具体配置如下:
首先applicationContext.xml应该相当于一个手脚架, 将引用各种职能的xml配置,如下图, 导入了三个数据库相关的xml引用:
...
spring-db.xml如下 , 配置了事务管理和jdbctemplate等工具,注意,这里用org.springframework.jdbc.datasource.DataSourceTransactionManager来处理数据库事务而不是org.springframework.orm.hibernate4.HibernateTransactionManager.
true
spring-hibernate.xml的配置:
com.freestyle.common.spring.entities
com.freestyle.test.hibernate.entities
com.freestyle.app.entities
com.freestyle.test.beans
org.hibernate.dialect.PostgreSQLDialect
update
true
true
false
false
100
50
false
auto
false
TRACE
spring-mybatis.xml:
classpath*:com/freestyle/common/db/mybatis/CommonMapper.xml
classpath*:com/freestyle/app/mybatis/dao/*Mapper.xml
POM的引用:
com.alibaba
druid
1.1.16
org.mybatis
mybatis
3.4.6
org.mybatis
mybatis-spring
1.3.2
org.hibernate
hibernate-core
${spring-hibernate.version}
org.javassist
javassist
org.hibernate
hibernate-entitymanager
4.3.11.Final
=========================================
下面是事务测试:
1/测试事务回滚, 下面test3代码里面用hibernate和mybatis两种截然不同的框架来进行数据库的更改最后触发异常,并两者都能成功进行事务回滚.
mvDao为Hinbernate访问对象, 执行了save操作,其实并未写入数据库, 所以在其后加入一个createSQLQuery来迫使hibernate先执行update运作.
@Repository("transactionTestModule")
public class TransactionTestModule {
@Resource(name = "hibernateUserDao")
protected IHibernateEntityDao mvDao;
@Resource(name = "hUserBaseDao")
protected IHibernateEntityDao mvBaseDao;
@Resource
protected TgSeriesMapper mTgDao;
@Resource
protected TaUserMapper mTaDao;
private Log log=LogFactory.getLog(TransactionTestModule.class);
/***
* 手动通过throw exception 回滚
*/
@Transactional
public void test3(){
log.info("数据操作后业务检查失败,全部回滚");
TaUser lvUser1=mvDao.get("admin"); //mvDao为hibernate访问对象
lvUser1.setFaName("user1");
mvDao.save(lvUser1,false);
mvDao.createSQLQuery("select * from ta_user").list();
com.freestyle.app.mybatis.entities.TaUser lvUser2=mTaDao.selectByPrimaryKey("test1");
lvUser2.setFaName("user2");
mTaDao.updateByPrimaryKey(lvUser2);
//testMybatis2();
if (mvDao!=null){ //演示业务检查失败,用抛出exception的方式回滚
throw new RuntimeException("因为长得太帅,这次不改名了");
}
}
但是他们处理同一个事务吗? 并不是, mvDao和mTaDao均处于各自的事务当中, 两者是隔离的,也就是说mvDao作的数据更改在mTaDao来说是看不见的. 有后台数据连接为证,下图为当断点打在"if (mvDao!=null){ //演示业务检查失败,用抛出exception的方式回滚"时后台数据库连接的截图, 9928这个PID是mvDao(hibernate)申请的连接, 而9716是mybatis申请的连接:
通过测试得知, 即使用@Transactional来指明这个函数是处于事务中, 但里面的hibermate和mybatis两种数据库访问对象尽管是处理事务管理当中, 但它们的事务是独立的, 不能互访的.
2/再测试jdbctemplate与hibernate是否处理同一事务
如下代码, 因为同是jdbctemplate, 所以在事务结束前tmp是有效的, 但mvDao则因为处理属于它自已的事务里面,所以第三句会出错"ERROR SqlExceptionHelper:146 - 错误: 关系 "tmp" 不存在"
@Transactional
public void testJdbcTemplateNHibernate() {
ContextHolder.getJdbcTemplate().execute("create temp table tmp on commit drop as select * from ta_user where fa_login='admin'");
ContextHolder.getJdbcTemplate().queryForList("select * from tmp");
List lvList= mvDao.createSQLQuery("select * from tmp").list();
log.info(lvList);
}
通过测试得知, jdbctemplate同样是与hibernate的事务是独立的, 因为开两个不同的数据库连接, 所以不能互访.
3/测试jdbctemplate与mybatis事务
修改TaUserMapper.xml,在末尾加上:
create temp table ${pvsTb} as select * from ta_user ;
修改TaUserMapper.java,在末尾加上:
void createTemp(@Param("pvsTb") String pvsTb);
List selectFromTemp(@Param("pvsTb") String pvsTb);
执行jdbctemplate与mybatis分别建立tmp1和tmp2,并交叉访问:
@Transactional
public void testJdbcTemplateNMybatis() {
//jdbctemplate 建立tmp1, mybatis建立tmp2
ContextHolder.getJdbcTemplate().execute("create temp table tmp1 on commit drop as select * from ta_user where fa_login='admin'");
mTaDao.createTemp("tmp2");
//交叉访问, jdbctemplate访问tmp2, mybatis访问tmp1
ContextHolder.getJdbcTemplate().queryForList("select * from tmp2");
mTaDao.selectFromTemp("tmp1");
}
很神奇地, 居然可以互访, 也就是说jdbctemplate与mybatis的事务用的是同一个, 查后台数据库连接也是共用一个连接的 .
4/测试hibernate两个sqlquery之间是否事务能互访 , 如下代码, 是可以互访的, 但必须注意, sqlquery每次执行都会开启自已独立的事务(在这里是事务嵌套了), 所以如果建立临时表时用on commit drop, 当sqlquery执行完就会commit, 临时表就会被回收, 那么第2个sqlquery就会出错.
mvDao.createSQLQuery("create temp table tmp /*on commit drop*/ as select * from ta_user where fa_login='admin'").executeUpdate();
mvDao.createSQLQuery("select * from tmp").list();
由于sqlquery会开启自已的事务的特性, 那如果想用runtimeexception来让方法层面的事务回滚那是不可能的, 在sqlquery执行中产生的数据变动不能回滚, 因为它执行完就commit了. 如下代码 , fa_name最后还是被改为abc.
@Transactional
public void testSQLQuery() {
mvDao.createSQLQuery("update ta_user set fa_name='abc' where fa_login='admin'").executeUpdate();
if (1+1==2)throw new RuntimeException("rollback");
}
而这时候, 若想要将sqlquery的事务要与hibernate的传统事务绑定在一起, 那么可以自已显式的用一个transaction将它们绑在一起:
Session lvSess=mvDao.getSession();
Transaction lvTrans=lvSess.beginTransaction();
try {
TaUser lvUser=mvDao.get("admin");
log.info(lvUser.getFaName());//fa name=admin
mvDao.evit(lvUser);
mvDao.createSQLQuery(lvSess,"update ta_user set fa_name='abc' where fa_login='admin'").executeUpdate();
lvUser=mvDao.get("admin");
log.info(lvUser.getFaName());//fa name=abc
mvDao.evit(lvUser);
com.freestyle.app.mybatis.entities.TaUser lvUser1=mTaDao.selectByPrimaryKey("admin"); //这是另一条transaction
log.info(lvUser1.getFaName()); //fa name=admin
....
}
catch (RuntimeException e) {
lvTrans.rollback();
throw e;
}
=====================
总结:
1/@Transactional注解的方法里面, mybatis与jdbctemplate共用同一事务共用同一个数据库连接
2/@Transactional注解的方法里面, mybatis与hibernate有各自的事务管理, 但它们的事务是互相独立的, 也用不同的数据库连接, 不能互访
3/@Transactional注解的方法里面, jdbctemplate与hibernate有各自的事务管理, 但它们的事务是互相独立的, 也用不同的数据库连接, 不能互访
4/即使在@Transactional注解的方法里面,hibernate的sqlquery所作出的数据变动是不能通过runtimeexception来回滚的,原因就是它会有自已的事务, 执行完就立即commit了. 最好的方法就是自已显式写代码将sqlquery和传统的hibernate的dao绑在同一个transaction里面