1. 事务的四个特性
原子性 Atomicity
事务包含的所有操作要么全部成功,要么全部失败回滚;成功必须要完全应用到数据库,失败则不能对数据库产生影响。
一致性 Consistency
事务执行前和执行后必须处于一致性状态。例如:转账事务执行前后,两账户余额的总和不变。
隔离性 Isolation
多个并发的事务之间要相互隔离。
持久性 Durability
事务一旦提交,对数据库的改变是永久性的.
2. 声明式事务管理
在 Spring 进行声明式事务管理,底层使用 AOP 原理。
2.1 重要的接口 PlatformTransactionManager
Spring 提供一个接口 PlatformTransactionManager,代表事务管理器,这个接口针对不同的框架提供不同的实现类:
2.2 基于 XML 配置
2.2.1 引入事务管理命名空间 tx
<?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.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
2.2.2 配置事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2.2.3 配置通知
<tx:advice id="txadvice">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"
isolation="REPEATABLE_READ"
timeout="-1"
read-only="false"/>
</tx:attributes>
</tx:advice>
2.2.4 配置切入点和切面
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.hedon.service.AccountService.*(..))"/>
<aop:advisor advice-ref="txadvice" pointcut-ref="pointcut"></aop:advisor>
</aop:config>
2.2.5 开启事务注解
执行前:
执行后报错:
数据库还是没有变,说明事务管理起到了作用:
2.3 基于注解
2.3.1 创建配置类 SpringConfig
@Configuration 配置类
@ComponentScan 组件扫描
@EnableTransactionManagement 开启事务支持
这里相当于 xml 文件中的:
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
@Configuration
@ComponentScan(basePackages
= {"com.hedon"})
@EnableTransactionManagement
public class SpringConfig {
2.3.2 配置数据源 DruidDataSource
@Bean
public DruidDataSource
druidDataSource(){
DruidDataSource druidDataSource
= new DruidDataSource();
druidDataSource
.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource
.setUrl("jdbc:mysql://localhost:3306/user_db");
druidDataSource
.setUsername("root");
druidDataSource
.setPassword("root");
return druidDataSource
;
}
2.3.3 配置 JdbcTemplate
@Bean
public JdbcTemplate
jdbcTemplate(DataSource dataSource
){
JdbcTemplate jdbcTemplate
= new JdbcTemplate();
jdbcTemplate
.setDataSource(dataSource
);
return jdbcTemplate
;
}
2.3.4 配置事务管理器 DataSourceTransactionManager
@Bean
public DataSourceTransactionManager
dataSourceTransactionManager(DataSource dataSource
){
DataSourceTransactionManager dataSourceTransactionManager
= new DataSourceTransactionManager();
dataSourceTransactionManager
.setDataSource(dataSource
);
return dataSourceTransactionManager
;
}
2.3.5 添加事务注解 @Transactional
添加到类上:这个类里面所有的方法都添加事务添加到方法上:为这个方法添加事务
@Service
@Transactional
public class AccountService {
2.3.6 测试
@Test
public void test1(){
ApplicationContext context
= new AnnotationConfigApplicationContext(SpringConfig
.class);
AccountService accountService
= context
.getBean("accountService", AccountService
.class);
accountService
.transfer();
}
结果是一样没有问题的。
2.4 @Transactional 参数说明
@Service
@Transactional(propagation
= Propagation
.REQUIRED
,
isolation
= Isolation
.REPEATABLE_READ
,
timeout
= 10,
readOnly
= false,
rollbackFor
= NullPointerException
.class,
noRollbackFor
= FileNotFoundException
.class)
public class AccountService {
2.4.1 propagation 事务传播行为
参考博客,讲得是真滴好!
指明当多事务方法直接进行调用时,这个过程中的事务是如何进行管理的。
如下:add() 方法有进行事务管理,而 update() 方法没有进行事务管理,一个有事务管理的方法调用一个没有事务管理的方法的时候,事务该如何传播呢?
2.4.1.1 REQUIRED
@Transactional(propagation
= Propagation
.REQUIRED
)
public void methodA() {
methodB();
}
@Transactional(propagation
= Propagation
.REQUIRED
)
public void methodB() {
}
单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
2.4.1.2 REQUIRES_NEW
使用 add() 方法调用 update() 方法,无论 add() 是否有事务,都创建新事务。
使用 PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager 作为事务管理器。 它会开启一个新的事务。如果一个事务已经存在,则先将这个存在的事务挂起。
如下代码:
@Transactional(propagation
= Propagation
.REQUIRED
)
public void methodA() {
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation
= Propagation
.REQUIRES_NEW
)
public void methodB() {
}
当单独调用 methodA 时,即:
main
{
methodA();
}
相当于:
main(){
TransactionManager tm
= null
;
try{
tm
= getTransactionManager();
tm
.begin();
Transaction ts1
= tm
.getTransaction();
doSomeThing();
tm
.suspend();
try{
tm
.begin();
Transaction ts2
= tm
.getTransaction();
methodB();
ts2
.commit();
} Catch(RunTimeException ex
) {
ts2
.rollback();
} finally {
}
tm
.resume(ts1
);
doSomeThingB();
ts1
.commit();
} catch(RunTimeException ex
) {
ts1
.rollback();
} finally {
}
}
在这里,我把 ts1 称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2 与 ts1是两个独立的事务,互不相干。Ts2 是否成功并不依赖于 ts1。如果 methodA 方法在调用 methodB 方法后的 doSomeThingB 方法失败了,而 methodB 方法所做的结果依然被提交。而除了 methodB 之外的其它代码导致的结果却被回滚了
2.4.1.3 MANDATORY
支持当前事务,如果当前没有事务,就抛出异常。
@Transactional(propagation
= Propagation
.REQUIRED
)
public void methodA() {
methodB();
}
@Transactional(propagation
= Propagation
.MANDATORY
)
public void methodB() {
}
当单独调用 methodB 时,因为当前没有一个活动的事务,则会抛出异常 throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
当调用 methodA 时,methodB 则加入到 methodA 的事务中,事务地执行。
2.4.1.4 SUPPORTS
@Transactional(propagation
= Propagation
.REQUIRED
)
public void methodA() {
methodB();
}
@Transactional(propagation
= Propagation
.SUPPORTS
)
public void methodB() {
}
单纯的调用 methodB 时,methodB 方法是非事务的执行的。当调用 methdA 时,methodB 则加入了 methodA 的事务中,事务地执行。
2.4.1.5 NOT_SUPPORTED
使用 add() 方法调用 update() 方法,无论 add() 是否有事务,update() 都不进行事务管理。PROPAGATION_NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务。使用 PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager 作为事务管理器。
2.4.1.6 NEVER
以非事务方式执行,如果当前存在事务,则抛出异常。
2.4.1.7 NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务,则按 TransactionDefinition.PROPAGATION_REQUIRED 属性执行。
这是一个嵌套事务,使用 JDBC 3.0 驱动时,仅仅支持 DataSourceTransactionManager 作为事务管理器。需要 JDBC 驱动的java.sql.Savepoint 类。使用 PROPAGATION_NESTED,还需要把 PlatformTransactionManager 的 nestedTransactionAllowed属性设为 true (属性值默认为false)。
这里的关键是嵌套事务,如下代码:
@Transactional(propagation
= Propagation
.REQUIRED
)
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
@Transactional(propagation
= Propagation
.NEWSTED
)
methodB(){
……
}
如果单独调用 methodB 方法,则按 REQUIRED 属性执行。如果调用 methodA 方法,相当于下面的效果:
main(){
Connection con
= null
;
Savepoint savepoint
= null
;
try{
con
= getConnection();
con
.setAutoCommit(false);
doSomeThingA();
savepoint
= con2
.setSavepoint();
try{
methodB();
} catch(RuntimeException ex
) {
con
.rollback(savepoint
);
} finally {
}
doSomeThingB();
con
.commit();
} catch(RuntimeException ex
) {
con
.rollback();
} finally {
}
}
当 methodB 方法调用之前,调用 setSavepoint 方法,保存当前的状态到 savepoint。如果 methodB 方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码 (doSomeThingB()方法) 调用失败,则回滚包括 methodB 方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
△ 比较PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别
# 相似点
都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
# 不同点
① 使用 PROPAGATION_REQUIRES_NEW 时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
② 使用PROPAGATION_NESTED 时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager 使用 savepoint 支持 PROPAGATION_NESTED 时,需要 JDBC 3.0以上驱动及 1.4 以上的JDK版本支持。其它的 JTATrasactionManager 实现可能有不同的支持方式。
③ PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围,自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
④ 另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
# 最大区别
PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back。
2.4.2 isolation 事务隔离级别
2.4.2.1 事务的隔离性
多事务操作之间不会产生影响。
2.4.2.2 三个读问题
脏读
一个未提交的事务读取到另一个未提交的事务的数据。
如上:东方不败想从5000改到100,而岳不群想从5000改到60000。这个时候岳不群先改了,然后东方不败读取到数据已经改成60000了,所以东方不败就会继续在60000的基础上进行修改。但是这个时候,岳不群的事务并没有进行提交,而且进行了事务回滚,所以真实的数据现在还是5000,而东方不败操作的数据是60000。这就叫脏读。
不可重复读
一个未提交事务读取到另一提交事务修改数据。
如上:东方不败先读取到数据是5000,想对数据进行操作,但是这个时候岳不群已经将数据改成900了。而东方不败又检测到了数据已经改成900了,读两次,数据不一致,这就是不可重复读(因为不知道再读的话是不是又会不一样了)。
虚(幻)读
一个未提交事务读取到另一提交事务增加的数据。
2.4.2.3 解决读问题 —— 设置事务的隔离性
isolation 属性值意思脏读不可重复读虚读作用
READ UNCOMMITTED读未提交有有有效率高,但是啥也避免不了READ COMMITTED读已提交无有有常用,可避免脏读REPEATABLE READ可重复读无无有可以用在非 insert 方法上SERIALIZABLE串行化无无无三个问题都解决了,但效率低DEFAULT使用数据库默认MySQL 的话就是 REPEATABLE READ
2.4.3 timeout 超时时间
事务需要在一定时间内进行提交,如果不提交进行回滚。
默认时间是 -1(永不超时),设置时间以秒单位进行计算。
2.4.4 readOnly 是否只读
readOnly 默认值 false,表示可以查询,可以添加修改删除操作。
设置 readOnly 值是 true,设置成 true 之后,只能查询。
2.4.5 rollbackFor 回滚
设置出现哪些异常进行事务回滚。
2.4.6 noRollbackFor 不回滚
设置出现哪些异常不进行事务回滚。