视频参考C:UsersAdministratorDesktop蚂蚁3期【www.zxit8.com】 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring注解版本&事务传播行为 018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring注解版本&事务传播行为
今天视频要手写一个@Transaction事务注解框架
我们首先来看一个spring 事务的常见配置
<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> <!-- 开启注解事物 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" /> </beans>
在业务层中@Transaction来使用注解
@Transactional public void add() { userDao.add("wangmazi", 27); int i = 1 / 0; System.out.println("我是add方法"); userDao.add("zhangsan", 16); }
接下来我们要手写@Transactional注解框架
接下来首先学习下java的注解以及利用反射读取注解
@Target(value = { ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface AddAnnotation { int userId() default 0; String userName() default "默认名称"; String[]arrays(); }
接下来利用反射读取注解
package com.itmayiedu.annotation; import java.lang.reflect.Method; public class User { @AddAnnotation(userName = "张三", userId = 18, arrays = { "1" }) public void add() { } public void del() { } public static void main(String[] args) throws ClassNotFoundException { // 怎么样获取到方法上注解信息 反射机制 Class<?> forName = Class.forName("com.itmayiedu.annotation.User"); // 获取到当前类(不包含继承)所有的方法 Method[] declaredMethods = forName.getDeclaredMethods(); for (Method method : declaredMethods) { // 获取该方法上是否存在注解 System.out.println("####方法名称" + method.getName()); AddAnnotation addAnnotation = method.getAnnotation(AddAnnotation.class); if (addAnnotation == null) { // 该方法上没有注解 System.out.println("该方法上没有加注解.."); continue; } // 在该方法上查找到该注解 System.out.println("userId:" + addAnnotation.userId()); System.out.println("userName:" + addAnnotation.userName()); System.out.println("arrays:" + addAnnotation.arrays()); System.out.println(); } } }
现在有个User类,类中add方法我们添加了注解,接下来使用反射来操作User类,通过反射获得add方法,然后判断该方法上面是否使用了注解,如果使用了把注解的值打印出来
我们来看下打印的日志
####方法名称main
该方法上没有加注解..
####方法名称add
userId:18
userName:张三
arrays:[Ljava.lang.String;@6607db7d
####方法名称del
该方法上没有加注解..
接下来我们重点讲解手写@Transaction spring的事物注解
例如现在add方法上我们使用@ExtTransactional如何实现注解了。定义一个aop的切面类,在切面类中定义切点可以拦截到controller类的add方法被调用了。在切面中利用反射技术判断add方法上面是否使用了注解,如果使用了注解就开启事务,没有就不开启事务
第一步:需要将spring框架配置文件中的开启事务管理注解去掉
<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> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 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> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> </beans>
去掉:
<!-- 开启注解事务 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
第二步:自定义一个注解
package com.itmayiedu.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 事务注解 设置传播行为 @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface ExtTransaction { }
第三步:自定义一个事务管理工具的实现类,这里使用的是jdbc的事务管理器来实现对事务的管理
package com.itmayiedu.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; // 编程事务(需要手动begin 手动回滚 手都提交) @Component @Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子 public class TransactionUtils { // 全局接受事务状态 private TransactionStatus transactionStatus; // 获取事务源 @Autowired private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务 public TransactionStatus begin() { System.out.println("开启事务"); transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transactionStatus; } // 提交事务 public void commit(TransactionStatus transaction) { System.out.println("提交事务"); dataSourceTransactionManager.commit(transaction); } // 回滚事务 public void rollback() { System.out.println("回滚事务..."); dataSourceTransactionManager.rollback(transactionStatus); } }
上面这个类有几个地方需要注意的,dao层使用的是jdbcTemplate,使用jdbc的方式,所以要使用jbdc数据源的事务管理器
DataSourceTransactionManager,其他框架hibernate的事务管理器为
Spring声明式事务管理器类:
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
第二:对于事务管理工具类一个事务应该对于一个事务管理工具类的实例,spring默认代理对象都是单例模式,如果是单例模式多个事务就是多个线程共享一个对象,会出现线程安全问题
所以@Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子,定义为多实例类型
接下来,定位aop实现对调用方法的拦截,实现事务管理
package com.itmayiedu.aop; import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.itmayiedu.annotation.ExtTransaction; import com.itmayiedu.transaction.TransactionUtils; // 自定义事务注解具体实现 @Aspect @Component public class AopExtTransaction { // 一个事务实例子 针对一个事务 @Autowired private TransactionUtils transactionUtils; // 使用异常通知进行 回滚事务 @AfterThrowing("execution(* com.itmayiedu.service.*.*.*(..))") public void afterThrowing() { // 获取当前事务进行回滚 // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); transactionUtils.rollback(); } // 环绕通知 在方法之前和之后处理事情 @Around("execution(* com.itmayiedu.service.*.*.*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { // 1.获取该方法上是否加上注解 ExtTransaction extTransaction = getMethodExtTransaction(pjp); TransactionStatus transactionStatus = begin(extTransaction); // 2.调用目标代理对象方法 pjp.proceed(); // 3.判断该方法上是否就上注解 commit(transactionStatus); } private TransactionStatus begin(ExtTransaction extTransaction) { if (extTransaction == null) { return null; } // 2.如果存在事务注解,开启事务 return transactionUtils.begin(); } private void commit(TransactionStatus transactionStatus) { if (transactionStatus != null) { // 5.如果存在注解,提交事务 transactionUtils.commit(transactionStatus); } } // 获取方法上是否存在事务注解 private ExtTransaction getMethodExtTransaction(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException { String methodName = pjp.getSignature().getName(); // 获取目标对象 Class<?> classTarget = pjp.getTarget().getClass(); // 获取目标对象类型 Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes(); // 获取目标对象方法 Method objMethod = classTarget.getMethod(methodName, par); ExtTransaction extTransaction = objMethod.getAnnotation(ExtTransaction.class); return extTransaction; } }
package com.itmayiedu.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, age); System.out.println("updateResult:" + updateResult); } }
package com.itmayiedu.service; //user 服务层 public interface UserService { public void add(); public void del(); }
接下来在实现类使用
@ExtTransaction就实现了我们的自定义事务注解
package com.itmayiedu.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.itmayiedu.dao.UserDao; import com.itmayiedu.service.LogService; import com.itmayiedu.service.UserService; //user 服务层 @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Autowired private TransactionUtils transactionUtils; @ExtTransactionpublic void add() { userDao.add("test001", 20); // int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); } // 方法执行完毕之后,才会提交事务 public void del() { System.out.println("del"); } }
接下来我们分析下事务的传播特性,我们使用spring框架默认自带的注解
我们在配置文件中开启事务管理,然后将上面我们自己编写的AopExtTransaction事务框架代码屏蔽掉,以免和spring默认的框架起冲突
注意:spring的事务传播特性是在两个不同的service中相互调用
我们在新建一个dao
package com.itmayiedu.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class LogDao { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name) { String sql = "INSERT INTO t_log(log_name) VALUES(?);"; int updateResult = jdbcTemplate.update(sql, name); System.out.println("##LogDao##updateResult:" + updateResult); } }
package com.itmayiedu.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.itmayiedu.dao.LogDao; import com.itmayiedu.service.LogService; @Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Transactional public void addLog() { logDao.add("addLog" + System.currentTimeMillis()); // int i = 1 / 0; } }
<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> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 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> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启注解事务 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" /> </beans>
使用spring框架的默认事务管理,这里需要开启注解事务管理的配置
<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" />
PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
对应的就是在addLog()方法上面使用@Transactional注解
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
}
@Transactional
public void add() {
// 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。
logService.addLog(); // 后面程序发生错误,不能影响到我的回滚### 正常当addLog方法执行完毕,就应该提交事务
userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
}
我们来看下运行的情况
第一种情况:正常情况下,事务正常 log日志和人员信息都会添加成功
第二种情况,如果在addLog方法中发送了异常
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0;
}
这个时候因为addLog中发送了异常,这个时候就会把异常抛出去给aop框架处理,后续的代码就不会再继续执行
就不会再执行
logService.addLog(); // 后面程序发生错误,不能影响到我的回滚### 正常当addLog方法执行完毕,就应该提交事务
userDao.add("test001", 200);
// int i = 1 / 0;
System.out.println("################");
userDao.add("test002", 21);
因为addLOg方法出现了异常就不会再执行后续的userDao.add("test001", 200);方法,抛出异常的时候aop框架会对事务进行回滚,所以ogDao.add("addLog" + System.currentTimeMillis());不会添加数据到数据库中
第三种情况
如果在addLog方法中发送了异常
@Transactional
public void addLog() {
logDao.add("addLog" + System.currentTimeMillis());
int i = 1 / 0;
}
但是在userService的add方法中使用了try catch进行捕获,我们来看下数据库的情况
@Transactional(rollbackFor=Exception.class) public void add() { // 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 try{ logService.addLog(); }catch(Exception e){ System.out.println(e.toString()); } userDao.add("test001", 200); // int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); }
我们来看下这种情况下的运行情况
运行居然抛出了异常
Exception in thread "main" org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
at com.sun.proxy.$Proxy13.add(Unknown Source)
at com.itmayiedu.Test001.main(Test001.java:12)
数据库中没有插入任何数据,为啥出现上面这种情况了
##Transaction rolled back because it has been marked as rollback-only spring 具备多种事务传播机制,最常用的是REQUIRED,即如果不存在事务,则新建一个事务;如果存在事务,则加入现存的事务中。 示例代码如下:
public void A() {
querySomething(...);
try {
B()
} catch () {
}
saveSomethinf();
}
public void B() {
throw Exception()
}
此时B会和A存在一个事务中。如果B抛出异常没有捕获,即使在A中捕获并处理,仍会发生异常:Transaction rolled back because it has been marked as rollback-only 因为spring会在A捕获异常之前提前捕获到异常,并将当前事务设置为rollback-only,而A觉得对异常进行了捕获,它仍然继续commit,当TransactionManager发现状态为设置为rollback-only时, 则会抛出UnexpectedRollbackException 相关代码在AbstractPlatformTransactonManager.java中:
public final void commit(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
// Throw UnexpectedRollbackException only at outermost transaction boundary
// or if explicitly asked to.
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
processCommit(defStatus);
}
解决方法: 在抛出异常的最原始地方处理异常,即在spring捕获到异常之前处理掉
在一个transactional中如果有另一transaction,内层的事务会跟随外层的事务,变为一个整体的事务。
如果内层transaction发生了异常,即使你捕捉了这个异常,那么整体的Transaction也会被定义成RollbackOnly,这也正是事务管理的原则。所以当外层事务想提交整个事务时,出现异常。
解决版本就是在logService中对异常进行try catch处理
@Transactional public void addLog() { try{ logDao.add("addLog" + System.currentTimeMillis()); int i = 1 / 0; }catch(Exception e){ } }
@Transactional(rollbackFor=Exception.class) public void add() { // 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 logService.addLog(); userDao.add("test001", 200); // int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); }
上面这种情况:log日志和人员信息都会添加到数据库中, int i = 1 / 0;发现了异常但是我们自定义了try catch处理异常,就不会把异常抛出去给aop框架进行处理,对数据进行回滚
因为logService.addLog(); 对外没有抛出异常,后续的 userDao.add("test001", 200);代码也会正常执行,把人员数据添加到数据库中
第五情况在add添加人员方法中抛出了异常
@Transactional public void addLog() { logDao.add("addLog" + System.currentTimeMillis()); int i = 1 / 0; }
@Transactional(rollbackFor=Exception.class) public void add() { // 调用接口的时候 接口失败 需要回滚,但是日志记录不需要回滚。 logService.addLog(); userDao.add("test001", 200); int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); }
在调用add方法中抛出了异常,因为当前事务addLog的事务传播属性是 @Transactional,PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
所以logService.addLog();和userDao.add("test001", 200);是在同一个事务中,当发现异常的时候,如果没有对异常使用try catch进行处理,会把异常抛出去给aop框架进行处理
异常前面的
logService.addLog(); 和 userDao.add("test001", 200);都会回滚