• Spring 事务管理的使用


    Spring提供了2种事务管理

    • 编程式的
    • 声明式的(重点):包括xml方式、注解方式(推荐)

    基于转账的demo

    dao层

    新建包com.chy.dao,包下新建接口AccountDao、实现类AccountDaoImpl:

    public interface AccountDao {
        //查询用户账户上的余额
        public double queryMoney(int id);
        //减少用户账户上的余额
        public void reduceMoney(int id, double amount);
        //增加用户账户上的余额
        public void addMoney(int id, double amount);
    }
    @Repository
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        @Override
        public double queryMoney(int id) {
            String sql = "select money from account_tb where id=?";
            JdbcTemplate jdbcTemplate = super.getJdbcTemplate();
            double money = jdbcTemplate.queryForObject(sql, double.class,id);
            return money;
        }
    
        @Override
        public void reduceMoney(int id, double account) {
            double money = queryMoney(id);
            money -= account;
            if (money>=0){
                String sql = "update account_tb set money=? where id=?";
                JdbcTemplate jdbcTemplate = super.getJdbcTemplate();
                jdbcTemplate.update(sql, money, id);
            }
            //此处省略余额不足时的处理
        }
    
        @Override
        public void addMoney(int id, double account) {
            double money = queryMoney(id);
            money += account;
            String sql = "update account_tb set money=? where id=?";
            JdbcTemplate jdbcTemplate = super.getJdbcTemplate();
            jdbcTemplate.update(sql, money, id);
        }
    }

    service层

    新建包com.chy.service,包下新建接口TransferService、实现类TransferServiceImpl:

    public interface TransferService {
        public void transfer(int from,int to,double account);
    }
    @Service
    public class TransferServiceImpl implements TransferService {
        private AccountDao accountDao;
        
        @Autowired
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public void transfer(int from, int to, double account) {
            accountDao.reduceMoney(from,account);
            // System.out.println(1/0);
            accountDao.addMoney(to,account);
        }
    }

    数据库连接信息

    src下新建db.properties:

    #mysql数据源配置
    url=jdbc:mysql://localhost:3306/my_db?serverTimezone=GMT
    user=chy
    password=abcd

    xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 引入数据库配置信息 -->
        <context:property-placeholder location="db.properties" />
    
        <!-- 使用包扫描-->
        <context:component-scan base-package="com.chy.dao,com.chy.service" />
    
        <!-- 配置数据源,此处使用jdbc数据源 -->
        <bean name="jdbcDataSource" class="com.mysql.cj.jdbc.MysqlDataSource">
            <property name="url" value="${url}" />
            <property name="user" value="${user}" />
            <property name="password" value="${password}" />
        </bean>
    
        <!-- 配置AccountDaoImpl,注入数据源 -->
        <bean name="accountDaoImpl" class="com.chy.dao.AccountDaoImpl">
            <!--注入数据源-->
            <property name="dataSource" ref="jdbcDataSource" />
        </bean>
    </beans>

    测试

    新建包com.chy.test,包下新建主类Test:

            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml");
            TransferServiceImpl transferServiceImpl = applicationContext.getBean("transferServiceImpl", TransferServiceImpl.class);
            transferServiceImpl.transfer(1,2,1000);

    以上是未使用事务管理的,将service层的这句代码取消注释,会出现钱转丢的情况(对方收不到钱)。

    // System.out.println(1/0);

    编程式事务管理

    编程式事务管理,顾名思义需要自己写代码。

    自己写代码很麻烦,spring把代码封装在了TransactionTemplate类中,方便了很多。

    (1)事务需要添加到业务层(service),修改TransferServiceImpl类如下:

    @Service
    public class TransferServiceImpl implements TransferService {
        private AccountDao accountDao;
        private TransactionTemplate transactionTemplate;
    
        @Autowired
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Autowired
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
    
        @Override
        public void transfer(int from, int to, double account) {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    accountDao.reduceMoney(from, account);
                    // System.out.println(1 / 0);
                    accountDao.addMoney(to, account);
                }
            });
        }
    }
    • 注入事务管理模板TransactionTemplate(成员变量+setter方法)。
    • 在处理业务的方法中写:
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {  });

    使用匿名内部类传入一个TransactionCallbackWithoutResult接口类型的变量,只需实现一个方法:把原来处理业务的代理都放到这个方法中。

    
    
    

    (2)在xml中配置事务管理、事务管理模板

        <!-- 配置事务管理器,此处使用JDBC的事务管理器-->
        <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="jdbcDataSource" />
        </bean>
    
        <!-- 配置事务管理模板-->
        <bean class="org.springframework.transaction.support.TransactionTemplate">
            <!-- 注入事务管理器 -->
            <property name="transactionManager" ref="transactionManager" />
        </bean>

    把下面这句代码取消注释,运行,不会出现钱转丢的情况(执行失败,自动回滚):

    // System.out.println(1/0);

    声明式事务管理(此节必看)

    声明式事务管理,不管是基于xml,还是基于注解,都有以下3个点要注意:

    • 底层是使用AOP实现的(在Spring中使用AspectJ),需要把AspectJ相关的jar包添加进来。
    • 因为我们的service层一般写成接口——实现类的形式,既然实现了接口,基于动态代理的AspectJ代理的自然是接口,所以只能使用接口来声明:
    TransferService transferServiceImpl = applicationContext.getBean("transferServiceImpl", TransferService.class);

    红色标出的2处只能使用接口。

    • 在配置xml时有一些新手必遇到的坑,如果在配置xml文件时遇到了问题,可滑到最后面查看我写的解决方式。

    基于xml的声明式事务管理

    xml配置:

        <!-- 配置事务管理器,此处使用JDBC的事务管理器-->
        <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="jdbcDataSource" />
        </bean>
    
        <!-- 配置增强-->
        <!-- 此处只能用id,不能用name。transaction-manager指定要引用的事务管理器 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 配置要添加事务的方法,直接写方法名即可。可以有多个method元素,可以使用通配符* -->
                <tx:method name="transfer" />
            </tx:attributes>
        </tx:advice>
    
        <!-- 配置aop-->
        <aop:config>
            <!-- 配置切入点-->
            <aop:pointcut id="transferPointCut" expression="execution(* com.chy.service.TransferServiceImpl.transfer(..))"/>
            <!--指定要引用的<tx:advice>,指定切入点。切入点可以point-ref引用,也可以pointcut现配。可以有多个advisor元素。 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="transferPointCut"/>
        </aop:config>

    说明

    <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" read-only="false" />
    • propagation 指定事务的传播行为,默认为REQUIRED

    • isolation 指定事务的隔离级别

    • read-only 是否只读,读=>查询,写=>增删改。默认为false——读写。

    • timeout  事务的超时时间,-1表示永不超时。

    这4个属性一般都不用设置,使用默认值即可。当然,只查的时候,可以设置read-only="true"。

    常见的3种配置方式:

                <tx:method name="transfer" />
    <tx:method name="*" />
    <tx:method name="save*" /> <tx:method name="find*" read-only="false"/> <tx:method name="update*" /> <tx:method name="delete*" />
    • 指定具体的方法名
    • 用*指定所有方法
    • 指定以特定字符串开头的方法(我们写的dao层方法一般都以这些词开头)

    因为要配合aop使用,筛选范围是切入点指定的方法,不是项目中所有的方法。

    比如

    <tx:method name="save*" />
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.chy.service.TransferServiceImpl.*(..))" />

    切入点是TransferServiceImpl类中的所有方法,是在TransferServiceImpl类所有方法中找到以save开头的所有方法,给其添加事务。


    基于注解的声明式事务管理(推荐)

    (1)xml配置

        <!-- 配置事务管理器,此处使用JDBC的事务管理器-->
        <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="jdbcDataSource" />
        </bean>
    
        <!--启用事务管理的注解,并指定要使用的事务管理器 -->
        <tx:annotation-driven transaction-manager="transactionManager" />

    (2)使用@Transactional标注

    @Service
    // @Transactional
    public class TransferServiceImpl implements TransferService {
        private AccountDao accountDao;
    
        @Autowired
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        @Transactional
        public void transfer(int from, int to, double account) {
            accountDao.reduceMoney(from, account);
            // System.out.println(1 / 0);
            accountDao.addMoney(to, account);
        }
    }

    可以标注在业务层的实现类上,也可以标注在处理业务的方法上。

    标注在类上,会给这个所有的业务方法都添加事务;标注在业务方法上,则只给这个方法添加事务。

    同样可以设置传播行为、隔离级别、是否只读:

    @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false)

    基于注解的声明式事务管理是最简单的,推荐使用。


    声明式事务管理,配置xml时的坑

    • <tx:advice>、<tx:annotation-driven>的代码提示不对、配置没错但仍然显式红色

    原因:IDEA自动引入的约束不对。

    IDEA会自动引入所需的约束,但spring事务管理所需的约束,IDEA引入的不对。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/cache"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">

    看我红色标出的部分(需拖动滚动条查看最右边),这是IDEA自动引入的tx的约束,这是旧版本的约束,已经无效了。

    新版本的约束:

    xmlns:tx="http://www.springframework.org/schema/tx"

    xsi里对应的部分也要修改:

    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd

    可以在错误约束的基础上改,也可以添加。

    如果没有修改xsi中对应的部分,会报下面的错:

    通配符的匹配很全面, 但无法找到元素 'tx:advice' 的声明。
    通配符的匹配很全面, 但无法找到元素 'tx:annotation-driven' 的声明。

    上面提供的约束是目前版本的约束,以后可能会变。最好在官方文档中找,有2种方式:

    (1)http://www.springframework.org/schema/tx/spring-tx.xsd     可修改此url查看其它约束

    (2)如果下载了spring的压缩包,可在schema文件夹下查看约束。

    spring压缩包的下载可参考:https://www.cnblogs.com/chy18883701161/p/11108542.html

    • 配置了事务的注解驱动<tx:annotation-driven transaction-manager="transactionManager" /> ,但一次都没有标注@Transactional,即一次都没有使用事务,会报错:
    通配符的匹配很全面, 但无法找到元素 'tx:annotation-driven' 的声明。
  • 相关阅读:
    再见OI,AFO
    时间复杂度
    NOIP真题:矩阵取数问题
    [USACO12FEB]附近的牛Nearby Cows
    合唱队
    子串
    ZJOI2010基站选址
    分治FFT学习笔记
    「HAOI2018」染色
    「SDOI2015」序列统计
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/12239011.html
Copyright © 2020-2023  润新知