• spring声明式事务


    11.声明式事务

    11.1 事务回顾

    • 把一组业务当做一个业务来坐,要么都成功,要么都失败!
    • 事物在项目开发中的重要性不言而喻,关系到数据的一致性文件
    • 确保完整性和一致性

    事务的ACID原则

    • 原子性(Atomicity):一个事务是一个不可分割的工作单位,事务中包括的动作要么都做要么都不做。
    • 一致性(Consistency):事务必须保证数据库从一个一致性状态变到另一个一致性状态,一致性和原子性是密切相关的,即数据最终结果一致,即对于资源和数据来说,事务提交是一个状态,回滚是一个状态
    • 隔离性(Isolation):一个事务的执行不能被其它事务干扰,即一个事务内部的操作及使用的数据对并发的其它事务是隔离的,并发执行的各个事务之间不能互相打扰,即防止事务损坏。
    • 持久性(Durability):持久性也称为永久性,指一个事务一旦提交,它对数据库中数据的改变就是永久性的,被持久化到存储器中,后面的其它操作和故障都不应该对其有任何影响。
    • 具体请翻看jdbc了解及使用或者是Mysql基础6-事务事物方面的内容

    • 事务允许我们将几个或一组数据操作组合成一个原子性的业务。如果业务中的所有的操作都执行成功,那自然万事大吉。但如果业务中的任何一个操作失败,那么业务中所有的操作都会被回滚,已经执行成功操作也会被完全清除干净,就好像什么事都没有发生一样。

    11.2 Spring 事务

    • Spring 对事务提供了很好的支持。Spring 借助 IoC 容器强大的配置能力,为事务提供了丰富的功能支持。

    • Spring 支持以下 2 种事务管理方式。

    事务管理方式说明
    编程式事务管理编程式事务管理是通过编写代码实现的事务管理。 这种方式能够在代码中精确地定义事务的边界,我们可以根据需求规定事务从哪里开始,到哪里结束。
    声明式事务管理Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

    选择编程式事务还是声明式事务,很大程度上就是在控制权细粒度和易用性之间进行权衡。

    • 编程式对事物控制的细粒度更高,我们能够精确的控制事务的边界,事务的开始和结束完全取决于我们的需求,但这种方式存在一个致命的缺点,那就是事务规则与业务代码耦合度高,难以维护,因此我们很少使用这种方式对事务进行管理。
    • 声明式事务易用性更高,对业务代码没有侵入性,耦合度低,易于维护,因此这种方式也是我们最常用的事务管理方式。

    11.3 编程式事务管理

    • MyBatis 的 SqlSession 提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession 或映射器。也就是说,Spring 总是为你处理了事务。

    • 你不能在 Spring 管理的 SqlSession 上调用 SqlSession.commit()SqlSession.rollback()SqlSession.close() 方法。如果这样做了,就会抛出 UnsupportedOperationException 异常。在使用注入的映射器时,这些方法也不会暴露出来。

    • 无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。

    底层原理提醒:这就是Innodb的底层原理 ,除了死锁以外,事务都不会自动回滚

    这里数据源没有配置事务提交,默认使用mysql的,InnoDB默认就是事务自动提交的

    • 编程式地控制事务:
    public class UserService {
      private final PlatformTransactionManager transactionManager;
      public UserService(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
      }
      public void createUser() {
        TransactionStatus txStatus =
            transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
          userMapper.insertUser(user);
        } catch (Exception e) {
          transactionManager.rollback(txStatus);
          throw e;
        }
        transactionManager.commit(txStatus);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    事务管理器:Spring 并不会直接管理事务,而是通过事务管理器对事务进行管理的。

    • 在 Spring 中提供了一个 org.springframework.transaction.PlatformTransactionManager 接口,这个接口被称为 Spring 的事务管理器,其源码如下
    public interface PlatformTransactionManager extends TransactionManager {
        TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
        void commit(TransactionStatus status) throws TransactionException;
        void rollback(TransactionStatus status) throws TransactionException;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 该接口中各方法说明如下:
    名称说明
    TransactionStatus getTransaction(TransactionDefinition definition)用于获取事务的状态信息
    void commit(TransactionStatus status)用于提交事务
    void rollback(TransactionStatus status)用于回滚事务
    • Spring 为不同的持久化框架或平台(例如 JDBC、Hibernate、JPA 以及 JTA 等)提供了不同的 PlatformTransactionManager 接口实现,这些实现类被称为事务管理器实现。
    实现类说明
    org.springframework.jdbc.datasource.DataSourceTransactionManager使用 Spring JDBC 或 iBatis 进行持久化数据时使用。
    org.springframework.orm.hibernate3.HibernateTransactionManager使用 Hibernate 3.0 及以上版本进行持久化数据时使用。
    org.springframework.orm.jpa.JpaTransactionManager使用 JPA 进行持久化时使用。
    org.springframework.jdo.JdoTransactionManager当持久化机制是 Jdo 时使用。
    org.springframework.transaction.jta.JtaTransactionManager使用 JTA 来实现事务管理,在一个事务跨越多个不同的资源(即分布式事务)使用该实现。

    TransactionDefinition 接口:Spring 将 XML 配置中的事务信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作。

    • TransactionDefinition源码
    public interface TransactionDefinition {
        int getPropagationBehavior();
        int getIsolationLevel();
        String getName();
        int getTimeout();
        boolean isReadOnly();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    方法说明
    String getName()获取事务的名称
    int getIsolationLevel()获取事务的隔离级别
    int getPropagationBehavior()获取事务的传播行为
    int getTimeout()获取事务的超时时间
    boolean isReadOnly()获取事务是否只读

    事务的隔离级别:事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。此时就有可能导致脏读、幻读以及不可重复读等问题的出现。

    在理想情况下,事务之间是完全隔离的,这自然不会出现上述问题。但完全的事务隔离serializable会导致性能问题,而且并不是所有的应用都需要事务的完全隔离,因此有时应用程序在事务隔离上也有一定的灵活性。

    • Spring 中提供了以下隔离级别
    方法说明
    ISOLATION_DEFAULT使用后端数据库默认的隔离级别
    ISOLATION_READ_UNCOMMITTED允许读取尚未提交的更改,可能导致脏读、幻读和不可重复读
    ISOLATION_READ_COMMITTEDOracle 默认级别,允许读取已提交的并发事务,防止脏读,可能出现幻读和不可重复读
    ISOLATION_REPEATABLE_READMySQL 默认级别,多次读取相同字段的结果是一致的,防止脏读和不可重复读,可能出现幻读
    ISOLATION_SERIALIZABLE完全服从 ACID 的隔离级别,防止脏读、不可重复读和幻读

    事务的传播行为:事务传播行为(propagation behavior)指的是,当一个事务方法被另一个事务方法调用时,这个事务方法应该如何运行。例如,事务方法 A 在调用事务方法 B 时,B 方法是继续在调用者 A 方法的事务中运行呢,还是为自己开启一个新事务运行,这就是由事务方法 B 的事务传播行为决定的。

    • Spring 提供了以下 7 种不同的事务传播行为。
    名称说明
    PROPAGATION_MANDATORY支持当前事务,如果不存在当前事务,则引发异常。
    PROPAGATION_NESTED如果当前事务存在,则在嵌套事务中执行。
    PROPAGATION_NEVER不支持当前事务,如果当前事务存在,则引发异常。
    PROPAGATION_NOT_SUPPORTED不支持当前事务,始终以非事务方式执行。
    PROPAGATION_REQUIRED默认传播行为,如果存在当前事务,则当前方法就在当前事务中运行,如果不存在,则创建一个新的事务,并在这个新建的事务中运行。
    PROPAGATION_REQUIRES_NEW创建新事务,如果已经存在事务则暂停当前事务。
    PROPAGATION_SUPPORTS支持当前事务,如果不存在事务,则以非事务方式执行。

    TransactionStatus 接口:控制事务的执行、查询事务的状态

    public interface TransactionStatus extends SavepointManager {
        boolean isNewTransaction();
        boolean hasSavepoint();
        void setRollbackOnly();
        boolean isRollbackOnly();
        boolean isCompleted();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 该接口中各方法说明如下。
    名称说明
    boolean hasSavepoint()获取是否存在保存点
    boolean isCompleted()获取事务是否完成
    boolean isNewTransaction()获取是否是新事务
    boolean isRollbackOnly()获取事务是否回滚
    void setRollbackOnly()设置事务回滚

    11.4 声明式事务管理

    • Spring 声明式事务管理是通过 AOP 实现的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建(或加入)一个事务,在执行完目标方法后,根据执行情况提交或者回滚事务。

    声明式事务最大的优点就是对业务代码的侵入性低,可以将业务代码和事务管理代码很好地进行解耦。

    Spring 实现声明式事务管理主要有 2 种方式:

    • 基于 XML 方式的声明式事务管理。
    • 通过 Annotation 注解方式的事务管理。
    11.4.1 基于 XML 方式的声明式事务管理
    • 引入 tx 命名空间,简化 Spring 中的声明式事务的配置(在aop中作为一个通知,被织入到切入点中)
    <?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:aop="http://www.springframework.org/schema/aop"
           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
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd">
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 举例理解:

    在一个业务中先将用户新增,之后进行删除,后续在查询数据,但故意将删除操作报错,结果是没加事务时,数据被新增进去,删除因报错没有执行,最后查出来的是多了一条的数据。

    • 实体类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • dao接口、实现类和mapper.xml
    public interface UserMapper {
    
        List<User> getUserList();
    
        int insertUser(User user);
    
        int deleteUser(@Param("id") int id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 注意:此时删除表记录时没有这张表,会报错
    <mapper namespace="com.zk.dao.UserMapper">
    
        <!--返回集合对象类型也是用resultType指明集合泛型-->
        <select id="getUserList" resultType="user">
            select * from school.user
        </select>
        <insert id="insertUser" parameterType="user">
            insert into school.user (id,name,pwd) values (#{id},#{name},#{pwd});
        </insert>
        <delete id="deleteUser">
            delete from school.users where id = #{id}
        </delete>
    </mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
    
        @Override
        public List<User> getUserList() {
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            return userList;
        }
    
        @Override
        public int insertUser(User user) {
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            int result = mapper.insertUser(user);
            return result;
        }
    
        @Override
        public int deleteUser(int id) {
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            int result = mapper.deleteUser(id);
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • service调用
    public class UserService {
    
        private UserMapper userMapper;
    
    
        public void setUserMapper(UserMapper userMapper) {
            this.userMapper = userMapper;
        }
    
        public List<User> getUserList(){
            userMapper.insertUser(new User(14,"李华","123123"));
            userMapper.deleteUser(14);
            List<User> userList = userMapper.getUserList();
            return userList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • db.properties
    • mybatis配置文件mybatis-config.xml
    • spring整合mybatis配置文件spring-mybatis-congfig.xml
    • spring配置文件applicationContext.xml
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/school?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    #导入的properties文件中不要将user定为username,否者会获取系统用户名
    usernames=root
    password=123456
    
    • 1
    • 2
    • 3
    • 4
    • 5
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--只保留两项,setting,typeAliases-->
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
        <typeAliases>
            <package name="com.zk.pojo"/>
        </typeAliases>
    </configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    <!--引入 db.properties 中的配置-->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!--定义数据源 Bean
    使用spring数据源替换mybatis的数据源c3p0,druid,dbcp,这里使用的是spring-jdbc提供的org.springframework.jdbc.datasource-->
    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
        <property name="driverClassName" value="${driver}"/>
        <property name="username" value="${usernames}"/>
        <property name="url" value="${url}"/>
        <property name="password" value="${password}"/>
    </bean>
    <!--要让Spring使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:
    一个 SqlSessionFactory 和至少一个数据映射器类。-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!--mybatis有mapper和configuration两种配置文件 后者中的所有配置都可以被Spring整合到sessionFactory中来-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/zk/dao/*.xml"/>
    </bean>
    
    <!--SqlSessionTemplate就是sqlSession,这个sqlSession是线程安全的,
    基于Spring 的事务配置来自动提交、回滚、关闭 session。
    批量创建一些 SqlSession-->
    <bean class="org.mybatis.spring.SqlSessionTemplate" id="sqlSession">
        <!--只能用构造器,SqlSessionTemplate没有setter方法-->
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    
    <!--配置声明式事务-->
    <!--传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource。-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    <!--开启注解扫描-->
    <context:component-scan base-package="com.zk"/>
    
    <!--spring-mybatis-congfig整合逻辑放这个配置文件不动了-->
    <import resource="classpath:spring-mybatis-congfig.xml"/>
    <!--同样的还有spring-mvc配置也是这样集成进来-->
    
    <!--本文件注入bean被调用-->
    <bean class="com.zk.dao.UserMapperImpl" id="userMapper">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
    <bean class="com.zk.service.UserService" id="userService">
    	<property name="userMapper" ref="userMapper"/>
    </bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 测试及结果
    @Test
        public void test02(){
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = context.getBean("userService", UserService.class);
            List<User> userList = userService.getUserList();
            userList.forEach(val-> System.out.println(val.toString()));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7efaad5a] will not be managed by Spring
    ==>  Preparing: insert into school.user (id,name,pwd) values (?,?,?);
    ==> Parameters: 14(Integer), 李华(String), 123123(String)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58d75e99]
    Creating a new SqlSession
    SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32c4e8b2] was not registered for synchronization because synchronization is not active
    JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4c1f22f3] will not be managed by Spring
    ==>  Preparing: delete from school.users where id = ?
    ==> Parameters: 14(Integer)
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@32c4e8b2]
    
    org.springframework.jdbc.BadSqlGrammarException: 
    ### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: Table 'school.users' doesn't exist
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    • 结果是这个删除语句报错后,增加操作没有回退

    • 在这个例子中有以下几点需要注意:

    spring构建sqlSession时默认是自动提交事务的

    底层原理提醒:这就是Innodb的底层原理 ,除了死锁以外,事务都不会自动回滚

    这里数据源没有配置事务提交,默认使用mysql的,InnoDB默认就是事务自动提交的

    • 在spring-mybatis-congfig.xml添加事务
    <!--配置声明式事务-->
    <!--传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource。-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--事务注解-->
    <!--<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>-->
    <!--结合AOP实现事务的植入-->
    <!--配置事务通知-->
    <tx:advice id="interceptor" transaction-manager="transactionManager">
        <tx:attributes>
            <!--这里的方法名要和接口里的方法名匹配-->
            <!--propagation="REQUIRED"传播特性,默认REQUIRED-->
            <tx:method name="*add*" propagation="REQUIRED"/>
            <tx:method name="*delete*" propagation="REQUIRED"/>
            <tx:method name="*update*" propagation="REQUIRED"/>
            <tx:method name="*User*" propagation="REQUIRED"/>
            <tx:method name="*query*" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.zk.dao.*.*(..))"/>
        <aop:pointcut id="myPointcut2" expression="execution(* com.zk.service.*.*(..))"/>
        <aop:advisor advice-ref="interceptor" pointcut-ref="myPointcut"/>
        <aop:advisor advice-ref="interceptor" pointcut-ref="myPointcut2"/>
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    事务属性配置

    • 对于tx:advice 来说,事务属性是被定义在tx:attributes 中的,该元素可以包含一个或多个 tx:method 元素。

    • tx:method 元素包含多个属性参数,可以为某个或某些指定的方法(name 属性定义的方法)定义事务属性,如下表所示。

    事务属性说明
    propagation指定事务的传播行为。
    isolation指定事务的隔离级别。
    read-only指定是否为只读事务。
    timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
    rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
    no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。

    配置切点切面

    • tx:advice 元素只是定义了一个 AOP 通知,它并不是一个完整的事务性切面。我们在 tx:advice 元素中并没有定义哪些 Bean 应该被通知,因此我们需要一个切点来做这件事。

    • 在 Spring 的 XML 配置中,我们可以利用 Spring AOP 技术将事务通知(tx-advice)和切点配置到切面中,配置内容如下。

    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.zk.dao.*.*(..))"/>
        <aop:pointcut id="myPointcut2" expression="execution(* com.zk.service.*.*(..))"/>
        <aop:advisor advice-ref="interceptor" pointcut-ref="myPointcut"/>
        <aop:advisor advice-ref="interceptor" pointcut-ref="myPointcut2"/>
    </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 同样测试,发现报错后新增也回滚了

    在这里插入图片描述

    • 业务比较复杂的时候选择编程式事务会更高效,中小型项目用注解
    • 为什么需要事务?

    不配置事务,会存在数据提交不一致问题

    不用spring去配置声明式事务,需要在代码中手动配置事务

    事务在项目开发中十分重要,涉及到数据一致性和完整性问题

    11.4.2 Annotation 注解方式的事务管理
    • 开启注解事务

    • tx 命名空间提供了一个 tx:annotation-driven 元素,用来开启注解事务,简化 Spring 声明式事务的 XML 配置。

    • tx:annotation-driven 元素的使用方式也十分的简单,我们只要在 Spring 的 XML 配置中添加这样一行配置即可。

    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    
    • 1

    与 tx:advice 元素一样,tx:annotation-driven 也需要通过 transaction-manager 属性来定义一个事务管理器,这个参数的取值默认为 transactionManager。如果我们使用的事务管理器的 id 与默认值相同,则可以省略对该属性的配置,形式如下。

    <tx:annotation-driven/>
    
    • 1

    通过 tx:annotation-driven 元素开启注解事务后,Spring 会自动对容器中的 Bean 进行检查,找到使用 @Transactional 注解的 Bean,并为其提供事务支持。

    • 使用 @Transactional 注解:@Transactional 注解是 Spring 声明式事务编程的核心注解,该注解既可以在类上使用,也可以在方法上使用。

    • @Transactional 注解在类上使用,则表示类中的所有方法都支持事务;若 @Transactional 注解在方法上使用,则表示当前方法支持事务。

    • Spring 在容器中查找所有使用了 @Transactional 注解的 Bean,并自动为它们添加事务通知,通知的事务属性则是通过 @Transactional 注解的属性来定义的。

    事务属性说明
    propagation指定事务的传播行为。
    isolation指定事务的隔离级别。
    read-only指定是否为只读事务。
    timeout表示超时时间,单位为“秒”;声明的事务在指定的超时时间后,自动回滚,避免事务长时间不提交会回滚导致的数据库资源的占用。
    rollback-for指定事务对于那些类型的异常应当回滚,而不提交。
    no-rollback-for指定事务对于那些异常应当继续运行,而不回滚。
    • 举例:改造上面的xml方式的例子

    • 用注解修改dao接口实现类

    @Repository
    public class UserMapperImpl implements UserMapper{
    
        @Autowired
        private SqlSessionTemplate sessionTemplate;
    
        @Override
        public List<User> getUserList() {
            UserMapper mapper = sessionTemplate.getMapper(UserMapper.class);
            List<User> userList = mapper.getUserList();
            return userList;
        }
        @Override
        public int insertUser(User user) {
            UserMapper mapper = sessionTemplate.getMapper(UserMapper.class);
            int result = mapper.insertUser(user);
            return result;
        }
    
    
        @Override
        public int deleteUser(int id) {
            UserMapper mapper = sessionTemplate.getMapper(UserMapper.class);
            int result = mapper.deleteUser(id);
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 给Service方法或类上添加事务注解并指定隔离、传播属性
    @Service("userService")
    public class UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Transactional(isolation= Isolation.DEFAULT,propagation = Propagation.REQUIRED)
        public List<User> getUserList(){
            userMapper.insertUser(new User(14,"李华","123123"));
            userMapper.deleteUser(14);
            List<User> userList = userMapper.getUserList();
            return userList;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 修改spring-mybatis-congfig.xml整合文件,需要注册sessionTemplate
    <?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:aop="http://www.springframework.org/schema/aop"
           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
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd">
        <!--引入 db.properties 中的配置-->
        <context:property-placeholder location="classpath:db.properties"/>
    
        <!--定义数据源 Bean
        使用spring数据源替换mybatis的数据源c3p0,druid,dbcp,这里使用的是spring-jdbc提供的org.springframework.jdbc.datasource-->
        <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
            <property name="driverClassName" value="${driver}"/>
            <property name="username" value="${usernames}"/>
            <property name="url" value="${url}"/>
            <property name="password" value="${password}"/>
        </bean>
        <!--要让Spring使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:
        一个 SqlSessionFactory 和至少一个数据映射器类。-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <!--mybatis有mapper和configuration两种配置文件 后者中的所有配置都可以被Spring整合到sessionFactory中来-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <property name="mapperLocations" value="classpath:com/zk/dao/*.xml"/>
        </bean>
    
        <bean class="org.mybatis.spring.SqlSessionTemplate" id="sessionTemplate">
            <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
        </bean>
    
        <!--配置声明式事务-->
        <!--传入的 DataSource 可以是任何能够与 Spring 兼容的 JDBC DataSource。包括连接池和通过 JNDI 查找获得的 DataSource。-->
        <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
    
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 修改spring配置文件applicationContext.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"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!--spring-mybatis-congfig整合逻辑放这个配置文件不动了-->
        <import resource="classpath:spring-mybatis-congfig.xml"/>
        <!--同样的还有spring-mvc配置也是这样集成进来-->
        <!--事务注解-->
        <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
        <!--开启注解扫描-->
        <context:component-scan base-package="com.zk"/>
    </beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 测试及结果,同样数据没有插入进去
     @Test
        public void test01(){
            ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = context.getBean("userService", UserService.class);
            List<User> userList = userService.getUserList();
            userList.forEach(val-> System.out.println(val.toString()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述

    本专栏下一篇:Spring SpEL表达式语言

  • 相关阅读:
    C语言字符型
    几种常用的排序
    图07 --- 关键路径
    使用 kubebuilder 创建并部署 k8s-operator
    Oracle函数之聚组函数
    Python中常用的模块:random模块
    中国户外休闲家具及用品市场发展规划趋势及运营状况研究报告2022年版
    JVM学习(宋红康)之运行时数据区之虚拟机栈中方法返回地址
    leetcode 19. 删除链表的倒数第 N 个结点
    SSM的整合与使用
  • 原文地址:https://blog.csdn.net/weixin_42045639/article/details/125530282