spring事务深度解析
什么是事务?
事物的概述
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
事务的7种传播行为:
Propagation(key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。)有以下选项可供使用:
PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。(如果当前有事物,我就用当前事物,如果当前没有事物,就以非事物进行执行)
PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
事务的分类:
- 编程式事务控制
自己手动控制事务,就叫做编程式事务控制。
Jdbc代码:
Conn.setAutoCommite(false); // 设置手动控制事务
Hibernate代码:
Session.beginTransaction(); // 开启一个事务
【细粒度的事务控制: 可以对指定的方法、指定的方法的某几行添加事务控制】
(比较灵活,但开发起来比较繁琐: 每次都要开启、提交、回滚.)
2. 声明式事务控制(xml 或者注解)
Spring提供了对事务的管理, 这个就叫声明式事务管理。
【粗粒度的事务控制: 只能给整个方法应用事务,不可以对方法的某几行应用事务。】(因为aop拦截的是方法。)
我们实际开发中使用的是声明式事务《注解式》,在深入了解注解式事务之前,我们先自己手写一个事务《手动管理事务》。
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void add(String name, Integer age) {
String sql = "INSERT INTO users(NAME, age) VALUES(?,?);";
int update = jdbcTemplate.update(sql, name, age);
System.out.println("insertResult:" + update);
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionUtils transactionUtils
public void add() {
TransactionStatus begin= null;
try{
begin = transactionUtils .begin();
userDao.add("lisi", 18);
int i =1/0;//可能会发生异常
transactionUtils .commit(begin);
}catch(Exception e){
e.printStackTrace();
transactionUtils.rollback(begin);
}
}
}
手动事务管理类:
@Component
public class TransactionUtils {
// 事物管理器
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
//开启一个默认事务
public TransactionStatus begin() {
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionDefinition());
return transaction;
}
public void commit(TransactionStatus transaction) {
dataSourceTransactionManager.commit(transaction);
}
public void rollback(TransactionStatus transaction) {
dataSourceTransactionManager.rollback(transaction);
}
}
xml文件配置:
<?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:p="http://www.springframework.org/schema/p"
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">
<!-- 开启注解 -->
<context:component-scan base-package="com.itmayiedu"></context:component-scan>
<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事物 -->
<bean id="DataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
这个时候假如类似Dao层的add()方法多的话,每一个方法都要开启事务,提交事务,回滚事务,造成了代码的冗余。所以我们要用AOP管理事务。
声明式事务原理:
<!—配置事物增强-->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="*" read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 切面类 -->
<bean id="aop" class="com.itmayiedu.Aop"></bean>
<!-- Aop配置: 拦截哪些方法(切入点表表达式) + 应用上面的事务增强配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.itmayiedu.service.*.*(..))"
id="pt" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
<!-- 切面 -->
<aop:aspect ref="aop">
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 前置通知: 在目标方法调用前执行 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 后置通知: -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 返回后通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 异常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
public class Aop {
private TransactionUtils transactionUtils
private TransactionStatus begin;
//@Before("execution(* com.itmayiedu.service.UserService.add(..))")
public void begin() {
System.out.println("前置通知");
}
// @After("execution(* com.itmayiedu.service.UserService.add(..))")
public void commit() {
System.out.println("后置通知");
}
// @AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))")
public void afterReturning() {
System.out.println("运行通知");
}
//@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
public void afterThrowing() {
System.out.println("异常通知");
//出现异常,进行事务的回滚
transactionUtils.rollback(begin);
}
//@Around("execution(* com.itmayiedu.service.UserService.add(..))")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开启事务
begin =transactionUtils .begin();
System.out.println("我是环绕通知-前");
proceedingJoinPoint.proceed();//执行add方法
System.out.println("我是环绕通知-后");
transactionUtils .commit(begin);//提交事务
}
}
com.itmayiedu.service.UserService.add方法:xml文件中配置的Aop的环绕通知来处理add()方法
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionUtils transactionUtils
public void add() {
userDao.add("lisi", 18);
int i =1/0;//可能会发生异常
}
}
注意:这里的add方法是不能加入try Catch代码块的,因为这里如果出现异常的话,直接在catch块里捕捉了异常,不会再aop切面类里的环绕通知执行方法时,有异常抛出,即使用声明式事务时,不要try,将异常抛出
try{
userDao.add("lisi", 18);
int i =1/0;//可能会发生异常
}catch(Exception e){
e.printStackTrace();
}
事务原理:Aop编程 + 环绕通知 + 异常通知
1.环境搭建
spring如果使用mysql数据库的话,在pom文件中需要导入有个依赖:
他会引入两个Jar包:
2.配置数据源,使用JDBC Template操作数据 (此时并没有用到mybatis框架)
分析注解原理:
@EnableTransactionManagement开启注解进入这个注解中,可以看到
解析一:Improt导入组件TransactionManagementConfigurationSelector(事务管理配置选择器),AdviceMode 是一个枚举类,默认设置AdviceMode 的值为Proxy
进入这个选择器:
解析一:通过switch case判断我们要进入的选择为:PROXY
返回值为:一个字符数组,数组里面包含了AutoProxyRegistrar 和ProxyTransactionManagementConfiguration,说明我们要导入这两个组件
解析二 :AutoProxyRegistrar功能分析:
AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,用registerBeanDefinitions方法来注册Bean
因为mode等于PROXY ,所以执行如下功能:
registerAutoProxyCreatorIfNecessary 方法 注InfrastructureAdvisorAutoProxyCreator组件
所以我们来分析InfrastructureAdvisorAutoProxyCreator组件的功能?
InfrastructureAdvisorAutoProxyCreator 继承了AbstractAdvisorAutoProxyCreator
利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;
解析三 :ProxyTransactionManagementConfiguration
功能分析:1.给容器中注册事务增强器,
需要用到事务注解的信息
需要用到事务拦截器TransactionInterceptor的信息:
保存了事务属性信息 和事务管理信息
进入事务拦截器TransactionInterceptor:
发现他是一个方法拦截器。MethodInterceptor:代理对象如果要执行目标方法,方法拦截器就要工作。
找到TransactionInterceptor的invoke方法:
可以看到invoke方法执行了invokeWithinTransaction方法
进入这个方法:invokeWithinTransaction
txAttr:先获取事务属性
再获取PlatformTransactionManager事务管理器,
PlatformTransactionManager事务管理器是什么呢?
分析事务管理器的获取方法?
如果异常,获取到事务管理器,利用事务管理回滚操作;
进入异常回滚方法:
如果正常,利用事务管理器,提交事务
在目标方法执行的时候;
执行拦截器链;
事务拦截器:
-
1)、先获取事务相关的属性 2)、再获取PlatformTransactionManager,如果事先没有添加指定任何transactionmanger 最终会从容器中按照类型获取一个PlatformTransactionManager; 3)、执行目标方法 如果异常,获取到事务管理器,利用事务管理回滚操作; 如果正常,利用事务管理器,提交事务