zoukankan      html  css  js  c++  java
  • 基于注解的声明式事务的使用方法

    使用方法

    1. 添加相关jar包或依赖——数据源、数据库驱动、mysql或spring-jdbc等,这里以spring-jdbc为例;

    2. 数据库连接参数,一般单独写在properties或yaml配置文件中;

    3. 编写数据库访问层组件(dao)和业务逻辑层组件(service),且在service层需要事务管理的方法上加@Transactional注解;

    4. 在容器中注册数据源、数据库操作模板、事务管理器,以及步骤3中的组件;

    5. 在容器中开启基于事务的注解,即在容器配置类上加@EnableTransactionManagement注解。

    下面是一个使用spring-jdbc访问数据库的事务demo

    pom文件

    <!--spring数据库操作模板-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.16.RELEASE</version>
    </dependency>
    <!--数据源,由于只是测试事务的demo,用的c3p0-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.4</version>
    </dependency>
    <!--数据库驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.15</version>
    </dependency>

    数据库配置文件

    jdbc.url=jdbc:mysql://127.0.0.1:3306/test
    jdbc.username=root
    jdbc.password=123456
    jdbc.driverClass=com.mysql.jdbc.Driver

    容器配置类

    /**
     * 容器配置类
     * @EnableTransactionalManagement注解的意义是开启基于注解的事务,即使方法上的@Transactional注解生效
    */ @Configuration @ComponentScan(basePackages = {"cn.monolog.service", "cn.monolog.dao"}) @PropertySource(value = "classpath:/datasource.properties") @EnableTransactionManagement public class TxBeanConfig { //从配置文件中注入属性 @Value(value = "${jdbc.username}") private String username; @Value(value = "${jdbc.password}") private String password; @Value(value = "${jdbc.driverClass}") private String driverClass; @Value(value = "${jdbc.url}") private String url; //注册数据源 @Bean(name = "dataSource") public DataSource dataSource() throws PropertyVetoException { //创建数据源实例 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); //属性注入 comboPooledDataSource.setUser(this.username); comboPooledDataSource.setPassword(this.password); comboPooledDataSource.setDriverClass(this.driverClass); comboPooledDataSource.setJdbcUrl(this.url); //返回 return comboPooledDataSource; } //注册spring的数据库操作模板 @Bean(name = "jdbcTemplate") public JdbcTemplate jdbcTemplate() throws PropertyVetoException { //从容器中获取数据源 // --注:spring对配置类(即加了@Configuration的类)有特殊处理, // 当多次调用注册方法时,并不是每次都会创建新bean,而是会从容器中获取bean DataSource dataSource = this.dataSource(); //创建模板 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); //返回模板 return jdbcTemplate; } //注册事务管理器 @Bean(name = "transactionManager") public PlatformTransactionManager transactionManager() throws PropertyVetoException { //从容器中获取数据源 DataSource dataSource = this.dataSource(); //创建事务管理器实例 DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource); //返回 return dataSourceTransactionManager; } }

    数据库访问层组件

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    /**
     * 数据库访问层组件
     * 用于测试事务
     */
    @Repository
    public class VariationDao {
    
        //从容器中自动注入spring数据库操作模板
        @Autowired
        @Qualifier("jdbcTemplate")
        private JdbcTemplate jdbcTemplate;
    
        /**
         * 创建一条数据
         */
        public void create(String staffId, Integer alloted) {
            //编写sql语句
            String sql = "insert into c_variation_config (staff_id, alloted) values (?, ?)";
            //执行sql语句
            this.jdbcTemplate.update(sql, staffId, alloted);
        }
    }

    业务逻辑层组件

    import cn.monolog.dao.VariationDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * 业务逻辑层组件
     * 用于测试事务
     */
    @Service
    public class VariationService {
    
        //从容器中自动注入dao层组件
        @Autowired
        @Qualifier("variationDao")
        private VariationDao variationDao;
    
        /**
         * 创建一条数据
         */
        @Transactional(rollback =Exception.class)
        public void create(String staffId, Integer alloted) {
            //执行
            this.variationDao.create(staffId, alloted);
            //手动制造异常,由于加了事务,上一条语句产生的数据库更新会被回滚
            int i = 10 / 0;
        }
    }

    注意事项

    1. 在上述demo注册事务管理器时,需要获取容器中的另一个组件——数据源,这种从一个注册方法中需要获取另一个组件的情况,有两种处理方案

    ① 在参数列表中接收

    @Configuration
    @PropertySource(value = "classpath:/datasource.properties")
    @EnableTransactionManagement
    public class TxBeanConfig {
    
        //从配置文件中注入属性
        @Value(value = "${jdbc.username}")
        private String username;
        @Value(value = "${jdbc.password}")
        private String password;
        @Value(value = "${jdbc.driverClass}")
        private String driverClass;
        @Value(value = "${jdbc.url}")
        private String url;
    
        //注册数据源
        @Bean(name = "dataSource")
        public DataSource dataSource() throws PropertyVetoException {
            //创建数据源实例
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    
            //属性注入
            comboPooledDataSource.setUser(this.username);
            comboPooledDataSource.setPassword(this.password);
            comboPooledDataSource.setDriverClass(this.driverClass);
            comboPooledDataSource.setJdbcUrl(this.url);
    
            //返回
            return comboPooledDataSource;
        }
    
        /**
         * 注册事务管理器
         * @param dataSource 在参数列表中接收容器中的另一个组件
         * @return
         * @throws PropertyVetoException
         */
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager(DataSource dataSource) throws PropertyVetoException {
            //创建事务管理器实例
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
            //返回
            return  dataSourceTransactionManager;
        }
    }

    ② 在方法体中调用另一个注册方法

    @Configuration
    @ComponentScan(basePackages = {"cn.monolog.service", "cn.monolog.dao"})
    @PropertySource(value = "classpath:/datasource.properties")
    @EnableTransactionManagement
    public class TxBeanConfig {
    
        //从配置文件中注入属性
        @Value(value = "${jdbc.username}")
        private String username;
        @Value(value = "${jdbc.password}")
        private String password;
        @Value(value = "${jdbc.driverClass}")
        private String driverClass;
        @Value(value = "${jdbc.url}")
        private String url;
    
        //注册数据源
        @Bean(name = "dataSource")
        public DataSource dataSource() throws PropertyVetoException {
            //创建数据源实例
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    
            //属性注入
            comboPooledDataSource.setUser(this.username);
            comboPooledDataSource.setPassword(this.password);
            comboPooledDataSource.setDriverClass(this.driverClass);
            comboPooledDataSource.setJdbcUrl(this.url);
    
            //返回
            return comboPooledDataSource;
        }
        
        
        @Bean(name = "transactionManager")
        public PlatformTransactionManager transactionManager() throws PropertyVetoException {
            //获取数据源
            DataSource dataSource = this.dataSource();
            //创建事务管理器实例
            DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
            //返回
            return  dataSourceTransactionManager;
        }
    }

    对于这种方式,我们可能会产生一种疑问:this.dataSource方法中会new一个数据源实例,那么在注册事务管理器的同时,会不会又new了一个数据源实例,与单例模式违背?

    答案是不会。因为spring对于容器配置类(即加了@Configuration)的注册方法有特殊处理,多次调用并不会反复new实例,如果容器中已经存在实例,再次调用会从容器中直接取出。

    2. 如果service方法中加了try/catch语句,在捕获异常之后,之前的数据库更新操作并不会回滚,例如

    @Transactional(rollbackFor = Exception.class)
    public void create(String staffId, Integer alloted) {
        try {
            //执行--不回滚!
            this.variationDao.create(staffId, alloted);
            //手动制造异常
            int i = 10 / 0;
        } catch (Exception e) {
            //捕获异常后,数据库的更新操作并不会回滚
            e.printStackTrace();
        }
    }

    原因可能跟下面这段spring源码有关,但是暂时我没找到直接原因

    public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {
    /**
         * Handle a throwable, completing the transaction.
         * We may commit or roll back, depending on the configuration.
         * @param txInfo information about the current transaction
         * @param ex throwable encountered
         */
        protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
            if (txInfo != null && txInfo.getTransactionStatus() != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
                            "] after exception: " + ex);
                }
                if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
                    try {
                        txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
                    }
                    catch (TransactionSystemException ex2) {
                        logger.error("Application exception overridden by rollback exception", ex);
                        ex2.initApplicationException(ex);
                        throw ex2;
                    }
                    catch (RuntimeException | Error ex2) {
                        logger.error("Application exception overridden by rollback exception", ex);
                        throw ex2;
                    }
                }
                else {
                    // We don't roll back on this exception.
                    // Will still roll back if TransactionStatus.isRollbackOnly() is true.
                    try {
                        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                    }
                    catch (TransactionSystemException ex2) {
                        logger.error("Application exception overridden by commit exception", ex);
                        ex2.initApplicationException(ex);
                        throw ex2;
                    }
                    catch (RuntimeException | Error ex2) {
                        logger.error("Application exception overridden by commit exception", ex);
                        throw ex2;
                    }
                }
            }
        }
    
    }

    粉色粗体语句上面的注释,意思是在遇到这种异常时不回滚,但是如果TransactionStatus.isRollbackOnly==true,仍然会回滚;

    继续跟进源码,TransactionStatus是一个接口,isRollbackOnly方法在它的实现类中找到了:

    public abstract class AbstractTransactionStatus implements TransactionStatus {
    
        private boolean rollbackOnly = false;
    
        @Override
        public void setRollbackOnly() {
            this.rollbackOnly = true;
        }
    
        @Override
        public boolean isRollbackOnly() {
            return (isLocalRollbackOnly() || isGlobalRollbackOnly());
        }
    
        /**
         * Determine the rollback-only flag via checking this TransactionStatus.
         * <p>Will only return "true" if the application called {@code setRollbackOnly}
         * on this TransactionStatus object.
         */
        public boolean isLocalRollbackOnly() {
            return this.rollbackOnly;
        }
    
    }

    从上面截取的源码可以看出,rollbackOnly的默认值是false,但是这个TransactionStatus的实现类也提供了修改它的方法——setRollbackOnly。

    至此,我们得出一个结论,如果在service方法中使用了try/catch字句,只要在捕获异常的同时,把这个TransactionStatus.rollbackOnly设为ture,就可以实现回滚了,demo代码如下

        /**
         * 创建一条数据
         */
        @Transactional(rollbackFor = Exception.class)
        public void create(String staffId, Integer alloted) {
            try {
                //执行
                this.variationDao.create(staffId, alloted);
                //手动制造异常
                int i = 10 / 0;
            } catch (Exception e) {
                //捕获异常后,将Transaction.status设置为true,实现回滚
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                e.printStackTrace();
            }
        }

    3. 事务与切面的执行顺序问题

    https://www.cnblogs.com/dubhlinn/p/10735015.html一文中,通过分析事务源码,得出结论:事务也是springAOP的一种应用。如果容器中存在多个切面,就涉及到执行顺序问题,如果对事务和某些切面的执行顺序先后有要求,需要让切面实现Ordered接口(或加@Order注解)手动规定顺序,并且在事务的@EnableTransactional注解中设置order属性,让事务的order数字大于上述切面即可。项目中一个典型的案例——数据源切换。如果项目中使用了多数据源,而且利用springAOP在service层做数据源切换,如果service层的方法加了事务,这时就要考虑事务和切面的执行顺序问题了。因为事务也是一种切面,且事务管理器会加载固定的数据源,如果事务先执行,数据源就已经加载了,后面的切面无法再切换数据源。

    /**
     * 用于切换数据源的切面,应用于service层
     * 执行顺序为1,在事务之前,这样加载事务时,会读取切换后的数据源
     * created by YinHF on 2019-03-14
     */
    @Aspect
    @Component
    @Order(value = 1)
    public class DataSourceAOP {
    
        //使用test1库的接口
        private static final String TEST1 = "execution(* cn.monolog.biz.service.impl.test1.*(..))";
        //使用test2库的接口
        private static final String TEST2 = "execution(* cn.monolog.biz.service.impl.test2.*(..))";
        //使用test3库的接口
        private static final String TEST3 = "execution(* cn.monolog.biz.service.impl.test3.*(..))";
    
        /**
         * 切换数据源 → test1库
         */
        @Before(TEST1)
        public void switchToApp() {
            MultipleDataSource.setDataSourceKey(MultipleDataSource.TEST_1);
        }
    
        /**
         * 切换数据源 → test2库
         */
        @Before(TEST2)
        public void switchToConsole() {
            MultipleDataSource.setDataSourceKey(MultipleDataSource.TEST_2);
        }/**
         * 切换数据源 → test3库
         */
        @Before(TEST3)
        public void switchToDing() {
            MultipleDataSource.setDataSourceKey(MultipleDataSource.TEST_3);
        }
    }
    /**
     * 容器配置类
     */
    @Configuration
    @EnableTransactionManagement(order = 2)
    public class TxBeanConfig {
    }
  • 相关阅读:
    黑客是如何知道我们常用的密码的
    一个核物理学霸为何两次收到BlackHat的邀请
    透过大数据剖析漫画何去何从
    SJF(最短作业优先)
    RR(轮转调度算法)
    hrrf(最高响应比)
    fcfs
    Process 2(完成版)
    进程2
    进程1
  • 原文地址:https://www.cnblogs.com/dubhlinn/p/10731884.html
Copyright © 2011-2022 走看看