Spring5️⃣声明式事务(todo

1、事务

MySQL 事务

MySQL 默认自动提交事务:每执行一条 DML 语句,MySQL 立即将事务提交到数据库。

  • 事务操作
    • 控制事务 :BEGIN 或 START TRANSACTION
    • 提交事务 :COMMIT
    • 回滚事务 :ROLLBACK
  • 四大特性:ACID
  • 并发事务:脏读、不可重复读、幻读
  • 隔离级别:RU、RC、RR、Serializable
  • 传播行为:REQUIRED、SUPPORT、MANDATORY 等

Spring 事务管理

  • 编程式事务管理:代码级别(类似 JDBC 事务管理)
  • 声明式事务管理:配置

2、编程式事务

2.1、JDBC 事务管理

回顾 JDBC 事务管理

Connection 对象提供操作事务的方法。

  • 查看、设置自动提交状态

  • 提交事务

  • 回滚事务

    boolean getAutoCommit() throws SQLException;
    void setAutoCommit(boolean autoCommit) throws SQLException;
    
    void commit() throws SQLException;
    
    void rollback() throws SQLException;
    

示例

SQL 可能发生运行时异常(而非编译期异常),需通过 try-catch 捕获。

  1. 开启事务。

  2. 执行 SQL。

  3. 若无异常,则正常提交事务。

  4. 若发生异常,则捕获并回滚事务。

    public static void update(String sql1, String sql2) throws SQLException {
        // 省略:注册驱动、获取连接、获取SQL执行对象
        try {
            // 开启事务
            if (connection.getAutoCommit()) {
                connection.setAutoCommit(false);
            }
            // 执行SQL
            int count1 = statement.executeUpdate(sql1);
            // 此处可模拟异常,如int i = 1/0;
            int count2 = statement.executeUpdate(sql2);
            // 提交事务
            connection.commit();
        } catch (Exception e) {
            // 回滚事务
            connection.rollback();
            e.printStackTrace();
        }
        // 释放资源
    }
    

2.2、Spring API

2.2.1、平台事务管理器

PlatformTransactionManager:定义操作事务的方法

类似 JDBC 事务管理,将事务信息封装为状态对象

  • 获取事务:根据事务传播级别,返回当前事务或创建新事务

  • 提交事务

  • 回滚事务

    TransactionStatus getTransaction(TransactionDefination defination);
    
    void commit(TransactionStatus status);
    
    void rollback(TransactionStatus status);
    

2.2.2、事务定义对象

TransactionDefination:定义事务信息

  • 成员变量:事务传播行为、事务隔离级别、超时时间。

  • 常用方法

    方法 默认值
    获取事务传播行为 int getPropagationBehavior() REQUIRED
    获取事务隔离级别 int getIsolationLevel DEFAULT,即数据库的默认隔离级别
    获取超时时间 int getTimeout() 基础事务系统的默认超时
    是否只读 boolean isReadOnly() false(建议 DQL 设为 true)

事务的传播行为、隔离级别

参考:MySQL 事务

2.2.3、事务状态对象

TransactionStatus:定义事务的运行状态

  • 继承自 TransactionExecution 接口的方法

    // 当前事务是否是新事务
    boolean isNewTransaction();
    // 设置事务仅回滚
    void setRollbackOnly();
    // 事务是否标记为仅回滚
    boolean isRollbackOnly();
    // 事务是否已完成:即是否已提交或回滚
    boolean isCompleted();
    
  • 内部定义的方法

    // 当前事务是否在内部存储回滚点
    boolean hasSavepoint();
    

关于编程式事务,需了解

  • JDBC 的事务管理操作
  • 以及 Spring 提供的 API 即可。

==3、声明式事务

声明式事务:AOP 的应用。

采用声明的方式来管理事务。

  • 声明:通过在 Spring 配置文件中配置的方式,代替代码级别的事务管理。
  • 步骤
    1. 导入依赖:spring-jdbc
    2. 注册 bean:事务管理器
    3. 事务配置
    4. AOP 配置

3.1、环境搭建

3.1.1、搭建环境

整合 MyBatis ,再进行以下测试。

Mapper

增删改操作需要提交事务,因此在 Mapper 中添加 insert 方法和 delete 方法来测试。

UserMapper

int insertUser(User user);
int deleteUser(long id);

UserMapper.xml

<insert id="insertUser" parameterType="user">
    insert into mybatis.user(id, name, password)
    values (#{id}, #{name}, #{password});
</insert>

<delete id="deleteUser" parameterType="_long">
    delete
    from mybatis.user
    where id = #{id}
</delete>

UserMapperImpl

public class UserMapperImpl implements UserMapper {

    private SqlSessionTemplate sqlSession;

    public void setSqlSession(SqlSessionTemplate sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public int insertUser(User user) {
        return sqlSession.getMapper(UserMapper.class).insertUser(user);
    }

    @Override
    public int deleteUser(long id) {
        return sqlSession.getMapper(UserMapper.class).deleteUser(id);
    }
}

测试

分别对两个方法进行测试

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    int i = -1;
    i = userMapper.insertUser(new User(10001, "u10001", "123456"));
    if (i > 0) {
        System.out.println("插入成功");
    }

    i = userMapper.insertUser(new User(10002, "u10002", "123456"));
    if (i > 0) {
        System.out.println("插入成功");
    }
}

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    // List<User> users = userMapper.listUsers();
    //
    // for (User user : users) {
    //     System.out.println(user);
    // }

    int i = -1;

    i = userMapper.deleteUser(10001);
    if (i > 0) {
        System.out.println("删除成功");
    }
}

3.1.2、测试事务

UserMapper.xml

delete 语句修改为错误的 SQL 语句(将 delete 关键字改成 deletes),使其无法正确执行。

<delete id="deleteUser" parameterType="_long">
    deletes
    from mybatis.user
    where id = #{id}
</delete>

UserMapperImpl

将 listUsers 方法作为事务来测试,调用 insert 和 delete 方法,查看结果。

@Override
public List<User> listUsers() {
    // 增加用户101
    insertUser(new User(101,"u101","123456"));
    // 删除用户10002
    deleteUser(10002);
    return sqlSession.getMapper(UserMapper.class).listUsers();
}

JUnit

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    List<User> users = userMapper.listUsers();

}

结果

  1. 删除语句报错

    image-20210808150726916

  2. 用户101成功插入,用户10002未被删除

    image-20210808150807341

我们想要的结果是:要么都成功,要么都失败。

即要么用户101 插入成功且用户10002被删除,要么用户101没插入且用户10002没被删除。

因此需要开启事务,来达到预期结果。

3.2、基于 XML 实现

3.2.1、导入配置

mybatis-config

使用 AOP 实现 Spring 声明式事务,因此需要导入 AOP 和 事务的配置信息。

xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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

3.2.2、设置事务

mybatis-config

  1. 开启事务管理
    • 使用的 dataSource 数据源,必须与 sqlSessionFactory 是同一个。
  2. 配置事务通知
  3. tx-mehod:要开启事务的方法
    • name:方法名,可以使用通配符*
    • propagation:事务传播机制;
      • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  4. 配置AOP
<!-- 开启事务管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg ref="dataSource"/>
</bean>

<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="listUsers" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!-- 配置AOP:切入事务 -->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* indi.jaywee.mapper.UserMapperImpl.* (..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

3.2.3、测试

UserMapperImpl

测试 insert 用户103、delete 用户 10002,查看结果

@Override
public List<User> listUsers() {
    // 增加用户101
    insertUser(new User(103,"u103","123456"));
    // 删除用户10002
    deleteUser(10002);
    return sqlSession.getMapper(UserMapper.class).listUsers();
}

Junit

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);

    List<User> users = userMapper.listUsers();

}

结果

  1. 删除语句报错

    image-20210808151521381

  2. 用户103未被插入,用户10002未被删除:即事务成功开启

    image-20210808151608856

posted @ 2021-08-02 21:21  Jaywee  阅读(77)  评论(0编辑  收藏  举报

👇