【回顾原生JDBC手动管理事务以及两种方式实现Spring编程式事务】
文章目录
- 一.关于事务
- 1.事务概念
- 2.事务四个基本特性
- 3. 事务的生命周期
- 4.事务的隔离级别
- 5.事务的应用场景
- 二.回顾原生JDBC手动管理事务
- 三.Spring编程式事务
- 1.使用 TransactionTemplate 进行编程式事务管理
- 2.使用 PlatformTransactionManager 进行编程式事务管理
- 四.编程式事务的应用场景
- 五.总结
一.关于事务
1.事务概念
事务(Transaction) 是数据库操作中的一个基本概念,指的是一组作为单个工作单元执行的操作。这些操作要么全部执行成功并提交(commit),要么全部失败并回滚(rollback),确保数据库的一致性、完整性和可靠性。
事务通常应用于多步骤的数据库操作中,以确保数据在各种操作中的一致性,特别是在出现系统故障、错误或并发操作时。事务可以跨越多个SQL语句,这些语句共同构成一个完整的任务。
2.事务四个基本特性
事务的四个基本特性(ACID),事务的核心特性可以用 ACID 四个字母来概括:
- 原子性(Atomicity):
事务中的所有操作要么全部成功,要么全部失败回滚。如果在事务执行过程中出现任何错误,已经执行的操作必须撤销(回滚),数据库恢复到事务开始前的状态。 - 一致性(Consistency):
事务开始前和结束后,数据库必须处于一致的状态。事务的执行不能破坏数据库的规则(如约束、触发器等),任何违反数据库一致性规则的操作都会导致事务失败并回滚。 - 隔离性(Isolation):
事务的执行过程与其他事务是隔离的,一个事务的执行不应受到其他事务的干扰。并发事务互不影响,每个事务的中间状态对其他事务不可见。
数据库系统提供不同的隔离级别(如读未提交、读已提交、可重复读、可串行化)来控制并发事务之间的相互影响。 - 持久性(Durability):
一旦事务提交,它对数据库的修改是永久的,即使系统发生故障也不会丢失。数据库系统通常会使用日志、备份等机制确保事务的持久性。
3. 事务的生命周期
- 开始事务:
通过客户端或应用程序显式发起一个事务。在这期间执行的所有操作都被认为是这个事务的一部分。 - 执行操作:
事务中执行多个SQL语句,如插入、更新、删除等。 - 提交事务:
如果所有操作都成功,事务提交,数据库将保存这些操作的结果,数据状态更新。 - 回滚事务:
如果事务中某些操作失败或发生错误,事务回滚,数据库将撤销已经执行的操作,恢复到事务开始前的状态。
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中推荐使用的方式,能够简化事务管理,它通过模板模式封装了事务处理逻辑。
@Autowiredprivate PlatformTransactionManager platformTransactionManager;/*** 编程式事务1:使用 TransactionTemplate 进行编程式事务管理*/public void programmingTransaction1() {TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);transactionTemplate.execute(new TransactionCallbackWithoutResult() {@Overrideprotected 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 false;}
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.commitplatformTransactionManager.commit(transactionStatus);} catch (Exception e) {//6.回滚事务:platformTransactionManager.rollbackplatformTransactionManager.rollback(transactionStatus);}log.info("after:{}", jdbcTemplate.queryForList("SELECT * from t_user1"));}
编程式事务过程简化了一下,如下:
- 定义事务属性信息:TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
- 定义事务管理器:PlatformTransactionManager platformTransactionManager = new DataSourceTransactionManager(dataSource);
- 获取事务:TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
- 执行sql操作:比如上面通过JdbcTemplate的各种方法执行各种sql操作
- 提交事务(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声明式事务,分布式事务还是有很大帮助的~