手写源码(一):自己实现Spring事务

栏目: Java · 发布时间: 5年前

内容简介:Spring事务分为声明式事务(注解或包扫描)和编程式(在代码里提交或回滚)事务,声明式事务就是在编程式事务的基础上加上AOP计数进行包装这个工程为了实验事务的回滚,使用用了数据库,使用了jdbc模板连接数据库 ,数据库连接池配置再RootConfig里我导入的Maven依赖如下

Spring事务分为声明式事务(注解或包扫描)和编程式(在代码里提交或回滚)事务,声明式事务就是在编程式事务的基础上加上AOP计数进行包装

这个工程为了实验事务的回滚,使用用了数据库,使用了jdbc模板连接数据库 ,数据库连接池配置再RootConfig里

我导入的Maven依赖如下

<dependencies>
        <!-- 引入Spring-AOP等相关Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.20.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>
        <!--mysql连接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

复制代码

配置类如下,用于代替有些过时的XML配置Spring

@Configuration
@ComponentScan(basePackages = {"com.libi"})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sms?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
复制代码

需要加入事务的方法如下userDao是会操作数据的,在中间的间隔会抛出异常

@Service
 public class UserServiceImpl implements UserService {
     @Autowired
     private UserDao userDao;
     public void add() {
         userDao.add("test001","1233321");
         System.out.println("中间的间隔,且出现异常");
         int i = 1 / 0;
         userDao.add("test002","135365987");
     }
 }
复制代码

这时只会插入test001的语句,test002不会插入成功。

编程式事务

这时我们封装一个事务工具

@Component
@Scope("prototype")
public class TransactionUtils {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    private TransactionStatus status;
    
    /** 开启事务*/
    public TransactionStatus begin() {
        //使用默认的传播级别
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        return transaction;
    }

    /** 提交事务 需要传入这个事务状态*/
    public void commit() {
        dataSourceTransactionManager.commit(status);
    }

    /**回滚事务 需要传入这个事务状态*/
    public void rollBack() {
        //获取当前事务,如果有,就回滚
        if (status != null) {
            dataSourceTransactionManager.rollback(status);
        }
    }
}
复制代码

再这样使用,修改add方法

public void add() {
        TransactionStatus begin = null;
        try {
            begin = transactionUtils.begin();
            userDao.add("test001", "1233321");
            System.out.println("中间的间隔,且出现异常");
            int i = 1 / 0;
            userDao.add("test002", "135365987");
            transactionUtils.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transactionUtils.rollBack();
        }
    }
复制代码

声明式事务

我们使用AOP编程把刚刚的事务 工具 封装一下

@Component
@Aspect
public class AopTransaction {
    @Autowired
    private TransactionUtils transactionUtils;

    @Around("execution(* com.libi.service.UserService.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("开启事务");
        proceedingJoinPoint.proceed();
        System.out.println("提交事务");
        transactionUtils.commit();
    }

    @AfterThrowing("execution(* com.libi.service.UserService.add(..))")
    public void afterThrowing() {
        System.out.println("回滚事务");
        //获取当前事务,直接回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    
}
复制代码

然后清空原来的方法里所有的try代码,让他回到最初的状态( 不能捕获异常,否者出现异常后不能被异常通知捕获到,导致事务不生效

注解式事务

在Spring里已经帮我们实现类注解事务,需要在配置类里添加下面的注解来开启注解事务的支持

@EnableTransactionManagement
复制代码

然后 注释掉我们上次的AOP注解 ,使用 @Transactional(rollbackFor = Exception.class) 的注解开启这个方法的事务, rollbackFor 标识需要回滚的异常类,整个方法如下

@Transactional(rollbackFor = Exception.class)
    public void add() {
        userDao.add("test001", "1233321");
        System.out.println("中间的间隔,且出现异常");
        int i = 1 / 0;
        userDao.add("test002", "135365987");
    }
复制代码

这样也可以实现这个方法的事务。 当然,这个方法里也不能捕获异常,这样仍然会导致无法触发异常通知而导致事务无效

我们就以这种效果作为模板手写事务的框架

具体步骤

  • 定义注解
/**
 * @author libi
 * 自己实现的事务注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {
    
}
复制代码
  • 封装手动事务(使用原来的TransactionUtils类)
  • 使用AOP扫描规定包下的注解
    • 在AOP上封装找到注解并且加上注解的操作
@Component
@Aspect
public class AopAnnotationTransaction {
    @Autowired
    private TransactionUtils transactionUtils;
    /**这边规定扫描service下的所有方法*/
    @Around("execution(* com.libi.service.*.*(..))")
        //获取方法上的注解,这里把获取注解的方法单独提出来了
        ExtTransaction extTransaction = getExtTransaction(proceedingJoinPoint);

        TransactionStatus status = null;
        if (extTransaction != null) {
            //若果有事务,开启事务
            System.out.println("开启事务");
            status = transactionUtils.begin();
        }
        //调用代理目标方法
        proceedingJoinPoint.proceed();
        if (status != null) {
            //提交事务
            System.out.println("提交事务");
            transactionUtils.commit();
        }
    }

    /**事务的异常通知*/
    @AfterThrowing("execution(* com.libi.service.*.*.*(..))")
    public void afterThrowing() {
        System.out.println("回滚事务");
       transactionUtils.rollBack();
    }

    /**获取方法上的注解*/
    private ExtTransaction getExtTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
        //获取代理对象的方法
        String methodName = proceedingJoinPoint.getSignature().getName();
        Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
        Class[] parameterTypes = ((MethodSignature) (proceedingJoinPoint.getSignature())).getParameterTypes();
        Method targetMethod = targetClass.getMethod(methodName, parameterTypes);
        //获取方法上的注解
        return targetMethod.getAnnotation(ExtTransaction.class);
    }
}
复制代码

还要注意的是,TransactionUtils类仍然需要时多例的,不然会出现线程安全问题

事务传播行为

  • 什么是传播行为(Propagation) :事务的传播行为产生在调用事务中,也就是说当小个事务嵌套在大事务里时,会发生怎样的行为
  • 传播行为的种类
    • PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。( 如果大的方法有事务,那么需要事务的小方法就加入到这个事务里去,如果大方法没有事务,就创建事务
    • PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。//( 如果外层方法没有事务,就会以非事务进行执行。这样相当于默认没有事务
    • PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
    • PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起( 互不影响,运行到小事务时暂停大事务 )。
    • PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • --- 如果当前有事务,就是以非事务进行执行
    • PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Elements of Programming

Elements of Programming

Alexander A. Stepanov、Paul McJones / Addison-Wesley Professional / 2009-6-19 / USD 39.99

Elements of Programming provides a different understanding of programming than is presented elsewhere. Its major premise is that practical programming, like other areas of science and engineering, mus......一起来看看 《Elements of Programming》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具