• 03-04-Spring声明式事务


    一、Spring JdbcTemplate

    在Spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为了给不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式尽可能的保留了灵活性,将数据库存取的工作量降到最低。

    1、应用实践

    1.1 导入相关pom依赖

     	
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-contextartifactId>
            <version>5.3.28version>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>8.0.19version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.16version>
        dependency>
        
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-ormartifactId>
            <version>5.3.28version>
        dependency>
        
       	<dependency>
           <groupId>junitgroupId>
           <artifactId>junitartifactId>
           <version>4.13version>
           <scope>testscope>
        dependency> 
    

    1.2 编写数据库信息的资源文件

    db.driver=com.mysql.cj.jdbc.Driver
    db.url=jdbc:mysql://localhost:3306/trs-db?useUnicode=true&amp;characterEncoding=utf8&amp;tinyInt1isBit=false&amp;useSSL=false&amp;serverTimezone=GMT%2B8&amp;allowMultiQueries=true&amp;zeroDateTimeBehavior=convertToNull
    db.username=root
    db.password=rootxq
    

    1.3 编写Spring的核心配置文件

    
    <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/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        
        <context:property-placeholder location="classpath:db.properties">context:property-placeholder>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driver}">property>
            <property name="url" value="${db.url}">property>
            <property name="username" value="${db.username}">property>
            <property name="password" value="${db.password}">property>
        bean>
    
        
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource">property>
        bean>
    beans>
    

    1.4 常用API的实践

    • 通用的增删改方法:jdbcTemplate.update(Spring sql,Object…args)
    • 通用的批处理增删改方法:jdbcTemplate.batchUpdate(String sql,List args)
    • 查询单个值:jdbcTemplate.queryForObject(String sql,Class clazz,Object…args)
    • 查询单个对象:jdbcTemplate.queryForObject(String sql,RowMapper rm,Object … args)
    • 查询多个对象:jdbcTemplate.query(String sql,RowMapper rm,Object … args)
      ClassPathXmlApplicationContext applicationContext = null;
        @Before
        public void before(){
            applicationContext = new ClassPathXmlApplicationContext("spring-ioc-jdbc.xml");
        }
    
        @Test
        public void queryForObject(){
            //获取jdbcTemplate对象
            JdbcTemplate jdbcTemplate = applicationContext.getBean(JdbcTemplate.class);
    
            // 查询单个值
            Long count = jdbcTemplate.queryForObject("select count(1) from users", Long.class);
            System.out.println("查询结果:" + count);
    
    
            //查询单个实体(实体属性与数据库字段名称一致)
            User user1 = jdbcTemplate.queryForObject("select id ,u_name as name,salary from users where id = 1", new BeanPropertyRowMapper<>(User.class));
            System.out.println(user1);
    
            //查询单个实体(实体属性与数据库字段名称不一致)
            User user2 = jdbcTemplate.queryForObject("select * from users where id = 2", (RowMapper<User>) (rs, rowNum) -> {
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("u_name"));
                u.setSalary(rs.getDouble("salary"));
                return u;
            });
            System.out.println(user2);
    
            //查询列表(实体属性与数据库字段名称一致)
            List<User> users = jdbcTemplate.query("select id ,u_name as name,salary from users", new BeanPropertyRowMapper<>(User.class));
            System.out.println(users);
    
            //查询列表(实体属性与数据库字段名称不一致)
            List<User> users2 = jdbcTemplate.query("select * from users", (RowMapper<User>) (rs, rowNum) -> {
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("u_name"));
                u.setSalary(rs.getDouble("salary"));
                return u;
            });
            System.out.println(users2);
        }
    

    二、声明式事务

    • 事务:是把一组业务当成一个业务来做,要么全都成功,要么全都失败,是保证业务操作完整性的一种数据库机制。

    • 事务的ACID四大特性:

      • 原子性:在一组业务所有的操作步骤,要么全都成功,要么全都失败;
      • 一致性:事务执行前后,要保证数据一致性,例如,两个人转账,转账前后他们的账户总额应该是不变的。
      • 隔离性:在多个事务并发执行时,每个事务之间都是独立的,互不影响。
      • 持久性:事务一旦执行成功,对数据的影响是不可逆的、永久性的。
    • 事务分为编程式事务声明式事务两种:

      • 编程式事务:
        • 指由用户自己通过代码来控制事务的处理过程,需要在代码中显式调用开启、提交、回滚事务等方法,可以使用TransactionTemplate来实现。
      • 声明式事务:
        • 在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
        • 其本质就是通过AOP的功能,对方法前后进行拦截,将事务处理的逻辑编织在拦截的方法中。也就是在目标方法开始之前加入一个事务,然后根据目标方法的执行情况来提交或者回滚事务。

    1、声明式事务的配置

    • Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员可以通过配置的方式进行事务管理。
    • Spring的事务管理器是PlatformTransactionManager,它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是不可缺少的。

    1.1 在配置文件中添加事务管理器

    
    <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/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        
        <context:property-placeholder location="classpath:db.properties">context:property-placeholder>
    
        
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${db.driver}">property>
            <property name="url" value="${db.url}">property>
            <property name="username" value="${db.username}">property>
            <property name="password" value="${db.password}">property>
        bean>
    
        
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource">property>
        bean>
    
        
        <tx:annotation-driven transaction-manager="transactionManager">tx:annotation-driven>
    
    beans>
    

    1.2 添加事务注解

    • 在需要进行事务管理的方法或者类上添加事务注解:@Transactional
    • 用法
      • 可以标记在类上面,表示当前类中的所有方法都将被事务管理;
      • 可以标记在某个方法上,表示当前方法被事务管理;
      • 可以在类和方法上面同时标记:
        • 如果类和方法上都存在@Transactional,则会以方法上面的为准。
        • 如果方法上面没有@Transactional,则会以类上面的为准。
    • 建议
      • @Transactional写在方法上面,控制粒度更细。
      • @Transactional写在业务逻辑层上,因为只有业务逻辑层才会有嵌套调用的情况。

    1.3 设置事务属性

    1.3.1 设置事务隔离级别
    • 设置事务隔离级别是为了解决并发事务过程中产生的一些问题,例如:脏读、幻读、不可重复读等等。

    • 常见的事务问题

      • 脏读:一个事务读取到了另一个事务没有提交的数据,称为脏读;
        • 例如:张三的工资到位了,总共1000元。他和他的媳妇儿同时收到了短信。事务A:表示他的媳妇儿从中银行卡取了200元,此时钱还没到手,并发事务B:张三立马去查询银行卡的余额信息,发现只有800元。而事务A在操作过程中出现了一些问题取钱失败,事务A回滚。实际上,银行卡的余额还是1000元,事务B读取到的就是一个脏数据。
      • 不可重复读:一个事务前后多次读取同一行记录,但读取到的内容却不一样,称为不可重复读。因为并发事务对这行记录做了变更,所以前后读到的内容不同。
        • 例如:张三的工资到位了,总共1000元。他和他媳妇儿同时收到了短信。事务A表示张三查询账户余额,事务B表示他媳妇儿查看账户并取钱。他们两同时查看账户信息,但是张三略微快一点点先看到余额是 1000元,他媳妇儿紧接着就取了200元,此时事务A还想再确认一下,就又看看了账户余额,发现是 800元。事务A前后两次看到的余额不一致。
      • 幻读:一个事务前后多次读取整张表的数据,发现读取前后的内容多了几行或者少了几行,就好像发生幻觉了一样,称为幻读。
        • 例如:张三公司要统计上个月给每个人发的工资情况,A事务表示人事部门统计薪资发放表,B事务表示财务部统计薪资发放表。两个部门同时统计,但是人事部门略微先统计完,统计结果为5000元,而财务部此时发现有个人漏发了,就补发了1000元,财务部拿到的结果是6000元。当事务A再次统计时,发现变成了6000元,前后统计到的结果不同。
    • 事务隔离级别:

      • DEFAULT:它是 PlatfromTransactionManager 默认的隔离级别。通常是数据库默认的隔离级别(REPEATABLE_READ)
        • @Transactional(isolation = Isolation.DEFAULT)
      • READ_UNCOMMITTED【读未提交】,最低的隔离级别。它允许一个事务读取另一个事务未提交的数据。会导致脏读、不可重复读和幻读的问题。
        • @Transactional(isolation = Isolation.READ_UNCOMMITTED)
      • READ_COMMITTED【读已提交】,保证一个事务只能读取其他事务已经提交的数据,不能读取到未提交的数据。避免了脏读,但是仍然会有不可重复读、幻读的问题。
        • @Transactional(isolation = Isolation.READ_COMMITTED)
      • REPEATABLE_READ【可重复读】,这种隔离级别保证一个事务在执行期间,其他事务不能对这个事务所操作的行记录做修改,从而保证这个事务多次读取到的内容是一致的。实际上:就是用了一个行锁将这行记录给锁起来了。
        • @Transactional(isolation = Isolation.REPEATABLE_READ)
      • SERIALIZABLE【串行化】,最高的隔离级别。它通过强制事务串行执行,避免了所有可能的并发问题,但也最大限度地降低了系统的并发能力。
        • 确保事务A可以多次从一个表中读取到相同的行,在事务A执行期间禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。实际上:就是用表锁把整个表都给锁了。
        • @Transactional(isolation = Isolation.SERIALIZABLE)
    1.3.2 设置事务传播行为

    事务的传播特性指的是当一个事务方法被另一个事务方法调用(事务方法嵌套)时,需要指定事务应该如何传播。

    	 /**
         * 转账
         */
        @Transactional
        public void transferMoney(){
            //扣款
            subMoney();
            //记录转账流水日志
            addLog();
            //加款
            addMoney();
        }
        
        @Transactional
        public void subMoney(){
            System.out.println("扣除转账人的银行账户1000元");
        }
        
        public void addLog(){
            System.out.println("记录转账的银行交易流水日志");
        }
    
        @Transactional
        public void addMoney(){
            System.out.println("收款人银行账户加款1000元");
        }
    

    此时,所有的方法都有事务,每个事务方法执行时事务应该怎么来控制?

    • 事务的7种事务传播行为:
      • REQUIRED(默认)
        • 如果外部不存在事务,就开启一个自己的新事务。如果外部存在事务,就融合到外部的事务中(以外部事务为准)。
        • @Transactional(propagation = Propagation.REQUIRED)
        • 适用于增删改查操作。
      • SUPPORTS
        • 如果外部不存在事务,就不开启事务。如果外部存在事务,就融合到外部的事务中。
        • @Transactional(propagation = Propagation.SUPPORTS)
        • 适用于查询操作。
      • REQUIRES_NEW
        • 如果外部不存在事务,就开启一个自己的新事务。如果外部存在事务,就将外部事务挂起(外部事务暂不执行),创建一个自己的新事务去执行。
        • @Transactional(propagation = Propagation.REQUIRES_NEW)
        • 适用于内部事务,例如:记录日志时,无论其他方法执行成功失败,都需要记录日志,而不是回滚,这时候,记录日志的方法就需要玩自己的事务,而不跟着外部事务走。
        • 注意:当涉及到事务挂起时,要求外部方法和内部方法必须存在不同的类中;
      • NOT_SUPPORTED
        • 如果外部不存在事务,也不会开启自己的新事务。如果外部存在事务,则挂起外部事务。
        • @Transactional(propagation = Propagation.NOT_SUPPORTED)
      • NEVER
        • 如果外部不存在事务,也不会开启自己的新事务。如果外部存在事务,则抛出异常。
        • @Transactional(propagation = Propagation.NEVER)
      • MANDATORY
        • 如果外部不存在事务,则抛出异常。如果外部存在事务,就融合到外部的事务中。
        • @Transactional(propagation = Propagation.MANDATORY)
      • NESTED
        • 如果外部不存在事务,则执行与REQUIRED类似的操作。如果外部存在事务,就融合到外部事务中,以嵌套事务的方式执行。
        • @Transactional(propagation = Propagation.NESTED)
    1.3.3 设置超时属性
    • 指定事务等待的最长时间(秒)
    • 当前事务访问数据时,有可能访问的数据被别的事务进行了加锁的处理,那么此时事务就
      必须等待,如果等待时间过长给用户造成的体验感差。
    • @Transactional(timeout = 5)
    1.3.4 设置事务只读
    • 一般事务方法中只有查询操作时才将事务设置为只读;
    • 当将事务设置只读 就必须要要求你的业务方法中没有增删改的操作。由于只
      读事务不存在数据的修改,数据库将会为只读事务提供一些优化手段。
    • 默认值:readonly = false
    • @Transactional(readOnly = true)
    1.3.4 设置事务异常回滚
    • 设置当前事务出现的那些异常就进行回滚或者提交。
    • 默认对于RuntimeException 及其子类 采用的是回滚的策略。
    • 默认对于Exception 及其子类 采用的是提交的策略。
    • rollbackFor:设置发生异常时需要回滚的异常类型
      • @Transactional(rollbackFor = NullPointerException.class)
    • noRollbackkFor:设置发生异常时不需要回滚的异常类型
      • @Transactional(noRollbackkFor = NullPointerException.class)

    2、基于XML的事务配置

    
    <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/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        
        
        
        <aop:config>
            <aop:pointcut id="transactionCutPoint" expression="execution(* org.example.mvc.impl.*.*(..))"/>
        aop:config>
        
        <tx:advice>
            <tx:attributes>
                
                <tx:method name="add*"/>
                <tx:method name="update*" timeout="5" isolation="REPEATABLE_READ"/>
                <tx:method name="query*" propagation="SUPPORTS"/>
                <tx:method name="delete*" read-only="true"/>
            tx:attributes>
        tx:advice>
    
    beans>
    
  • 相关阅读:
    在量化交易过程中,散户可以这样做
    Ctfshow web入门 XSS篇 web316-web333 详细题解 全
    【机器学习】梯度下降预测波士顿房价
    1.集群环境搭建
    LeetCode 899. 有序队列
    Hadoop学习
    中医自学平台---前端网站
    XXXX项目管理目标(某项目实施后基于软件工程的总结)
    python基础运用例子
    魔搭 modelscope
  • 原文地址:https://blog.csdn.net/XQ_898878888/article/details/139428916