手写源码(一):本身实现Spring事务

手写Spring事务

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

<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
mysql

@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是会操做数据的,在中间的间隔会抛出异常spring

@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不会插入成功。
sql

编程式事务

这时咱们封装一个事务工具数据库

@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代码,让他回到最初的状态(不能捕获异常,否者出现异常后不能被异常通知捕获到,致使事务不生效bash

注解式事务

在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--以非事务方式执行,若是当前存在事务,则抛出异常。
相关文章
相关标签/搜索