(一)事务的意义
事务是用来解决并发事务同时操作同一资源所产生的问题,以确保数据的完整性和一致性。
(二)事务的特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
(三)事务管理器
spring并不直接管理事务,而是定义了一个标准事务管理接口,即事务管理器:org.springframework.transaction.PlatformTransactionManager,接口中具体事务方法的具体实现由各个平台自己实现(JDBC,Hibernate,JPA等)。
1、PlatformTransactionManager接口中的方法:
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
2、各个平台的具体事务管理器实现类:
(四)事务定义接口:描述了事务的基本属性(传播,隔离等级,超时,只读,回滚)
publicinterface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
// 返回事务必须在多少秒内完成
int getTimeout();
// 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
boolean isReadOnly();
}
1、事务传播行为(propagation):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
事务传播行为:(七种)
- REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。(常用)
- SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
- MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
- REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
- NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。拥有多个可以回滚的保存点,内部回滚不会对外部事务产生影响。只对DataSourceTransactionManager有效
脏读:一个事务读取了另一个事务改写但还未提交的数据,如果这些数据被回滚,则读到的数据是无效的。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,幻读就发生了。再后来的查询中,第一个事务就会发现有些原来没有的记录。
注意:不可重复读:侧重的是修改,幻读侧重的是新增和删除
对于不可重复读的控制:只需要锁住满足条件的记录即可
对于幻读:要锁住满足条件和相关记录才可以。
事务隔离级别:(五种)
- DEFAULT--使用后端数据库默认的隔离级别(Spring中的选择项)
- READ_UNCOMMITED--允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
- READ_COMMITTED--允许在并发事务已经提交后读取。可防止脏读,但幻读和不可重复读仍可发生
- REPEATABLE_READ--对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生
- SERIALIZABLE--完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的
3、事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
4、事务只读设置事务只读是一种优化设置,只是推荐,只读可以并发的进行,不涉及操作数据,可以提高程序的运行效率。
5、事务回滚
可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。
(五)事务状态接口(TransactionStatus ):描述事务的一些状态
publicinterfaceTransactionStatus{
// 是否是新的事物
boolean isNewTransaction();
// 是否有恢复点
boolean hasSavepoint();
// 设置为只回滚
void setRollbackOnly();
// 是否为只回滚
boolean isRollbackOnly();
// 是否已完成
boolean isCompleted;
}
描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
(六)Spring支持编程式事务管理和声明式事务管理两种方式
Spring配置文件中关于事务配置总是由三个组成部分,分别是DataSource、TransactionManager和代理机制这三部分,无论哪种配置方式,一般变化的只是代理机制这部分。
DataSource、TransactionManager这两部分只是会根据数据访问方式有所变化,比如使用Hibernate进行数据访问时,DataSource实际为SessionFactory,TransactionManager的实现为HibernateTransactionManager。
编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
结论:使用声明式事务管理,简洁方便。
(七)声明式事务管理
第一种方法:基于TransactionProxyFactoryBean的方式.(很少使用)
需要为每个进行事务管理的类,配置一个TransactionProxyFactoryBean进行增强.
第二种方法:基于XML配置(经常使用)
一旦配置好之后,类上不需要添加任何东西。
如果Action作为目标对象切入事务,需要在<aop:config>元素里添加proxy-target-class="true"属性。原因是通知Spring框架采用CGLIB技术生成具有事务管理功能的Action类。
第三种方法:基于注解(配置简单,经常使用)
在applicationContext.xml中开启事务注解配置。(applicationContext.xml中只需定义Bean并追加以下元素)
<bean id="txManager" class="...">
<property name="sessionFactory">
</property>
<tx:annotation-driven transaction-manager="txManager"/>
在目标组件类中使用@Transactional,该标记可定义在类前或方法前。
结论:一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。
(八)基于tx名字空间的xml配置
- <!-- 引入外部的属性文件 -->
- <context:property-placeholder location="classpath:jdbc.properties"/>
- <!-- 配置c3p0连接池 -->
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
- <property name="driverClass" value="${jdbc.driverClass}" />
- <property name="jdbcUrl" value="${jdbc.url}" />
- <property name="user" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
- <!-- 配置业务层类 -->
- <bean id="accountService" class="com.zs.spring.demo3.AccountServiceImpl">
- <property name="accountDao" ref="accountDao" />
- </bean>
- <!-- 配置DAO类(简化,会自动配置JdbcTemplate) -->
- <bean id="accountDao" class="com.zs.spring.demo3.AccountDaoImpl">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- ==================================3.使用XML配置声明式的事务管理,基于tx/aop=============================================== -->
- <!-- 配置事务管理器 -->
- <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 配置事务的通知 -->
- <tx:advice id="txAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <!--
- propagation :事务传播行为
- isolation :事务的隔离级别
- read-only :只读
- rollback-for:发生哪些异常回滚
- no-rollback-for :发生哪些异常不回滚
- timeout :过期信息
- -->
- <tx:method name="transfer" propagation="REQUIRED"/>
- </tx:attributes>
- </tx:advice>
- <!-- 配置切面 -->
- <aop:config>
- <!-- 配置切入点 -->
- <aop:pointcut expression="execution(* com.zs.spring.demo3.AccountService+.*(..))" id="pointcut1"/>
- <!-- 配置切面 -->
- <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/>
- </aop:config>
混合事务配置实例:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns:aop="http://www.springframework.org/schema/aop"
- xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
- http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
- http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
- http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">
- <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close" >
- <property name="driverClass" value="${db.driver}"></property>
- <property name="jdbcUrl" value="${db.url}"></property>
- <property name="user" value="${db.username}"></property>
- <property name="password" value="${db.password}"></property>
- <property name="acquireIncrement" value="2"></property>
- <property name="preferredTestQuery" value="select 1 from dual"></property>
- <!-- <property name="idleConnectionTestPeriod" value="60"></property> -->
- <property name="initialPoolSize" value="${db.minPoolSize}"></property>
- <property name="minPoolSize" value="${db.minPoolSize}"></property>
- <property name="maxPoolSize" value="${db.maxPoolSize}"></property>
- <property name="maxIdleTime" value="${db.maxIdleTime}"></property>
- </bean>
- <!-- Transaction manager :
- 非JTA事务(即非分布式事务), 事务配置的时候 ,事务管理器(transactionManager)需要指定dataSource属性(非分布式事务,事务是在数据库创建的链接上开启。)
- JTA事务(分布式事务), 事务配置的时候 ,事务管理器(transactionManager)不能指定dataSource属性(分布式事务,是有全局事务来管理数据库链接的)
- -->
- <bean id="transactionManager"
- class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
- <property name="dataSource" ref="dataSource" />
- </bean>
- <!-- 使用注解annotation定义事务:
- 如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。
- 如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。
- -->
- <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
- <!-- 使用AOP transaction定义事务 -->
- <tx:advice id="defaultTxAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="get*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="query*" read-only="true" />
- <tx:method name="search*" read-only="true" />
- <tx:method name="select*" read-only="true" />
- <tx:method name="find*" read-only="true" />
- <tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="create*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="do*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="execute*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="*" read-only="true" propagation="REQUIRED" isolation="DEFAULT" />
- </tx:attributes>
- </tx:advice>
- <tx:advice id="logTxAdvice" transaction-manager="transactionManager">
- <tx:attributes>
- <tx:method name="get*" propagation="REQUIRED" rollback-for="java.lang.Exception" isolation="DEFAULT" />
- <tx:method name="query*" read-only="true" />
- <tx:method name="search*" read-only="true" />
- <tx:method name="select*" read-only="true" />
- <tx:method name="find*" read-only="true" />
- <tx:method name="*" propagation="REQUIRES_NEW"
- rollback-for="java.lang.Exception" />
- </tx:attributes>
- </tx:advice>
- <!-- "order"参数,这个参数是用来控制aop通知的优先级,值越小,优先级越高 ,混合事务配置时要制定该参数。
- execution:使用“execution(方法表达式)”匹配方法执行
- within:使用“within(类型表达式)”匹配指定类型内的方法执行
- -->
- <aop:config>
- <aop:pointcut id="defaultOperation"
- expression="execution(* *..*ServiceImpl.*(..))" />
- <aop:pointcut id="logServiceOperation"
- expression="@within(com.homent.service.LogService+" />
- <aop:advisor advice-ref="defaultTxAdvice" order="3" pointcut-ref="defaultOperation" />
- <aop:advisor advice-ref="logTxAdvice" order="2" pointcut-ref="logServiceOperation" />
- </aop:config>
- </beans>
(九)采用基于@Transactional注解的方式
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
注意: Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
- /**
- * @Transactional中的的属性 propagation :事务的传播行为 isolation :事务的隔离级别 readOnly :只读
- * rollbackFor :发生哪些异常回滚 noRollbackFor :发生哪些异常不回滚
- * rollbackForClassName 根据异常类名回滚
- */
- @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false)
- public class AccountServiceImpl implements AccountService {
- // 注入转账的DAO
- private AccountDao accountDao;
- /**
- * @param out
- * :转出账号
- * @param in
- * :转入账号
- * @param money
- * :转账金额
- */
- @Override
- public void transfer(String out, String in, Double money) {
- accountDao.outMoney(out, money);
- // int i = 1/0;
- accountDao.inMoney(in, money);
- }
- public void setAccountDao(AccountDao accountDao) {
- this.accountDao = accountDao;
- }
- }
<?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.bluesky" />
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
</bean>
<!-- 定义事务管理器(声明式的事务) -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
</beans>
@Transactional注解中常用参数说明
参 数 名 称 |
功 能 描 述 |
readOnly |
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor |
该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class,Exception.class}) |
参 数 名 称 |
功 能 描 述 |
rollbackForClassName |
该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如: 指定单一异常类名称:@Transactional(rollbackForClassName="RuntimeException") 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"}) |
noRollbackFor |
该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如: 指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class,Exception.class}) |
noRollbackForClassName |
该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如: 指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException") 指定多个异常类名称: @Transactional(noRollbackForClassName={"RuntimeException","Exception"}) |
propagation |
该属性用于设置事务的传播行为,具体取值可参考表6-7。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation |
该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout |
该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
参考文档:http://www.blogjava.net/robbie/archive/2009/04/05/264003.html#post
http://blog.csdn.net/yuanlaishini2010/article/details/45799309
http://blog.csdn.net/daijin888888/article/details/51822257