Spring 声明式事务
Spring 声明式事务
spring中对事务的控制有声明式事务和编程式事务
声明式事务是使用 AOP 进行织入, 而编程式事务是使用代码来实现
关于编程式事务, 这里不进行介绍, 可查看官网:http://mybatis.org/spring/zh/transactions.html#programmatic
我主要说声明式事务.
当然, 如果你是用的springboot项目, 你不需要任何配置, 直接使用注解即可: 基于@Transactional注解的实现
图解:
回顾事务
- 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
- 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。
是否还记得在JDBC中使用事务: 点击跳转到JDBC使用事务
事务四个ACID特性
- Atomicity: 原子性 事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
- Consistency: 一致性 事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账户金额之和在事务前后应该是保持不变的。
- Isolation: 隔离性,隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事中务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
- Durability: 持久性,一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
下面我们来正式开始使用事务
基于XML配置文件实现
1. 前提准备
spring已经整合了mybatis, 并导入了向关依赖, 如jdbc, mybatis, juntl, spring等
2. service示例(接口略)
我们假设第一条对数据库的插入操作成功, 第二条对数据库的更改失败
@Service
public class UserServiceImpl implements UserService {
// 注入dao层
@Resource
private UserDao userDao;
// 业务方法
@Override
public void change() {
// 使用dao层插入数据
User user = new User(null, "root", "122", "54627946@qq.com");
userDao.insertUsers(user);
// 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
User user2 = new User(3, "sys", "1232", "54665885@qq.com");
userDao.updateUser(user2);
}
}
3. 配置文件
3.1 jdbc事务
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.2 配置事务的通知
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
<tx:method name="insertUser" propagation="REQUIRED"/>
<tx:method name="updateUser" propagation="REQUIRED"/>
-->
<!--这里使用*号是配置service中的所有方法, 就可以省略上面每个方法都要声明了-->
<!--propagation属性为事务的传播特性, 默认就是REQUIRED, 可省略-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
关于<tx:method>
标签的详细内容[点击跳转]
3.3 AOP织入事务
<!--AOP织入事务-->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.aaron.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
4. 测试
@Test
public void test3() {
userService.change();
}
测试结果, 抛出异常, 数据库中没有插入数据
假设我们跳过第三步, 直接进行测试, 虽然抛出了异常, 但是数据还是插入了
tx:method标签属性
关于<tx:method>
标签的属性, 简介如下
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
propagation | Propagation枚举 | REQUIRED | 事务传播属性 |
isolation | isolation枚举 | DEFAULT(所用数据库默认级别) | 事务隔离级别 |
readOnly | boolean | boolean | boolean |
timeout | int | -1 | 超时(秒), 超时后抛出异常 |
rollbackFor | Class[] | {} | 需要回滚的异常类 即遇到哪些异常回滚 包括异常的子类 |
rollbackForClassName | String[] | {} | 需要回滚的异常类名 |
noRollbackFor | Class[] | {} | 不需要回滚的异常类 即遇到哪些异常不回滚<br?包括异常子类 |
noRollbackForClassName | String[] | {} | 不需要回滚的异常类名 |
-
readOnly
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。 -
timeout
在属性中还有定义“timeout”值的选项,指定事务超时为几秒。一般不会使用这个属性。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。 -
Isolation Level
(事务隔离等级)的5个枚举值
为什么事务要有Isolation Level这个属性?先回顾下数据库事务的知识:- 第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
- 第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
- 脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。
- 虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。
- 不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。
当遇到以上这些情况时我们可以设置isolation下面这些枚举值:
DEFAULT:采用数据库默认隔离级别
SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;
REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
READ_COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。 -
propagation属性, 事务传播特性
-
取值:(
REQUIRED
)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择, 也是spring中默认的选择 -
取值:(
SUPPORTS
)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。 -
取值:(
MANDATORY
)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。 -
取值:(
REQUIRES_NEW
)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。 -
取值:(
NOT_SUPPORTED
)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 -
取值:(
NEVER
)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。 -
取值:(
NESTED
)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作
-
基于@Transactional注解的实现
注解的实现非常的简单, 我们不需要任何的配置, 只需要开启驱动, 然后在在service类上加个@Transactional
注解即可
例如:
@Transactional(readOnly = false,
rollbackFor =Throwable.class,
timeout =-1,
isolation = Isolation.READ_COMMITTED)
// service实现类...
开启驱动
配置文件 springboot可不写
<tx:annotation-driven />
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
给service类加注解
@Service
@Transactional
public class UserServiceImpl implements UserService {
// 注入dao层
@Resource
private UserDao userDao;
// 业务方法
@Override
public void change() {
// 使用dao层插入数据
User user = new User(null, "root", "122", "54627946@qq.com");
userDao.insertUsers(user);
// 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
User user2 = new User(3, "sys", "1232", "54665885@qq.com");
userDao.updateUser(user2);
}
}
进行测试后以后抛出事务已经回滚, 就说明成功了, 即数据抛异常后没有插入
注解属性
属性也可参考上面的 tx:method标签属性
属性名 | 说明 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1(永不超时)。 如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |