1 事务简介
-
事务管理是企业级应用程序开发中必不可少的技术, 用来确保数据的完整性和一致性.
-
事务就是一系列的动作, 它们被当做一个单独的工作单元. 这些动作要么全部完成, 要么全部不起作用
-
事务的四个关键属性(ACID)
- 原子性(atomicity): 事务是一个原子操作, 由一系列动作组成. 事务的原子性确保动作要么全部完成要么完全不起作用.
- 一致性(consistency): 一旦所有事务动作完成, 事务就被提交. 数据和资源就处于一种满足业务规则的一致性状态中.
- 隔离性(isolation): 可能有许多事务会同时处理相同的数据, 因此每个事物都应该与其他事务隔离开来, 防止数据损坏.
- 持久性(durability): 一旦事务完成, 无论发生什么系统错误, 它的结果都不应该受到影响. 通常情况下, 事务的结果被写到持久化存储器中.
2 事务管理的问题
- 必须为不同的方法重写类似的样板代码
- 这段代码是特定于 JDBC 的, 一旦选择类其它数据库存取技术, 代码需要作出相应的修改
3 Spring 中的事务管理
-
作为企业级应用程序框架, Spring 在不同的事务管理 API 之上定义了一个抽象层. 而应用程序开发人员不必了解底层的事务管理 API, 就可以使用 Spring 的事务管理机制.
-
Spring 既支持编程式事务管理, 也支持声明式的事务管理.
-
编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚. 在编程式管理事务时, 必须在每个事务操作中包含额外的事务管理代码.
-
声明式事务管理: 大多数情况下比编程式事务管理更好用. 它将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理. 事务管理作为一种横切关注点, 可以通过 AOP 方法模块化. Spring 通过 Spring AOP 框架支持声明式事务管理.
- Spring声明式事务管理是基于AOP和线程绑定技术实现,利用AOP实现开启、关闭事务并且织入到指定业务中,利用线程绑定实现事务跨越多个方法的传播。
3.1 Spring事务分为物理事务和逻辑事务:
- 物理事务就是底层数据库支持的事务,如JDBC提供事务
- 逻辑事务是Spring管理的事务,逻辑事务提供更丰富的控制,而且如果想得到Spring事务管理的好处,必须使用逻辑事务,因此在Spring中如果没特别强调一般就是逻辑事务Spring声明式事务管理就是基于逻辑事务。
4 Spring 中的事务管理器
4.1 什么是事务管理器
-
Spring 从不同的事务管理 API 中抽象了一整套的事务机制. 开发人员不必了解底层的事务 API, 就可以利用这些事务机制. 有了这些事务机制, 事务管理代码就能独立于特定的事务技术了.
-
Spring 的核心事务管理抽象是 PlatformTransactionManager 接口 它为事务管理封装了一组独立于技术的方法. 无论使用 Spring 的哪种事务管理策略(编程式或声明式), 事务管理器都是必须的.
4.2 常见的事务管理器
- DataSourceTransactionManager :在应用程序中只需要处理一个数据源, 而且通过 JDBC 存取
- HibernateTransactionManager:用 Hibernate 框架存取数据库
- JtaTransactionManager : 在 JavaEE 应用服务器上用 JTA(Java Transaction API) 进行事务管理
- JdoTransactionManager Jdo事务管理器,用于Spring整合jdo时对jdo事务进行管理
- JpaTransactionManager JPA事务管理器,用于Spring整合JPA时对JPA进行数据管理
- ……
PlatformTransactionManager 的方法:
- getTransaction()返回一个已经激活的事务或创建一个新的事务
- commit()提交当前事务
- rollback() 回滚当前事务
事务管理器以普通的 Bean 形式声明在 Spring IOC 容器中
4.3 事务属性
事务隔离级别:用来解决并发事务时出现的问题,Spring事务隔离级别主要有以下几种:
- ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;
- ISOLATION_READ_UNCOMMITTED:读未提交;
- ISOLATION_READ_COMMITTED:提交读,一般情况下我们使用这个;
- ISOLATION_REPEATABLE_READ:可重复读;
- ISOLATION_SERIALIZABLE:序列化。
**事务只读:**将事务标识为只读,只读事务不修改任何数据。
对于JDBC只是简单的将连接设置为只读模式,对于更新将抛出异常;
而对于一些其他ORM框架有一些优化作用,如在Hibernate中,Hibernate会话在只读事务模式下不用尝试检测和同步持久对象的状态的更新,以提高程序效率。
事务传播行为:
Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为****。
事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为。
- Required:必须有逻辑事务,否则新建一个事务,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务。
- RequiresNew:创建新的逻辑事务,表示每次都创建新的逻辑事务(物理事务也是不同的)
- Supports:支持当前事务,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行
- NotSupported:不支持事务,如果当前存在事务则暂停该事务,即以非事务方式执行
- Mandatory:必须有事务,否则抛出异常
- Never:不支持事务,如果当前存在是事务则抛出异常
- Nested:嵌套事务支持,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务
5 事务配置的两种方式
5.1 用事务通知声明式地管理事务
-
事务管理是一种横切关注点
-
为了在 Spring 2.x 中启用声明式事务管理, 可以通过 tx Schema 中定义的
<tx:advice>
元素声明事务通知, 为此必须事先将这个 Schema 定义添加到 根元素中去. -
声明了事务通知后, 就需要将它与切入点关联起来. 由于事务通知是在
<aop:config>
元素外部声明的, 所以它无法直接与切入点产生关联. 所以必须 在<aop:config>
元素中声明一个增强器 通知与切入点关联起来. -
由于 Spring AOP 是基于代理的方法, 所以只能增强公共方法. 因此, 只有公有方法才能通过 Spring AOP 进行事务管理.
代码实例:
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" //name指方法的名字,可以加通配符使用
isolation="DEFAULT"
propagation="REQUIRES_NEW"
timeout="5"
read-only="true"/>
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="*" rollback-for="Exception" />
</tx:attributes>
</tx:advice>
<!-- 配置事务切入点,以及把事务切入点和属性关联起来 -->
<aop:config>
<aop:pointcut expression="execution(* com.xzj.cyg.jdbc.tx.xml.BookShopService.*(..))"
id="txPointCut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
上面我们看到了,简单的配置了事务,其中tx:attributes中设置了事务的传播性,隔离级别以及那种问题能进行回滚超时等这些问题,也就是你自己按照业务需求定制一个事务来满足你的业务需求.
注意: 这里注意一下,在tx:method中配置了rollback_for 中配置的Exception 这个是运行时的异常才会回滚不然其他异常是不会回滚的!
-
advice(建议)的命名:由于每个模块都会有自己的Advice,所以在命名上需要作出规范,初步的构想就是模块名+Advice(只是一种命名规范)。
-
tx:attribute标签所配置的是作为事务的方法的命名类型。
- 如
<tx:method name="save*" propagation="REQUIRED"/>
- 其中*为通配符,即代表以save为开头的所有方法,即表示符合此命名规则的方法作为一个事务。
- propagation="REQUIRED"代表支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- 如
-
aop:pointcut标签配置参与事务的类,由于是在Service中进行数据库业务操作,配的应该是包含那些作为事务的方法的Service类。
- 首先应该特别注意的是id的命名,同样由于每个模块都有自己事务切面,所以我觉得初步的命名规则因为 all+模块名+ServiceMethod。而且每个模块之间不同之处还在于以下一句:
expression="execution(* com.test.testAda.test.model.service.*.*(..))"
- 其中第一个代表返回值,第二代表service下子包,第三个*代表方法名,“(…)”代表方法参数。
-
aop:advisor标签就是把上面我们所配置的事务管理两部分属性整合起来作为整个事务管理。
模板
<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不是只读的
表示事务是否只读? spring会跟我们优化性能
* 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.2 注解式事务
-
除了在带有切入点, 通知和增强器的 Bean 配置文件中声明事务外, Spring 还允许简单地用 @Transactional 注解来标注事务方法.
-
为了将方法定义为支持事务处理的, 可以为方法添加 @Transactional 注解. 根据 Spring AOP 基于代理机制, 只能标注公有方法.
-
在 Bean 配置文件中只需要启用
<tx:annotation-driven>
元素, 并为之指定事务管理器就可以了.
如果事务处理器的名称是 transactionManager, 就可以在tx:annotation-driven 元素中省略 transaction-manager 属性. 这个元素会自动检测该名称的事务处理器.
//添加事务注解
@Transactional(isolation=Isolation.READ_COMMITTED,
propagation=Propagation.REQUIRED,
timeout=5)
public void purchase(String username, String isbn) {...}
@Transactional有如下参数:
- propagation:指定事务传播行为,默认为Required,
- isolation:指定事务隔离级别,默认为“DEFAULT”
- readOnly:指定事务是否只读,默认false表示事务非只读;
- timeout:指定事务超时时间,以秒为单位,默认-1表示事务超时将依赖于底层事务系统;
- rollbackFor:指定一组异常类,遇到该类异常将回滚事务;
- rollbackForClassname:指定一组异常类名字
- noRollbackFor:指定一组异常类,即使遇到该类异常也将提交事务,即不回滚事务;
- noRollbackForClassname:指定一组异常类名字
代码实例:
1. spring+mybatis 事务配置
<!-- 定义事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--使用注释事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
在spring的配置中配置数据源即dataSource、事务管理器,事务管理器使用不同的orm框架事务管理器类就不同,比如这里使用的是mybatis 所以是
org.springframework.jdbc.datasource.DataSourceTransactionManager
如果使用hibernate 则事务管理器类为:
org.springframework.orm.hibernate3.HibernateTransactionManager
这是使用注解方式时要配置的,代码中的具体的注解以及事务的传播性、隔离级别一般在service 层中配置;
2. @Transactional
(1)、这里说明一下,有的把这个注解放在类名称上面了,这样你配置的这个@Transactional 对这个类中的所有public方法都起作用
(2)、@Transactional 方法方法名上,只对这个方法有作用,同样必须是public的方法
(3)、如果类和方法上都指定了@Transactional,则方法上的事务属性被优先使用
@Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class,timeout=1,isolation=Isolation.DEFAULT)
public void saveUser(Map<String, String> map) throws Exception {
System.out.println("方法开始");
for (int i = 0; i < 500000; i++) {
System.out.println("*");
}
System.out.println("进入保存");
userDao.saveUser(map);
System.out.println("退出保存");
}
3. 注意
用 spring 事务管理器,由spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;
而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚;
- 要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .
- 如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)
如下:
@Transactional(rollbackFor=Exception.class) //指定回滚,遇到异常Exception时回滚
public void methodName() {
throw new Exception("注释");
}
@Transactional(noRollbackFor=Exception.class)//指定不回滚,遇到运行期例外(throw new RuntimeException("注释");)会回滚
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException("注释");
}