【回顾原生JDBC手动管理事务以及两种方式实现Spring编程式事务】

一.关于事务

1.事务概念

事务(Transaction) 是数据库操作中的一个基本概念,指的是一组作为单个工作单元执行的操作。这些操作要么全部执行成功并提交(commit),要么全部失败并回滚(rollback),确保数据库的一致性、完整性和可靠性。

事务通常应用于多步骤的数据库操作中,以确保数据在各种操作中的一致性,特别是在出现系统故障、错误或并发操作时。事务可以跨越多个SQL语句,这些语句共同构成一个完整的任务。

2.事务四个基本特性

事务的四个基本特性(ACID),事务的核心特性可以用 ACID 四个字母来概括:

  1. 原子性(Atomicity):
    事务中的所有操作要么全部成功,要么全部失败回滚。如果在事务执行过程中出现任何错误,已经执行的操作必须撤销(回滚),数据库恢复到事务开始前的状态。
  2. 一致性(Consistency):
    事务开始前和结束后,数据库必须处于一致的状态。事务的执行不能破坏数据库的规则(如约束、触发器等),任何违反数据库一致性规则的操作都会导致事务失败并回滚。
  3. 隔离性(Isolation):
    事务的执行过程与其他事务是隔离的,一个事务的执行不应受到其他事务的干扰。并发事务互不影响,每个事务的中间状态对其他事务不可见。
    数据库系统提供不同的隔离级别(如读未提交、读已提交、可重复读、可串行化)来控制并发事务之间的相互影响。
  4. 持久性(Durability):
    一旦事务提交,它对数据库的修改是永久的,即使系统发生故障也不会丢失。数据库系统通常会使用日志、备份等机制确保事务的持久性。

3. 事务的生命周期

  1. 开始事务:
    通过客户端或应用程序显式发起一个事务。在这期间执行的所有操作都被认为是这个事务的一部分。
  2. 执行操作:
    事务中执行多个SQL语句,如插入、更新、删除等。
  3. 提交事务:
    如果所有操作都成功,事务提交,数据库将保存这些操作的结果,数据状态更新。
  4. 回滚事务:
    如果事务中某些操作失败或发生错误,事务回滚,数据库将撤销已经执行的操作,恢复到事务开始前的状态。

4.事务的隔离级别

为了控制多个事务并发执行时的互相影响,数据库提供了多种隔离级别:

‌‌读未提交(Read Uncommitted)‌:最低的隔离级别,允许一个事务读取另一个事务未提交的数据。这可能导致脏读、不可重复读和幻读问题。
‌‌读已提交(Read Committed)‌:允许一个事务只能读取已经提交的其他事务的数据。这解决了脏读的问题,但仍可能存在不可重复读和幻读问题。
‌可重复读(Repeatable Read)‌:保证一个事务在执行期间多次读取相同的数据时,其结果是一致的。解决了脏读、不可重复读的问题,但仍可能存在幻读问题。
‌‌串行化(Serializable)‌:提供最高的隔离级别,通过强制事务串行执行来解决所有并发问题,包括脏读、不可重复读和幻读。虽然能够确保数据的完全一致性,但也导致了性能上的损失。

‌MySQL默认的隔离级别是可重复读(Repeatable Read)。‌‌在这个级别下,事务在读取数据时,会锁定读取的数据,防止其他事务对这些数据进行修改,直到当前事务提交或回滚。这意味着在一个事务中,读取的数据保持一致性,并且在事务结束之前不会被其他事务修改。

5.事务的应用场景

  • 银行转账:A用户向B用户转账,涉及A账户扣款和B账户入账,这两步操作必须作为一个事务,确保要么两步都成功,要么都失败。
  • 订单处理:在电商系统中,生成订单和扣除库存往往需要保证原子性,避免出现生成了订单但库存未更新的情况。

举例说明事务:事务中的两步操作,从账户A中扣除100元。向账户B中添加100元。这两步操作必须放在一个事务中。如果第一步成功,第二步失败(比如网络错误),数据库应回滚第一步的操作,恢复到未扣除100元的状态。

BEGIN TRANSACTION;

-- 第一步:从A账户扣除100元
UPDATE accounts SET balance = balance - 100 WHERE user_id = 'A';

-- 第二步:向B账户添加100元
UPDATE accounts SET balance = balance + 100 WHERE user_id = 'B';

-- 如果两步操作都成功,提交事务
COMMIT;

-- 如果任何一步失败,回滚事务
ROLLBACK;

事务的核心作用是通过ACID特性,确保数据操作在各种可能的异常情况下仍保持一致性和可靠性,防止数据错误和损坏。通过事务的使用,开发者可以更加自信地处理复杂的数据库操作,确保数据在不同步骤间保持一致。

二.回顾原生JDBC手动管理事务

为什么需要完全记住并且背熟JDBC流程? 因为这样去看Spring声明式事务或者去了解分布式事务,能让我们抓住主线,关注到重要的东西。

public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/xiaofa_lawyer";
        String user = "root";
        String password = "123456";

        Connection conn = null;
        PreparedStatement pstmt1 = null;
        PreparedStatement pstmt2 = null;

        try {
            // 1. 建立数据库连接
            conn = DriverManager.getConnection(url, user, password);

            // 2. 关闭自动提交,开始手动管理事务
            conn.setAutoCommit(false);

            // 3. 执行SQL操作
            String sql1 = "INSERT INTO t_user1 (name) VALUES (?)";
            pstmt1 = conn.prepareStatement(sql1);
            pstmt1.setString(1, "jinbiao111");
            pstmt1.executeUpdate();

            String sql2 = "INSERT INTO t_user2 (name) VALUES (?)";
            pstmt2 = conn.prepareStatement(sql2);
            pstmt2.setString(1, "jinbiao222");
            pstmt2.executeUpdate();

            // 4. 如果SQL操作成功,提交事务
            conn.commit();
            System.out.println("事务提交成功。");

        } catch (SQLException e) {
            // 5. 如果发生异常,回滚事务
            if (conn != null) {
                try {
                    System.out.println("发生错误,回滚事务。");
                    conn.rollback();
                } catch (SQLException rollbackEx) {
                    rollbackEx.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            // 6. 关闭资源
            try {
                if (pstmt1 != null) pstmt1.close();
                if (pstmt2 != null) pstmt2.close();
                if (conn != null) conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

测试结果,这里因为没有异常,对两张表都插入成功,日志提示:“事务提交成功。”

三.Spring编程式事务

在Spring中,除了通过声明式事务(使用注解或XML配置)管理事务外,还可以使用编程式事务管理。编程式事务允许开发者在代码中通过API显式控制事务的开始、提交和回滚操作。Spring 提供了 TransactionTemplate 和 PlatformTransactionManager 两种主要的方式来实现编程式事务管理。

1.使用 TransactionTemplate 进行编程式事务管理

TransactionTemplate 是Spring中推荐使用的方式,能够简化事务管理,它通过模板模式封装了事务处理逻辑。

 @Autowired
    private PlatformTransactionManager platformTransactionManager;

    /**
     * 编程式事务1:使用 TransactionTemplate 进行编程式事务管理
     */
    public void programmingTransaction1() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    // 执行业务逻辑
                    jdbcTemplate.update("insert into t_user1 (name) values (?)", "jinbiao333");
                    log.info("事务中执行的操作");
                    // 你可以根据特定条件回滚事务
                    if (someCondition()) {
                        log.info("事务回滚");
                        status.setRollbackOnly();  // 手动回滚事务
                    }

                } catch (Exception e) {
                    log.info("事务回滚");
                    status.setRollbackOnly();  // 发生异常时自动回滚
                    throw e;
                }
            }
        });
    }

    /**
     *  判断满足某个条件事务回滚
     * @return
     */
    private boolean someCondition() {
        return true;
    }

2.使用 PlatformTransactionManager 进行编程式事务管理

     /**
     * 编程式事务2:使用 PlatformTransactionManager 进行编程式事务管理
     */
    public void programmingTransaction2() {
        //定义一个数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql://localhost:3306/xiaofa_lawyer");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("123456");

        //定义一个JdbcTemplate,用来方便执行数据库增删改查
        JdbcTemplate jdbcTemplate = new JdbcTemplate(druidDataSource);
        
        //1.定义事务管理器,给其指定一个数据源(可以把事务管理器想象为一个人,这个人来负责事务的控制操作)
        PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(druidDataSource);
        
        //2.定义事务属性:TransactionDefinition,TransactionDefinition可以用来配置事务的属性信息,比如事务隔离级别、事务超时时间、事务传播方式、是否是只读事务等等。
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        
        //3.获取事务:调用platformTransactionManager.getTransaction开启事务操作,得到事务状态(TransactionStatus)对象
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        
        //4.执行业务操作,下面就执行2个插入操作
        try {
            log.info("before:{}", jdbcTemplate.queryForList("SELECT * from t_user1"));
            jdbcTemplate.update("insert into t_user1 (name) values (?)", "jinbiao555");
            jdbcTemplate.update("insert into t_user2 (name) values (?)", "jinbiao666");
            
            //5.提交事务:platformTransactionManager.commit
            platformTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            //6.回滚事务:platformTransactionManager.rollback
            platformTransactionManager.rollback(transactionStatus);
        }
        log.info("after:{}", jdbcTemplate.queryForList("SELECT * from t_user1"));
    }

编程式事务过程简化了一下,如下

  1. 定义事务属性信息:TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  2. 定义事务管理器:PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
  3. 获取事务:TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
  4. 执行sql操作:比如上面通过JdbcTemplate的各种方法执行各种sql操作
  5. 提交事务(platformTransactionManager.commit)或者回滚事务(platformTransactionManager.rollback).

**PlatformTransactionManager接口有多个实现类,用来应对不同的环境。**比如你操作db用的是hibernate或者mybatis,那么用到的事务管理器是不一样的,常见的事务管理器实现有下面几个:

  • JpaTransactionManager:如果你用jpa来操作db,那么需要用这个管理器来帮你控制事务。
  • DataSourceTransactionManager:如果你用是指定数据源的方式,比如操作数据库用的是:JdbcTemplate、mybatis、ibatis,那么需要用这个管理器来帮你控制事务。
  • HibernateTransactionManager:如果你用hibernate来操作db,那么需要用这个管理器来帮你控制事务。
  • JtaTransactionManager:如果你用的是java中的jta来操作db,这种通常是分布式事务,此时需要用这种管理器来控制事务。

我们的案例中使用的是JdbcTemplate来操作db,所以用的是DataSourceTransactionManager这个管理器。

事务传播行为和隔离级别
在编程式事务中,可以通过 TransactionDefinition 设置事务的传播行为和隔离级别:

  • 传播行为:
  • PROPAGATION_REQUIRED(默认):加入现有事务或创建新事务。
  • PROPAGATION_REQUIRES_NEW:创建一个新事务,暂停当前事务。
  • PROPAGATION_SUPPORTS:支持现有事务,但如果没有事务,则以非事务方式执行。

四.编程式事务的应用场景

  • 需要灵活控制事务边界,尤其是在一个方法中根据不同条件决定是否提交或回滚事务。
  • 需要更复杂的事务逻辑,比如多个数据源之间的事务处理。
    相比于声明式事务,编程式事务虽然更灵活,但也更复杂。一般情况下,推荐使用声明式事务,只有在需要精细控制事务时才使用编程式事务。

五.总结

了解原生JDBC手动管理事务、Spring编程式事务,对我们学习Spring声明式事务,分布式事务还是有很大帮助的~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值