-手写Spring注解版本&事务传播行为

视频参考C:\Users\Administrator\Desktop\蚂蚁3期\【www.zxit8.com】 0018-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring注解版本&事务传播行为\0018-(每特教育&每特学院&蚂蚁课堂)-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的事物注解

-手写Spring注解版本&事务传播行为

例如现在add方法上我们使用@ExtTransactional如何实现注解了。定义一个aop的切面类,在切面类中定义切点可以拦截到controller类的add方法被调用了。在切面中利用反射技术判断add方法上面是否使用了注解,如果使用了注解就开启事务,没有就不开启事务

-手写Spring注解版本&事务传播行为

第一步:需要将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> <!-- 开启事物注解 --> <!-- . 数据源对象: 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> <!-- . 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>
 使用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);都会回滚

上一篇:C# foreach 循环遍历数组


下一篇:Spring中@Transactional事务回滚