@Transactional实现原理
Transactional是spring中定义的事务注解,在方法或类上加该注解开启事务。主要是通过反射获取bean的注解信息,利用AOP对编程式事务进行封装实现。AOP对事务的封装可以看我的这篇文章的介绍。
我们先写个demo,感受它的加载过程。
spring事务注解:
1. 自定义一个注解
/** * @Target 作用域(作用在方法上,类上,或是属性上) * @Retention 运行周期 * @interface 定义注解 */ @Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { //自定义注解的属性 int id() default 0; String name() default "默认名称"; String[]arrays(); String title() default "默认标题"; }
2. 测试
import java.lang.reflect.Method; public class User { @MyAnnotation(name="吴磊",arrays = {"2","3"}) public void aMethod () {} public void bMethod () {} public static void main(String[] args) throws ClassNotFoundException { //1. 反射获取到类信息 Class<?> forName = Class.forName("com.demo.User"); //2. 获取到当前类(不包含继承)所有的方法 Method[] declaredMethods = forName.getDeclaredMethods(); //3. 遍历每个方法的信息 for (Method method : declaredMethods) { System.out.println("方法名称:" + method.getName()); //4. 获取方法上面的注解 MyAnnotation annotation = method.getDeclaredAnnotation(MyAnnotation.class); if(annotation == null) { System.out.println("该方法上没有加注解...."); }else { System.out.println("Id:" + annotation.id()); System.out.println("Name:" + annotation.name()); System.out.println("Arrays:" + annotation.arrays()); System.out.println("Title:" + annotation.title()); } System.out.println("--------------------------"); } } } ============================= 【控制台输出】 方法名称:main 该方法上没有加注解.... -------------------------- 方法名称:aMethod Id:0 Name:吴磊 Arrays:[Ljava.lang.String;@74a14482 Title:默认标题 -------------------------- 方法名称:bMethod 该方法上没有加注解.... --------------------------
总结:通过上面这么一个小demo我们就能发现,反射获取到每一个方法的注解信息然后进行判断,如果这是@Transactional注解,spring就会开启事务。接下来我们可以按照这种思路基于上一篇博客的编程式事务自己实现一个事务注解。
手写注解事务:
1. 导包
<dependencies> <!-- 引入Spring-AOP等相关Jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> </dependencies>
2. 配置spring.xml文件
<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.wulei"/> <!-- 开启切面代理 --> <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>
3.1 自定义事务注解 (通过反射解析方法上的注解,如果有这个注解就执行事务逻辑)
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //@Transaction可以作用在类和方法上, 我们这里只作用在方法上。 @Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) /** * 自定义事务注解 */ public @interface MyAnnotation { }
3.2 封装编程式事务
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; @Component @Scope("prototype") // 申明为多例,解决线程安全问题。 /** * 手写编程式事务 */ public class TransactionUtil { // 全局接受事务状态 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("提交事务"); if(dataSourceTransactionManager != null) dataSourceTransactionManager.commit(transaction); } // 回滚事务 public void rollback(TransactionStatus transaction) { System.out.println("回滚事务..."); if(dataSourceTransactionManager != null) dataSourceTransactionManager.rollback(transaction); } }
3.3 通过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.wulei.transaction.TransactionUtil; @Aspect// 申明为切面 @Component /** * 切面类封装事务逻辑 */ public class AopTransaction { @Autowired private TransactionUtil transactionUtil; private TransactionStatus transactionStatus; /** * 环绕通知 在方法之前和之后处理事情 * @param pjp 切入点 */ @Around("execution(* com.wulei.service.*.*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { // 1.获取方法的注解 MyAnnotation annotation = this.getMethodMyAnnotation(pjp); // 2.判断是否需要开启事务 transactionStatus = begin(annotation); // 3.调用目标代理对象方法 pjp.proceed(); // 4.判断关闭事务 commit(transactionStatus); } /** * 获取代理方法上的事务注解 * @param pjp 切入点 */ private MyAnnotation getMethodMyAnnotation(ProceedingJoinPoint pjp) throws Exception { //1. 获取代理对对象的方法 String methodName = pjp.getSignature().getName(); //2. 获取目标对象 Class<?> classTarget = pjp.getTarget().getClass(); //3. 获取目标对象类型 Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes(); //4. 获取目标对象方法 Method objMethod = classTarget.getMethod(methodName, par); //5. 获取该方法上的事务注解 MyAnnotation annotation = objMethod.getDeclaredAnnotation(MyAnnotation.class); return annotation; } /** 开启事务 */ private TransactionStatus begin(MyAnnotation annotation) { if(annotation == null) return null; return transactionUtil.begin(); } /** 关闭事务 */ private void commit(TransactionStatus transactionStatus) { if(transactionStatus != null) transactionUtil.commit(transactionStatus); } /** * 异常通知进行 回滚事务 */ @AfterThrowing("execution(* com.wulei.service.*.*(..))") public void afterThrowing() { // 获取当前事务 直接回滚 //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); if(transactionStatus != null) transactionUtil.rollback(transactionStatus); } }
4. 编写dao层
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /* CREATE TABLE `t_users` ( `name` varchar(20) NOT NULL, `age` int(5) DEFAULT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; */ @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public int add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int result = jdbcTemplate.update(sql, name, age); System.out.println("插入成功"); return result; } }
5. 编写service
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.wulei.dao.UserDao; import com.wulei.transaction.MyAnnotation; @Service public class UserService { @Autowired private UserDao userDao; // 加上事务注解 @MyAnnotation public void add() { userDao.add("test001", 20); int i = 1 / 0; userDao.add("test002", 21); // // 如果非要try,那么出现异常不会被aop的异常通知监测到,必须手动在catch里面回滚事务。 // try { // userDao.add("test001", 20); // int i = 1 / 0; // userDao.add("test002", 21); // } catch (Exception e) { // // 回滚当前事务 // System.out.println("回滚"); // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // } } public void del() { System.out.println("del..."); } }
6. 测试
import org.springframework.context.support.ClassPathXmlApplicationContext; import com.wulei.service.UserService; public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); // aop()对userService类进行了拦截,添加自定义事务注解的方法会触发事务逻辑 userService.add(); // del()方法没有加注解,则什么也不会触发。 //userService.del(); } }