2019独角兽企业重金招聘Python工程师标准>>>
一:基础知识介绍
1.1:事务四个特性:ACID
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么全部失败。
- 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
- 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
1.2 隔离级别错误说明
- 脏读:并发事务之间一个事务读取了另一个事务还未提交的数据。
- 不可重复读:并发事务间一个事务多次读取,分别读取了另一个事务未提交和已经提交的数据,导致多次读取的数据状态不一致。
- 幻读:并发事务间一个事务读取了另一个事务已经提交的数据。与不可重复读的区别是不可重复读前后读取的是同一数据项,而幻读是一批数据。比如前后读取的数据数量不一致。
1.3 数据库四大隔离级别
- Serializable(串行化):可避免脏读、不可重复读、幻读的发生。
- Repeatable Read(可重复读):可避免脏读、不可重复读的发生。
- Read Committed(读已提交):可避免脏读的发生。
- Read unCommitted:(读未提交):最低级别,任何情况都会发生。
1.4 Spring隔离级别属性
- TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。
- TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
- TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
1.5 Spring事务的传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Spring定义了七种传播行为:
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前事务存在,则加入当前事务;如果不存在,则创建一个新的事务。一般默认此项。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前事务存在,则挂起当前事务。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事物;如果事务不存在,则以无事务方式执行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式执行,如果当前事务存在,则挂起当前事务。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式执行,如果存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:表示该方法必须在事务中运行,如果当前存在事务,则加入当前事务;如果不存在,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个新的事务作为当前事务内嵌事务执行,嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果不存在,则取该值等价于TransactionDefinition.PROPAGATION_REQUIRED,即创建一个新的事务
注:以下具体讲解传播行为的内容参考自Spring事务机制详解
此@Transactional注解来自org.springframework.transaction.annotation包,而不是javax.transaction。
1.6 Spring事务分类
- 声明式事务:即为在配置文件中配置,无需程序员手动编程控制事务,也就是说数据库的事务的开启,提交都是框架帮助我们做好的
- 编程式事务:是需要在方法中加入Spring的事务API 例如hibernate中的 beginTransaction() commit() rollback, 更加具有细粒度,但是同时也增加了代码的倾入性!
二:核心接口
Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。
Spring事务管理涉及的接口的联系如下:
2.1 事务管理器
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:
Public interface PlatformTransactionManager()...{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。
2.1.1 JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
2.1.2 Hibernate事务
如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
2.1.3 Java持久化API事务(JPA)
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
2.1.4 Java原生API事务
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName" value="java:/TransactionManager" />
</bean>
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
2.2 基本事务属性的定义
上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:
而TransactionDefinition接口内容如下:
public interface TransactionDefinition {
// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getIsolationLevel();
// 返回事务必须在多少秒内完成
int getTimeout();
// 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
boolean isReadOnly();
}
我们可以发现TransactionDefinition正好用来定义事务属性,下面详细介绍一下各个事务属性。
2.2.1 Spring 事务实现原理
在应用系统调用 事务声明 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据事务配置信息,这个代理对象决定该声明事务的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以CglibAopProxy 为例,对于CglibAopProxy,需要调用其内部类的DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,则调用其 invoke 方法。
三:遇到的问题
3.1:如果只是加了@Transactional注解,事务并不生效。
- 事务注解就否开启:
- xml:<tx:annotation-driver transaction-manager="transactionManager">
- java:@EnableTransactionManagement 开启事务支持后。
- 这个注解若是加载方法上,只能加在public的方法上,加到其他如private上不会报错,但是事务控制就不会生效。
- mysql数据库:myisam存储引擎不支持事务,换成innodb
3.2 Spring事务中需要注意的问题
事务失效问题
在同一个代理对象内部,事务方法之间的直接嵌套调用,普通方法和事务方法之间的直接嵌套调用,都会造成事务异常!具体表现为某些传播行为不生效或者直接事务控制不生效。
@Service
public class DemoService {
@Transaction
public void transactionMethod1()
{
op1();
op2();
...
}
public void commonMethod(){
...
transactionMethod1(); //照顾基础不牢的朋友,这里相当于 this.transactionMethod1();
...
}
@Transaction
public void transactionMethod2(){
...
this.transactionMethod3();
...
}
@Transaction(propagation= Propagation.REQUIRES_NEW)
public void transactionMethod3()
{
op3();
...
}
}
上面代码中,如果调用 DemoService 的 bean 对象的commonMethod ,则transactionMethod1里定义的事务将不生效(比如op2发生错误时,并不会回滚op1的操作),bean 调用 transactionMethod2时,transactionMethod2时里面调用的transactionMethod3也不会开启新的事务。
为什么会这样?
上面的实现机制中讲到,AOP的实现都是通过动态代理来实现,而AOP限制了我们只能在目标方法的开始和结束作为切点做切入处理增强。当动态代理对象最终调用的原始对象的目标方法时,并不能干预到目标方法内的方法调用行为,如果原始对象直接调用(用this.xxx方式)其他同类方法时,实际调用的是原始对象自身的方法,而不是代理类对象增强后(增加事务控制后)的方法。此时Spring对方法事务的控制(包括事务的传播行为、事务的隔离级别等)完全失效。
如何解决?
要想解决此类问题,主要都在于原始对象在调用对象内其他方法时,不要使用this.xxx的方式直接调用,通过注入或者获取代理对象的方式,使用代理对象调用需要调用的方法。下面列举几个解决方式:
1.注入自身,使用代理对象调用
@Service
public class DemoService {
@Autowired
DemoService demoService;
@Transaction
public void transactionMethod1() {
op1();
op2();
...
}
public void commonMethod() {
...
//this.transactionMethod1() -> demoService.transactionMethod1()
demoService.transactionMethod1();
...
}
}
2.使用AopContext,获取当前代理对象
@Service
public class DemoService {
@Transaction
public void transactionMethod1()
{
op1();
op2();
...
}
public void commonMethod()
{
...
//this.transactionMethod1() -> ((DemoService)AopContext.currentProxy()).transactionMethod1();)
((DemoService)AopContext.currentProxy()).transactionMethod1();
...
}
...
}
3.使用BeanFactory获取代理对象(代码略)
参考博客:
Spring事务配置的五种方式 :
spring 事务传播行为实例分析
Spring事务管理(详解+实例)
SpringBoot之事务处理机制
SpringBoot使用事务和AOP