Hibernate与Spring的事务管理

来源:互联网 发布:协议数据单元包两部分 编辑:程序博客网 时间:2024/05/16 15:49

什么是事务

这个问题比较大,按照我的理解就是,一个事务内的n个操作,要么全部完成,一旦有一个操作有问题,那么所有的操作都全部回滚。

Jdbc的事务

首先,大家已经知道了,事务说白了就是一个词----统一,要么全部OK,要么都不做。
在jdbc中,默认情况下,一个sql就是一个事务,一个事务也仅仅只有一个sql。AutoCommit=true
那么我们正常使用的时候,肯定是想把若干个sql绑到一起,看做一个事务。
那么我们第一步就是先告诉connection,你别一个sql一个sql提交了,整体来。即AutoCommit=false
我们看下面的例子
public int delete(int sID) {  dbc = new DataBaseConnection();  Connection con = dbc.getConnection();  try {   con.setAutoCommit(false);   // 更改JDBC事务的默认提交方式   dbc.executeUpdate("delete from xiao where ID=" + sID);   dbc.executeUpdate("delete from xiao_content where ID=" + sID);   dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);   con.commit();//提交JDBC事务   con.setAutoCommit(true);    // 恢复JDBC事务的默认提交方式   dbc.close();   return 1;  }  catch (Exception exc) {   con.rollBack();//回滚JDBC事务   exc.printStackTrace();   dbc.close();   return -1;  }}

jta事务

不懂,我目前没用到这个东西。


hibernate中的事务

Hibernate 是JDBC 的轻量级封装,本身并不具备事务管理能力。在事务管理层, Hibernate将其委托给底层的JDBC或者JTA,以实现事务管理和调度功能。 
Hibernate的默认事务处理机制基于JDBC Transaction。我们也可以通过配置文件设定采用JTA作为事务管理实现:
</pre><pre name="code" class="html"><hibernate-configuration>  <session-factory>  ……  <property name="hibernate.transaction.factory_class">  net.sf.hibernate.transaction.JTATransactionFactory  <!--net.sf.hibernate.transaction.JDBCTransactionFactory-->  </property>  ……  </session-factory>  </hibernate-configuration>  
单纯的使用hibernate,对于事务的处理是很简单的,例如
Date date = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");Teacher s = new Teacher();s.setName("zhangsan");s.setAge(232);s.setDate(sdf.format(date));SessionFactory sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();Session session = sessionFactory.getCurrentSession();session.beginTransaction();session.save(s);session.getTransaction().commit();session.close();sessionFactory.close();
抽象的来说,
session = sessionFactory.openSession();  Transaction tx = session.beginTransaction();  ……  tx.commit();  
在jdbc层面上就相当于:
Connection dbconn = getConnection();  dbconn.setAutoCommit(false);  ……  dbconn.commit();  
另一方面,在我们获得session的时候,hibernate会初始化数据库连接,把AutoCommit设置为false,后面在beginTransaction会再次检查AutoCommit的值是否未false(防止用户更改)。
所以
session = sessionFactory.openSession();  session.save(user);  session.close();  
上面的代码不会对数据库产生任何影响。你没提交嘛!


使用spring替hibernate管理事务

首先,我们为什么要让spring去替hibernate管理事务?
一 如果单纯的用hibernate,每次对数据库做一次操作,我都得beginTransaction然后getTransaction.commit。你不烦呀。
二 粒度的问题,事务其实在更高的层次上看是一个逻辑概念,它是几个操作的集合。而默认情况下,hibernate只管理对数据库最低层次的操作。(这个,具体的咱们在后面再说)


OK,我们已经知道了用spring管理事务的必要性,再看看用spring管理事务的方式。
有两种。

一种是使用xml,一种是使用Annotation

/////////////////////////////////以下为9月30日的补充内容

其实更精确的说,还有一种------编程式事务管理,只不过相对应后面介绍的声明式与注解式,编程式就显得很low了#

我写个简单的例子:

public class BankServiceImpl implements BankService {private BankDao bankDao;private TransactionDefinition txDefinition;private PlatformTransactionManager txManager;public boolean transfer(Long fromId, Long toId, double amount) {TransactionStatus txStatus = txManager.getTransaction(txDefinition);boolean result = false;try {result = bankDao.transfer(fromId, toId, amount);txManager.commit(txStatus);} catch (Exception e) {result = false;txManager.rollback(txStatus);System.out.println("Transfer Error!");}return result;}}

TransactionDefinition,PlatformTransactionManager都是spring注入的#
上面的bankDao.transfer(fromId, toId, amount)就是把tromid的amount块钱转移到toid上去#

感觉是和jdbc的每什么区别,还都得提交#,我们应该关注于业务本身,对于事务的提交与回滚应该交给系统#

既然说到了jdbc,那么大家就肯定会想到jdbctemplate#那么既然有jdbctebmplage,那为什么就不能有transactionTemplate呢?

我们看下面的例子

public class BankServiceImpl implements BankService {private BankDao bankDao;private TransactionTemplate transactionTemplate;public boolean transfer(final Long fromId, final Long toId, final double amount) {return (Boolean) transactionTemplate.execute(new TransactionCallback(){public Object doInTransaction(TransactionStatus status) {Object result;try {result = bankDao.transfer(fromId, toId, amount);} catch (Exception e) {status.setRollbackOnly();        result = false;       System.out.println("Transfer Error!");}return result;}});}}
这样一来,我们就能关注单纯的业务逻辑了(只不过一旦出错了,我们还得status.setRollbackOnly())

关于硬编码这块,大家参见

全面分析 Spring 的编程式事务管理及声明式事务管理  

只不过,大家请记着,除非我们是接手了一个遗留系统,否则还是不要用编程式事务管理了

当然,虽然不鼓励大家主动去用这个东西,但是我们至少得会用,看到这个这些代码得知道是什么意思,更进一步的,如果我们还能知道里面的实现过程,那对我们的编程技术,或者说架构能力都是有帮助的

总结一下:

基于 TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务管理是 Spring 提供的最原始的方式,通常我们不会这么写,但是了解这种方式对理解 Spring 事务管理的本质有很大作用。

基于 TransactionTemplate 的编程式事务管理是对上一种方式的封装,使得编码更简单、清晰。

/////////////////////////////////以上为9月30日的补充内容

Annotation

我们先说使用Annotation。
第一步

在spring的xml里面加上

一般情况下,JDBC(iBATIS) 使用 DataSourceTransactionManager,hibernate使用HibernateTransactionManager

<bean id="txManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory" /></bean><tx:annotation-driven transaction-manager="txManager"/>
当然,如果xml里面没有tx的命名空间,还得加上
xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/tx            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"
这一步没什么说的,大家记死就OK(我说的记死,不是说让大家记得上面的每一字母;只要知道有这两个配置,需要用的时候,知道去哪找就OK)。
第二步就是加事务了
这一步更简单
@Transaction
哪个方法需要spring的事务管理,就给那个方法加上 @Transaction
我给大家一个实例,向数据库里添加一个user,然后再添加一条日志记录。
说到这,先看代码。
package com.bjsxt.service;import javax.annotation.Resource;import org.springframework.stereotype.Component;import org.springframework.transaction.annotation.Transactional;import com.bjsxt.dao.LogDAO;import com.bjsxt.dao.UserDAO;import com.bjsxt.model.Log;import com.bjsxt.model.User;@Component("userService")public class UserService {        @Resourceprivate UserDAO userDAO;@Resourceprivate LogDAO logDAO;public User getUser(int id) {return null;}public void add(User user) {userDAO.save(user);Log log = new Log();log.setMsg("a user saved!");logDAO.save(log);}        //省略getset}package com.bjsxt.dao.impl;import javax.annotation.Resource;import org.hibernate.Session;import org.hibernate.SessionFactory;import org.springframework.stereotype.Component;import com.bjsxt.dao.UserDAO;import com.bjsxt.model.User;@Component("userDAO") public class UserDAOImpl implements UserDAO {private SessionFactory sessionFactory;public SessionFactory getSessionFactory() {return sessionFactory;}@Resourcepublic void setSessionFactory(SessionFactory sessionFactory) {this.sessionFactory = sessionFactory;}public void save(User user) {Session s = sessionFactory.getCurrentSession();s.save(user);}}
省略LogDAO的接口及实现。
现在的问题是我把 @Transaction加到UserDAOImpl的sava方法上(当然还有LogDAOImpl的save方法)还是加到UserService的add方法上。
我的代码都已经写出来了,大家就是用大腿想,应该也能想出来,加到整体的业务逻辑上。
这个观点提升一下就是,我们应该在service层做事务管理


OK,我们仔细看看这 @Transaction

下图是 @Transaction的doc文档


我们其实只有看两个:
readOnly,我们应该能猜出来,如果某个方法的 @transaction加了readonly,那么方法内部就不能对数据库有增删改的行为。
为什么会有这个属性呢,spring为对readOnly的transaction的方法做优化。
因此,如果你肯定某个方法是不会修改数据库,那就给他加上readOnly=true吧,另一方面,从设计上来讲,readOnly也可以看做一种检查,看某个不应该出现更改数据库的地方出现了更改操作。


第二个属性是propagation,我们能看出他的选值是Propagation,而Propagation是一个枚举类。
Propagation的说明如下:


我们最经常使用的,而且也是spring默认的就是REQUIRED
REQUIRED就是,如果当前方法没有事务,那就新产生一个事务,并且如果此方法有了事务,那么方法内部的方法调用也会使用这个事务。
至于别的几个参数,大家就都忘了吧。


如果把 @Transaction加到某个类上,就等于给这个类的所有方法都加上了 @Transaction
@Transaction标签不可继承。



前面我们已经说了,让spring管理事务有两种方式,第一是annotation,上面我们已经说了,下面我们说说使用xml。

XML(声明式事务管理)

在spring的xml中加入如下内容
<bean id="txManager"class="org.springframework.orm.hibernate3.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory" /></bean><aop:config><aop:pointcut id="bussinessService"expression="execution(public * com.bjsxt.service..*.*(..))" /><aop:advisor pointcut-ref="bussinessService"advice-ref="txAdvice" /></aop:config><tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes><tx:method name="getUser" read-only="true" /><tx:method name="add*" propagation="REQUIRED"/></tx:attributes></tx:advice>
当然,要去掉使用annotation的
<tx:annotation-driven transaction-manager="txManager"/>
我解释一下上面的定义。
在com.bjsxt.service包及其子包下的所有类的所有public的方法都加上事务管理
具体的事务设置是,如果方法名是getUser那么设置read-only为true,如果方法是以add开头的,那么设置propagation为REQUIRED(这个其实不用设,因为是默认的)




现在有个问题,到底是用annotation那,还是xml呢?
回答是看情况。
你觉得哪个方便用哪个。


另外,上面的代码使用的是spring3 hibernate3
如果使用spring4 hibernate4
xml如下:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd           http://www.springframework.org/schema/aop           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd           http://www.springframework.org/schema/tx            http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"><context:annotation-config /><context:component-scan base-package="com.bjsxt" /><beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"><property name="locations"><value>classpath:jdbc.properties</value></property></bean><bean id="dataSource" destroy-method="close"class="org.apache.commons.dbcp2.BasicDataSource"><property name="driverClassName"value="${jdbc.driverClassName}" /><property name="url" value="${jdbc.url}" /><property name="username" value="${jdbc.username}" /><property name="password" value="${jdbc.password}" /></bean><bean id="sessionFactory"          class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /><property name="annotatedClasses"><list><value>com.bjsxt.model.User</value><value>com.bjsxt.model.Log</value></list></property><property name="hibernateProperties"><props><prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop><prop key="hibernate.show_sql">true</prop><prop key="hibernate.hbm2ddl.auto">update</prop>  </props> </property></bean><bean id="txManager"class="org.springframework.orm.hibernate4.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory" /></bean><aop:config><aop:pointcut id="bussinessService"expression="execution(public * com.bjsxt.service..*.*(..))" /><aop:advisor pointcut-ref="bussinessService"advice-ref="txAdvice" /></aop:config><tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes><tx:method name="getUser" read-only="true" /><tx:method name="add*" propagation="REQUIRED"/></tx:attributes></tx:advice></beans>

如果在一个项目里,我既有通过hibernate来访问数据库,也有使用jdbc访问数据库,那么transactionmanager选哪个呢?(如果不清楚transactionmanager是干什么的,请参阅 全面分析 Spring 的编程式事务管理及声明式事务管理  )

到底用哪个呢?

答案是采用HibernateTransactionManager

它既可以管hibernate,也可以管jdbc

参见: spring 同时配置hibernate and jdbc 事务

/////////////////////////////////以下为9月30日的补充内容

其实声明式事务管理,除了上面说的,我们还可以看看历史,我们看看在spring3.0之前,还有什么方式
怎么说呢,虽然之前的事务管理方式都已经很out了,而且我们也没太大的必要对之前的实现方法做多么深的理解,但是不是有那么已经老话嘛:温故而知新,可以为师矣。
我们只有知道了之前是怎么样的,才能真正的体会,为什么事务管理会是今天这个样子。之前是现在的基础

之前的方法有这么几种基于TransactionInterceptor,基于TransactionProxyFactoryBean再之后就是我们上面讲的:基于 <tx> 和 <aop> 命名空间的声明式事务管理,和基于 @Transactional 的方式。

总结一下:

基于 TransactionInterceptor 的声明式事务是 Spring 声明式事务的基础,通常也不建议使用这种方式,但是与前面一样,了解这种方式对理解 Spring 声明式事务有很大作用。
基于 TransactionProxyFactoryBean 的声明式事务是上中方式的改进版本,简化的配置文件的书写,这是 Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。
基于 <tx> 和 <aop> 命名空间的声明式事务管理是目前推荐的方式,其最大特点是与 Spring AOP 结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活。
基于 @Transactional 的方式将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理 Bean 的配置,然后在需要实施事务管理的方法或者类上使用 @Transactional 指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。

关于TransactionInterceptor和TransactionProxyFactoryBean 大家请参考 全面分析 Spring 的编程式事务管理及声明式事务管理  

/////////////////////////////////以上为9月30日的补充内容


参考资料

北京尚学堂 马士兵 spring3讲解
http://www.iteye.com/topic/177988

http://blog.sina.com.cn/s/blog_4b5bc0110100h0wz.html

spring 同时配置hibernate and jdbc 事务

全面分析 Spring 的编程式事务管理及声明式事务管理  




0 0
原创粉丝点击