个人随笔
目录
手写实现Spring事务注解
2020-09-11 22:21:11

我们在用Spring框架的时候,在service层完全不用担心事物的控制,一个注解就搞定一切,这个Transaction注解这么厉害,到底是怎么实现的呢,这篇博文来手写一个跟Spring的Transaction事务控制一样的注解。当然只是简单的实现事务的自动提交,回滚功能。

一、Spring事务注解的原理分析

  1. 这里能按自己的分析理解一下Transaction的实现原理,后面再根据这个原理来手写实现。
  2. 我们知道,jdbc操作数据库是经过、创建连接、开启事务、执行语句、提交事务这几个步骤来操作的,有异常的话就回滚事务。在我们编写代码过程中,往往是直接将这几个步骤耦合在代码里,这样子的话代码太过冗余且难以维护。此时在Spring中只要在Service层加上@Transaction注解就自动帮我们完成啦。
  3. 不管再怎么简单,肯定都经历过开启事务、提交事务、有异常回滚事务的操作。所以Spring在逻辑执行到service的具体方法时,肯定先开启事务,然后执行完后提交事务,有异常回滚事务,那么什么技术可以不侵入代码可以织入这些操作呢,很容易我们就想到了Spring AOP技术,通过SpringAOP技术在逻辑执行到service的具体方法的时候,先检查方法是否有@Transaction注解,如果有就开启事务,在执行完后就提交事务,有异常就回滚事务,这里我们当然想到了AOP的环绕通知和异常通知。

二、手写实现事务注解

1、实现基础

这里直接借助SpringAOP的技术来实现,可能有人会问,这个不就还是用Spring来实现了吗,为什么不是从0开始?我天,要是从0开始,自己先实现Spring的IOC,实现Spring的AOP,不要问我用什么技术实现IOC,AOP,IOC当然是直接读取配置文件或者注解,然后用反射技术来实现,这个以后我会手写IOC的,AOP的话就用动态代理的技术来实现,所以如果我真的要从0开始实现,那么不是简单的一篇博文可以实现的,最起码得写成一本书。

2、项目结构

这里用maven来搭建项目,然后引入c3p0和mysql-connector-java。项目结构如下:

文件 作用
pom.xml maven项目依赖
spring.xml Spring的配置文件
AppMySpringTransaction.java 启动类,主函数
ExtTransaction.java 自定义的事务注解
TransactionAOP.java 检测事务注解的AOP
MyDao.java 操作数据库的类,DAO层
MyService.java Service层
MyTransaction.java 事务控制工具类,控有事务的开启,提交,回滚方法

3、pom.xml

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  2. <modelVersion>4.0.0</modelVersion>
  3. <groupId>cn.myforever</groupId>
  4. <artifactId>my-spring-transaction</artifactId>
  5. <version>0.0.1-SNAPSHOT</version>
  6. <dependencies>
  7. <!-- 引入Spring-AOP等相关Jar -->
  8. <dependency>
  9. <groupId>org.springframework</groupId>
  10. <artifactId>spring-core</artifactId>
  11. <version>3.0.6.RELEASE</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.springframework</groupId>
  15. <artifactId>spring-context</artifactId>
  16. <version>3.0.6.RELEASE</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework</groupId>
  20. <artifactId>spring-aop</artifactId>
  21. <version>3.0.6.RELEASE</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>org.springframework</groupId>
  25. <artifactId>spring-orm</artifactId>
  26. <version>3.0.6.RELEASE</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.aspectj</groupId>
  30. <artifactId>aspectjrt</artifactId>
  31. <version>1.6.1</version>
  32. </dependency>
  33. <dependency>
  34. <groupId>aspectj</groupId>
  35. <artifactId>aspectjweaver</artifactId>
  36. <version>1.5.3</version>
  37. </dependency>
  38. <dependency>
  39. <groupId>cglib</groupId>
  40. <artifactId>cglib</artifactId>
  41. <version>2.1_2</version>
  42. </dependency>
  43. <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
  44. <dependency>
  45. <groupId>com.mchange</groupId>
  46. <artifactId>c3p0</artifactId>
  47. <version>0.9.5.2</version>
  48. </dependency>
  49. <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  50. <dependency>
  51. <groupId>mysql</groupId>
  52. <artifactId>mysql-connector-java</artifactId>
  53. <version>5.1.37</version>
  54. </dependency>
  55. </dependencies>
  56. </project>

引入Spring和数据库操作的依赖,这用的是MySQL数据库

4、Spring.xml

  1. <beans xmlns="http://www.springframework.org/schema/beans"
  2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
  3. xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd
  9. http://www.springframework.org/schema/aop
  10. http://www.springframework.org/schema/aop/spring-aop.xsd
  11. http://www.springframework.org/schema/tx
  12. http://www.springframework.org/schema/tx/spring-tx.xsd">
  13. <context:component-scan base-package="cn.myforever"></context:component-scan>
  14. <aop:aspectj-autoproxy/>
  15. <!-- 1. 数据源对象: C3P0连接池 -->
  16. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
  17. <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
  18. <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
  19. <property name="user" value="root"></property>
  20. <property name="password" value="123456"></property>
  21. </bean>
  22. <!-- 2. JdbcTemplate工具类实例 -->
  23. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  24. <property name="dataSource" ref="dataSource"></property>
  25. </bean>
  26. <!-- 3.配置事务 -->
  27. <bean id="dataSourceTransactionManager"
  28. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  29. <property name="dataSource" ref="dataSource"></property>
  30. </bean>
  31. </beans>

5、DAO操作类MyDao.java

  1. @Repository
  2. public class MyDao {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. public void add(String name,String age) {
  6. String sql = "INSERT INTO user(username, age) VALUES(?,?);";
  7. int updateResult = jdbcTemplate.update(sql, name, age);
  8. System.out.println("updateResult:" + updateResult);
  9. }
  10. }

这里首先得在MySQL数据库中建立user表,就两个字段

6、Service层MyService.java

  1. @Service
  2. public class MyService {
  3. @Autowired
  4. private MyTransaction myTransaction;
  5. @Autowired
  6. private MyDao myDao;
  7. public void add() {
  8. try {
  9. myTransaction.begin();
  10. myDao.add("1", "1");
  11. int i =1/0;
  12. myDao.add("2", "2");
  13. myTransaction.commit();
  14. } catch (Exception e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. myTransaction.rollback();
  18. }
  19. }
  20. @ExtTransaction
  21. public void add1() {
  22. myDao.add("1", "1");
  23. int i =1/0;
  24. myDao.add("2", "2");
  25. }
  26. }

这里有两个方法,一个是没有加注解的方法,一个是使用了自定义注解的方法,两个方法实现的功能都一样,插入数据1,1后会遇到空指针异常,后面再插入2,2。若是调用第一个方法,我们是手动开启事务,然后执行成功后会提交事务,跑出异常后会回滚事务,第二个方法就直接@ExtTransaction注解。若是测试发现两个方法都不能插入数据,则表示自定义注解成功。

7、事务操作类MyTransaction.java

  1. @Component
  2. @Scope("prototype") //每个事物都是新的实例,解决线程安全问题
  3. public class MyTransaction {
  4. @Autowired
  5. private DataSourceTransactionManager dataSourceTransactionManager;
  6. //这里定义全局的,由于是多实例,所以这里没有关系
  7. private TransactionStatus transactionStatus;
  8. //开启事物
  9. public void begin() {
  10. System.out.println("开启事务");
  11. transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
  12. }
  13. //提交事务
  14. public void commit() {
  15. System.out.println("提交事务");
  16. if(transactionStatus!=null) {
  17. dataSourceTransactionManager.commit(transactionStatus);
  18. }
  19. }
  20. //回滚事务
  21. public void rollback() {
  22. System.out.println("回滚事务");
  23. if(transactionStatus!=null) {
  24. System.out.println("1");
  25. dataSourceTransactionManager.rollback(transactionStatus);
  26. }
  27. }
  28. }

@Component表示会自动实例化Bean放入Spring的上下文中;
@Scope(“prototype”) 表示多例,这样子就表示每次操作都实例化新的对象,不会有线程安全问题。

8、自定义注解ExtTransaction.java

  1. @Target({ElementType.METHOD})//只能用于方法
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface ExtTransaction {
  4. }

@Target({ElementType.METHOD})表示该注解只能用在方法上;
@Retention(RetentionPolicy.RUNTIME)表示该注解在运行期有效。

9、切面类TransactionAop.java

  1. @Component
  2. @Aspect
  3. public class TransactionAop {
  4. @Autowired
  5. private MyTransaction myTransaction;
  6. @AfterThrowing("execution(* cn.myforever.service.*.*(..))")
  7. public void afterThrowing() {
  8. System.out.println("异常通知");
  9. myTransaction.rollback();
  10. }
  11. @Around("execution(* cn.myforever.service.*.*(..))")
  12. public void around(ProceedingJoinPoint pjp) throws Throwable {
  13. System.out.println("坏绕通知前");
  14. //判断方法上是否有ExtTransaction注解
  15. MethodSignature ms = (MethodSignature) pjp.getSignature();
  16. System.out.println(ms.getName());
  17. ExtTransaction extTransaction = ms.getMethod().getDeclaredAnnotation(ExtTransaction.class);
  18. System.out.println(extTransaction);
  19. if(extTransaction!=null) {
  20. //开启事务
  21. System.out.println("----开启事务");
  22. myTransaction.begin();
  23. }
  24. pjp.proceed();
  25. System.out.println("环绕通知之后");
  26. if(extTransaction!=null) {
  27. //开启事务
  28. System.out.println("----提交事务");
  29. myTransaction.commit();
  30. }
  31. }
  32. }

这里自动注入了MyTransaction对象,因为是多例的,所以每次都会是新的对象,用了环绕通知和异常通知,环绕通知里,若是检测到方法上有@ExtTransaction注解,则会开启事务,在方法执行完后会提交事务,然后开启异常通知,有异常就回滚事务。若是需要了解自定义注解的使用,请参考博文Java注解详解

10、启动类AppMySpringTransaction.java

  1. public class AppMySpringTransaction {
  2. public static void main(String[] args) {
  3. ClassPathXmlApplicationContext app =new ClassPathXmlApplicationContext("spring.xml");
  4. MyService myService =(MyService) app.getBean("myService");
  5. //myService.add();
  6. myService.add1();
  7. }
  8. }

11、启动测试

启动测试后会输入如下日志

跟预期一模一样,先插入第一条记录,然后抛出异常,异常通知接收到后,回滚事务,然后我们去检查数据库,会发现一条记录也没有,表示事务控制成功。

结语

上面只是简单地实现了自定义事务,大家可以跟多的测试下不用事务的情况。还是蛮简单的,话说SpringAOP太强大了。:bowtie:

 494

啊!这个可能是世界上最丑的留言输入框功能~


当然,也是最丑的留言列表

有疑问发邮件到 : suibibk@qq.com 侵权立删
Copyright : 个人随笔   备案号 : 粤ICP备18099399号-2