最近在项目中遇到了spring事务的注解及相关知识,突然间感觉自己对于这部分知识只停留在表面的理解层次上,于是乎花些时间上网搜索了一些文章,以及对于源码的解读,整理如下:
一.既然谈到事务,那就先搞清到底什么是事务,或者说,Spring事务管理中的事务到底是指什么?
1.事务(Transaction),通常是指数据库的事务,在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit),例如insert 、update、delete等,事务是恢复和并发控制的基本单位。
2.事务具有四种属性(ACID特性):
原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。
一致性(consistency)。是指当事务完成时,必须使所有数据都具有一致的状态,事务必须是使数据库从一个一致性状态变到另一个一致性状态,一致性与原子性是密切相关的。
隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
二.事务的管理
1.spring 如何来管理事务呢,查看源码发现,一般我们在配置文件中声明的如<bean name="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">或者是其他的Manager,均继承自
org.springframework.transaction.PlatformTransactionManager,而PlatformTransactionManager.java中定义了以下几种方法,源码如下:
package org.springframework.transaction;
public abstract interface PlatformTransactionManager {
public abstract TransactionStatus getTransaction(TransactionDefinition paramTransactionDefinition) throws TransactionException;
public abstract void commit(TransactionStatus paramTransactionStatus) throws TransactionException;
public abstract void rollback(TransactionStatus paramTransactionStatus) throws TransactionException;
}
下面来具体分析一下:
1.1.TransactionDefinition.java,这个类(确切的说是接口)是事务的定义类,此接口指定了事务隔离级别、事务传播属性、事务超时、只读状态。
1.2.TransactionStatus接口。这个接口为处理事务提供简单的控制事务执行和查询事务状态的方法。
1.3.事务的传播属性:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。拥有多个可以回滚的保存点,内部回滚不会对外部事务产生影响。只对DataSourceTransactionManager有效
1.4.事务的隔离级别:
ISOLATION_DEFAULT: 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED: 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。
ISOLATION_READ_COMMITTED: 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ: 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻像读。
1.5.名词解释:什么是脏数据,脏读,不可重复读,幻觉读,事务的挂起?
脏读: 指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
不可重复读: 指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。
幻觉读: 指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还存在没有修改的数据行,就好象发生了幻觉一样。
挂起:例如,方法A支持事务,方法B不支持事务,方法A调用方法B。 在方法A开始运行时,系统为它建立Transaction,方法A中对于数据库的处理操作,会在该Transaction的控制之下。 这时,方法A调用方法B,方法A打开的 Transaction将挂起,方法B中任何数据库操作,都不在该Transaction的管理之下。 当方法B返回,方法A继续运行,之前的Transaction回复,后面的数据库操作继续在该Transaction的控制之下 提交或回滚。
2.spring事务管理的两种方式:
2.1.声明式事务管理 (分为两种,基于XML配置方式和基于注解配置方式)
如果并不需要细粒度的事务控制,可以使用声明式事务,在Spring中,只需要在Spring配置文件中做一些配置,即可将操作纳入到事务管理中,解除了和代码的耦合,这是对应用代码影响最小的选择,从这一点再次验证了Spring关于AOP的概念。当不需要事务管理的时候,可以直接从Spring配置文件中移除该设置.需要引入用于事务管理的命名空间(tx).
Spring并没有直接管理事务,而是将事务的管理委托给其他的事务管理器实现.
Spring支持的事务管理器:
- org.springframework.jdbc.datasource.DataSourceTransactionManager (在单一的JDBC Datasource中的管理事务)
- org.springframework.orm.hibernate3.HibernateTransactionManager (当持久化机制是hibernate时,用它来管理事务)
- org.springframework.jdo.JdoTransactionManager (当持久化机制是Jdo时,用它来管理事务)
- org.springframework.transaction.jta.JtaTransactionManager (使用一个JTA实现来管理事务。在一个事务跨越多个资源时必须使用)
- org.springframework.orm.ojb.PersistenceBrokerTransactionManager (当apache的ojb用作持久化机制时,用它来管理事务)
第一种.基于XML配置方式
<!--1 配置数据源-->
<bean id="dateSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="maxActive" value="100"/>
<!-- 初始化连接数 -->
<property name="initialSize" value="5"/>
<!--最大空闲数,当洪峰退去时, 连接池所放的最少连接数-->
<property name="maxIdle" value="8"/>
<!--最小空闲数,当洪峰到来时,引起的性能开销 -->
<property name="minIdle" value="5"/>
</bean>
<!--2 配置JdbcTemplate模板,类似于dbutils,可数据访问操作-->
<bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 给JdbcTemplate注入数据源,调用JdbcAccessor中的setDataSource(DataSource dataSource)注入数据源-->
<property name="dataSource" ref="dateSource"/>
</bean>
<!-- 3 声明事务管理器(实际上,事务管理器就是一个切面),事务管理器将在获取连接时,返回一个打开事务的连接 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源,spring的jdbc事务管理器在管理事务时,依赖于JDBC的事务管理机制 -->
<property name="dataSource" ref="dateSource"/>
</bean>
<!-- 4 配置通知
id="advice":该属性的值就是通知的唯一标识
transaction-manager:表示通知织入哪个切面
-->
<tx:advice id="advice" transaction-manager="txManager">
<tx:attributes>
<!-- tx:method的属性:
* name 是必须的,表示与事务属性关联的方法名(业务方法名),对切入点进行细化。通配符(*)可以用来指定一批关联到相同的事务属性的方法。
如:‘get*‘、‘handle*‘、‘on*Event‘等等.
* propagation 不是必须的 ,默认值是REQUIRED
表示事务传播行为, 包括REQUIRED,SUPPORTS,MANDATORY,REQUIRES_NEW,NOT_SUPPORTED,NEVER,NESTED
* isolation 不是必须的 默认值DEFAULT
表示事务隔离级别(数据库的隔离级别)
* timeout 不是必须的 默认值-1(永不超时)
表示事务超时的时间(以秒为单位)
* read-only 不是必须的 默认值false不是只读的
表示事务是否只读
* rollback-for 不是必须的
表示将被触发进行回滚的 Exception(s);以逗号分开。
如:‘com.foo.MyBusinessException,ServletException‘
* no-rollback-for 不是必须的
表示不被触发进行回滚的 Exception(s);以逗号分开。
如:‘com.foo.MyBusinessException,ServletException‘
任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚 -->
<tx:method name="save*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<tx:method name="update*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<tx:method name="delete*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<!-- 其他的方法只读的 -->
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 5 配置切入点 -->
<aop:config>
<!-- 定义切入点,可以定义到类中所有的方法,之后在事务中在对方法进行细化 -->
<aop:pointcut id="perform" expression="execution(* *.*(..))"/>
<!-- 将通知和切入点关联起来-->
<aop:advisor advice-ref="advice" pointcut-ref="perform"/>
</aop:config>
配置完事务管理器后,再常规的配置Bean注入对象.默认只有运行时异常才将导致事务回滚.
第二种.基于注解配置方式
XML配置文件中只需要声明事务管理器,而不需要给它"灵魂",因为"灵魂"是由注解注入,所以需要注解解析器的支持:
<tx:annotation-driven transaction-manager="txManager"/>
注册对事务注解进行解析的处理器,将注解与事务管理器关联起来即可.
/**
* 方法的事务设置将被优先执行。
* 例如: BusinessService类在类的级别上被注解为只读事务,
* 但是,这个类中的 save 方法的@Transactional 注解的事
* 务设置将优先于类级别注解的事务设置。
* 默认的 @Transactional 设置如下:
* 事务传播设置是 PROPAGATION_REQUIRED
* 事务隔离级别是 ISOLATION_DEFAULT
* 事务是 读/写
* 事务超时默认是依赖于事务系统的,或者事务超时没有被支持
* 任何 RuntimeException 将触发事务回滚,但是任何 checked Exception 将不触发事务回滚
*/
@Transactional(readOnly=true)
public class BusinessService {
@Resource(name="personDao")
private PersonDao personDao;
@Transactional(readOnly=false,isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public void save() throws Exception{
personDao.save();
this.update();
}
public void update() throws Exception{
personDao.save();
personDao.update();
}
}
2.2.编程式事务管理(也包含两种方式)
Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager。
第一种方式:TransactionTempale采用和其他Spring模板,如JdbcTempalte和HibernateTemplate一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:
Object result = tt.execute(new TransactionCallback()...{
public Object doTransaction(TransactionStatus status)...{
updateOperation();
return resultOfUpdateOperation();
}
});
使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。
第二种方式:使用PlatformTransactionManager直接管理事务。简单地通过一个bean引用给你的bean传递一个你使用的 PlatformTransaction对象。然后,使用TransactionDefinition和TransactionStatus对象就可以发起、回滚、提交事务。如下片段:
DefaultTransactionDefinition def= new DefaultTransactionDefinition(); //new 一 个事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //初始化事务,参数定义事务的传播类型;
TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
try...{ ……………..
transactionManager.commit(status); //提交事务;
}catch(…..)... {
transactionManager.rollback(status); //回滚事务;
}
三.到底把事务控制在哪一层,是dao?还是service层最好?
其实,对于数据库的操作,当然是dao层,但是,一个service中可以包含很多种dao操作,而出于对事务的特性的考虑,一般还是应该把事务控制在service层,下面是我目前项目中的配置小解:
<!--声明事务管理器-->
<bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="cc_ds"></property> <!--注入数据源-->
</bean>
<!--配置通知-->
<tx:advice id="userTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" read-only="true"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="*" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!--配置切入点-->
<aop:config>
<aop:pointcut id="pc" expression="execution(public * com.speed.*.service.impl.*.*(..))" /> <!--把事务控制在Service层 -->
<aop:advisor pointcut-ref="pc" advice-ref="userTxAdvice" />
</aop:config>
其中public * com.speed.*.service.impl.*.*(..))" 中,public是指该事务指定公共的方法,*相当于通配符,这里第一个*是指返回值的类型,第二个包路径的一部分,也就是speed目录下的所有子模块,第三个*指定各个serviceImpl.类,第四个*是指该类下的所有方法,(..)指参列表任意,整句话就是把事务控制在service层