一、相关概念

1.1、什么是事务

事务是作为单个逻辑工作单元执行的一系列操作

1.2、事务的四大特性(ACID)

  • 原子性(Atomicity) :强调事务的不可分割
  • 一致性(Consistency) :事务的执行的前后数据的完整性保持一致
  • 隔离性(Isolation) :一个事务执行的过程中,不应该受到其他事务的干扰
  • 持久性(Durability) :事务一旦结束,数据就持久到数据库

1.3、事务传播(七种传播行为)

保证同一个事务中

  • PROPAGATION_REQUIRED :支持当前事务,如果不存在 就新建一个(默认)
  • PROPAGATION_SUPPORTS :支持当前事务,如果不存在,就不使用事务
  • PROPAGATION_MANDATORY :支持当前事务,如果不存在,抛出异常

保证不在同一个事务中

  • PROPAGATION_REQUIRES_NEW :如果有事务存在,挂起当前事务,创建一个新的事务
  • PROPAGATION_NOT_SUPPORTED :以非事务方式运行,如果有事务存在,挂起当前事务
  • PROPAGATION_NEVER :以非事务方式运行,如果有事务存在,抛出异常
  • PROPAGATION_NESTED :如果当前事务存在,则嵌套事务执行

1.4、事务隔离(四种隔离级别)

  • ISOLATION_READ_UNCOMMITTED(未提交读) :脏读,不可重复读,幻读都有可能发生
  • ISOLATION_READ_COMMITTED(已提交读) :避免脏读,但是不可重复读和幻读有可能发生
  • ISOLATION_REPEATABLE_READ(可重复读) :避免脏读和不可重复读,但是幻读有可能发生
  • ISOLATION_SERIALIZABLE(串行化 ):避免以上所有读问题

1.5、脏读、不可重复读、幻读

脏读

脏读就是指当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这个数据,然后使用了这个数据,这就是脏读。

不可重复读

不可重复读指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

幻影读

幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

image.png

二、JDBC 事务操作

2.1、JDBC连接数据库操作步骤

  1. //1、加载驱动
  2. Class.forName("com.mysql.cj.jdbc.Driver");
  3. //2、获取连接对象
  4. Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
  5. //3、创建statement
  6. Statement statement = connection.createStatement();
  7. //4、获取结果集
  8. ResultSet resultSet = statement.executeQuery("select * from user");
  9. while (resultSet.next()) {
  10. int id = resultSet.getInt("id");
  11. String name = resultSet.getString("name");
  12. int age = resultSet.getInt("age");
  13. System.out.println("id=" + id + " name=" + name + " age=" + age);
  14. }

2.1、JDBC 事务操作

  1. //1、加载驱动
  2. Class.forName("com.mysql.cj.jdbc.Driver");
  3. //2、获取连接对象
  4. Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
  5. //3、关闭自动提交
  6. connection.setAutoCommit(false);
  7. try {
  8. //4、创建statement
  9. PreparedStatement preparedStatement = connection.prepareStatement("UPDATE test.`user` SET name= ? , age= ? WHERE id= ?");
  10. preparedStatement.setString(1, "Java--555");
  11. preparedStatement.setInt(2, 25);
  12. preparedStatement.setInt(3, 1);
  13. preparedStatement.execute();
  14. //在提交前发生错误
  15. int a = 1 / 0;
  16. //5、手动提交
  17. connection.commit();
  18. } catch (Exception e) {
  19. //6、失败回滚
  20. connection.rollback();
  21. e.printStackTrace();
  22. }

三、Spring 事务

3.1、使用

3.2、事务失效场景及解决方案

3.2.1、 首先排查步骤

  • 1、检查 MySQL 数据库的引擎是否是innoDB,其数据库检查是否开启支持事务
  • 2、启动类上是否加入@EnableTransactionManagement注解
  • 3、是否在方法上加入@Transactional注解或Service的类上是否有@Transactional注解
  • 4、方法是否为public
  • 5、是否是因为抛出了Exception等checked异常

3.2.2、为什么要抛出非检查异常,会导致事务失效?

  1. //类 RuleBasedTransactionAttribute 的回滚规则
  2. @Override
  3. public boolean rollbackOn(Throwable ex) {
  4. if (logger.isTraceEnabled()) {
  5. logger.trace("Applying rules to determine whether transaction should rollback on " + ex);
  6. }
  7. RollbackRuleAttribute winner = null;
  8. int deepest = Integer.MAX_VALUE;
  9. //当有设置@Transactional 的 rollbackFor 属性时
  10. //会检查是否支持该异常回滚
  11. if (this.rollbackRules != null) {
  12. for (RollbackRuleAttribute rule : this.rollbackRules) {
  13. int depth = rule.getDepth(ex);
  14. if (depth >= 0 && depth < deepest) {
  15. deepest = depth;
  16. winner = rule;
  17. }
  18. }
  19. }
  20. if (logger.isTraceEnabled()) {
  21. logger.trace("Winning rollback rule is: " + winner);
  22. }
  23. // User superclass behavior (rollback on unchecked) if no rule matches.
  24. if (winner == null) {
  25. logger.trace("No relevant rollback rule found: applying default rules");
  26. //这里就会调用默认的DefaultTransactionAttribute中的rollbackOn方法
  27. /*
  28. * 没有设置rollbackFor 属性时
  29. *默认只回滚RuntimeException 和 Error异常
  30. *public boolean rollbackOn(Throwable ex) {return (ex instanceof RuntimeException || ex instanceof Error);}
  31. */
  32. return super.rollbackOn(ex);
  33. }
  34. return !(winner instanceof NoRollbackRuleAttribute);
  35. }
  36. //getDepth方法
  37. private int getDepth(Class<?> exceptionClass, int depth) {
  38. if (exceptionClass.getName().contains(this.exceptionName)) {
  39. // Found it!
  40. return depth;
  41. }
  42. // If we've gone as far as we can go and haven't found it...
  43. if (exceptionClass == Throwable.class) {
  44. return -1;
  45. }
  46. return getDepth(exceptionClass.getSuperclass(), depth + 1);
  47. }

3.2.3、设置错误的事务传播行为导致事务失效

  1. //若错误的配置以下传播行为,则不会回滚
  2. TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  3. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  4. TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

3.2.4、Spring 的 AOP 的自调用导致事务失效问题?

  1. //若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,
  2. //有@Transactional 注解的方法的事务被忽略,不会发生回滚。
  3. //解决方法:
  4. //1. 把需要代理的方法放到其他类中
  5. //2. 使用AopContext 获取当前代理
  6. @Service
  7. public class OrderService {
  8. public void insert() {
  9. insertOrder();
  10. }
  11. //该方法事务不会回滚
  12. @Transactional
  13. public void insertOrder() {
  14. //insert log info
  15. //insertOrder
  16. //updateAccount
  17. }
  18. }

3.3、事务隔离级别使用

3.4、事务传播行为使用

参考