编程式事务控制相关对象
自己写代码的方式去控制事务为编程式。不用自己写代码,通过配置让spring去控制称为声明式
PlatformTransactionManager
platformTransactionManager接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法。属于控制行为的
方法 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 后滚事务 |
注:
PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:Dao层技术是jdbc或mybatis是:org.springframework.jdbc.datasource.DataSourceTransactionManager
TransactionDefinition
TransactionDefinition是事务的定义信息对象,里面有如下方法。属于参数信息
方法 | 说明 |
---|---|
int getIsolationLevel() | 获得事务的隔离级别 |
int getPropogationBehavior() | 获得事务的传播行为 |
int getTimeout() | 获得超长时间 |
boolean isReadOnly() | 是否只读 |
TransactionStatus
TransactionStatus接口提供的是事务具体的运行状态,方法如下状态
方法 | 说明 |
---|---|
boolean hasSavepoint() | 是否存储回滚点 |
boolean isCompleted() | 事务是否完成 |
boolean isNewTransaction() | 是否是新事务 |
boolean isRollbackOnly() | 事务是否后滚 |
事务的隔离级别
设置隔离级别,可以决绝事务并发产生的问题,如脏读、不可重复读和虚读
- ISOLATION_DEFAULT
- ISOLATION_READ_UNCOMMITTED:读未提交
- ISOLATION_READ_COMMITTED:读已提交
- ISOLATION_REPEATABLE_READ:重复读
- ISOLATION_SERIALIZABLE:序列化,可全读,但性能低
事务传播行为
业务在调用业务方法时他们之间的事务统一性的问题
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- …
- 超时时间:默认值是-1,没有超时限制。如果没有,以秒为单位进行设置
- 是否只读:建议查询时设置为只读
基于xml的声明式事务控制
【spring_tx】
声明式事务:采用声明的方式来处理事务
tx是事务的命名空间
注:spring声明式事务控制底层就是AOP
bean配置
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.name}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="accountDao" class="com.lhc.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--目标对象 内部的方法就是切点-->
<bean id="accountService" class="com.lhc.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置平台事务管理器-->
<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="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lhc.service.impl.*.*(..))"></aop:advisor>
</aop:config>
事务管理器的配详解
<!--通知 事务的增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
-
transaction-manager是使用平台事务的管理器
-
那么<tx:attributes>是设置事务的属性信息的
-
<tx:method name=“*”/>是增强的方法,name是方法名,即TransactionDefinition是事务的定义信息对象。属于参数信息
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"></tx:method>
还有这些属性可配置
基于注解的声明式事务控制@Transactional
注解 | 说明 |
---|---|
@Transactional | 使用在需要控制的事务上。可用在类或方法 |
- @Transactional参数说明
- int getIsolationLevel()获得事务的隔离级别====>isolation=Isolation.READ_COMMITTED
- int getPropogationBehavior()获得事务的传播行为====>Propagation=Proagation.REQUIRED
- int getTimeout()获得超长时间====>
- boolean isReadOnly()是否只读====>
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
/** 事务传播机制行为 */
Propagation propagation() default Propagation.REQUIRED;
/** 事务隔离级别 */
Isolation isolation() default Isolation.DEFAULT;
/** 超时时间/秒,如果超过这个时间还没执行完就回滚 */
int timeout() default -1;
String timeoutString() default "";
/** 只读模式 */
boolean readOnly() default false;
/** 回滚规则 */
/* 默认规则
检查性异常不会回滚eg:IOException
运行时异常才会回滚eg:RuntimeException
*/
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
/** 指定出现某个异常时,不回滚 */
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
spring配置文件需要配置事务的注解驱动,否则注解不起作用
<!--事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
全注解式的事务
@EnableTransactional、@Transactional
@Transactional
当有多个查询语句是,最好使用@Transactional(readOnly=true),保证多条语句查询到的数据都是一样的,不会因其它事务操作了之后而改变
- 只能在public方法中起作用
- 自调用事务,事务不生效
事务传播性(这里说的是Spring)
一个事务调用另一个事务,那么另一个事务是以什么样的格式来加入当前这个事务呢,这就是事务的传播性
一般用在子事务中,相对来说是进入其他事务时候的
传播性 | 描述 |
---|---|
REQUIRED(spring默认) | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
REQUIRES_NEW | 创建一个新的事务(独立的事务);如果当前存在事务,则把当前事务挂起 因为是独立的,所以子事务的回滚不会影响到父事务 |
NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPGATION_REQUIRED 子事务会跟着父事务一样回滚 |
@Transactional(propagation=Propagation.XXXX)
Propagation : key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS
–支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY-
-支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW
–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED
–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER
–以非事务方式执行,如果当前存在事务,则抛出异常。
1: Propagation.REQUIRED(spring默认的)
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2: Propagation.SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
3: Propagation.MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常
4: Propagation.REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5: Propagation.NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
6: Propagation.NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。
7: Propagation.NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
事务失效场景
- 类中的自调用:spring事务是基于AOP的,只要使用代理对象调用某个方法时,Spring事务才能生效,而在一个方法中调用,使用this.A()时,this并不是代理对象,所以会导致事务失效(@Transactional无效)
- 解决方法1:把调用方法拆分到另一个bean中
- 解决方法2:自己注入自己
- 解决方法3:(当前类)AopContext.currentProxy() + @EnableAspectJAutoProxy(exposeProxy = true)
- 方法是private 的:spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来实现,子类是代理类,父类是被代理类,如果父类中的某个方法是private的,那么子类就没有办法重写它,也就没有办法额外增加spring事务的逻辑
- 方法是final的:原因和private是一样的,也是由于子类不能重写父类中的final方法
- 单独的线程调用方法:当mybatis或jdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象,如果开始事务的线程和执行SQL的线程时同一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿不到数据库连接对象,这样,mybatis或jdbcTemplate就会去新建一个数据库连接用来执行SQL,此数据库连接的autocommit为true,那么执行为SQL就会提交,后续再抛异常也就不能回滚之前已经提交了的SQL了
- 没加@Configuration注解:如果用SpringBoot基本没有这个问题,但是如果用的Spring,那么可能会有这个问题,这个问题的原因其实也是由于Mybatis或JdbcTemplate会从ThreadLocal中去获取数据库连接,但是ThreadLocal中存储的一个MAP,MAP的key为DataSource对象,value为连接对象,而如果我们没有在AppConfig上添加@Configuration注解的话,会导致MAP中存的DataSource对象和Mybatis和JdbcTemplate中的DataSource对象不相等,从而拿不到数据库连接,导致自己去创建数据库连接了
- 异常被吃掉:如果Spring事务没有捕获到异常,那么也就不会回滚了,默认情况下Spring会捕获RuntimeException和Error
- 类没有被Spring管理
- 数据库不支持事务