事务(二)、强一致性事务

前文讲到程序员面对事务ACID特性如何实现一筹莫展,于是本文进入程序员穷开心的表演时间。
众所周知,关系型数据库天生就是解决具有复杂事务场景的问题,关系型数据库完全满足ACID的特性。数据库是怎样实现ACID的呢?

** 数据库实现ACID的核心技术是并发控制和日志技术 **

  • 并发控制:保证并发操作的正确性(2PL)
  • 日志:保证故障场景下可恢复(Undo/Redo,WAL协议)

现代数据库均基于Write ahead logging实现ACID,也就是预写式日志(WAL)。WAL的中心思想是对数据文件的修改必须是只能发生在这些修改已经记录了日志之后 – 也就是说,在日志记录冲刷到永久存储器之后. 如果我们遵循这个过程,那么我们就不需要在每次事务提交的时候 都把数据页冲刷到磁盘,因为我们知道在出现崩溃的情况下, 我们可以用日志来恢复数据库:任何尚未附加到数据页的记录 都将先从日志记录中重做(这叫向前滚动恢复,也叫做 REDO) 然后那些未提交的事务做的修改将被从数据页中删除 (这叫向后滚动恢复 - UNDO)。

程序员穷开心假装自己会写数据库核心代码:

开始
   预写式日志[声明一个事务的唯一标记]
   查看李雷是否有一百元
   李雷账号pk=1减少100元
   韩梅梅账号pk-=2增加100元
   预写式日志[标明该事务提交]
结束

穷开心:如果你在一个数据库中,可以直接使用数据库的事务机制保证ACID。
开心:你说的我都不懂,可我用的就是Mysql,也是现代关系型数据库RDBMS。可问题还是存储。
穷开心:(-_-)~ 那一定是你使用的姿势不对。
开心:你胡说,我是按照csdn上直接照抄过来的(-_-)~

JAVA代码使用事务的正确姿势

try{
      //STEP 2: Register JDBC driver
      Class.forName("com.mysql.jdbc.Driver");

      //STEP 3: Open a connection
      System.out.println("Connecting to database...");
      conn = DriverManager.getConnection(DB_URL,USER,PASS);

      //STEP 4: Set auto commit as false.
      conn.setAutoCommit(false);

      //STEP 5: Execute a query to create statment with
      // required arguments for RS example.
      System.out.println("Creating statement...");
      stmt = conn.createStatement(
                           ResultSet.TYPE_SCROLL_INSENSITIVE,
                           ResultSet.CONCUR_UPDATABLE);

      //STEP 6: INSERT a row into Employees table
      System.out.println("Inserting one row....");
      String SQL = "INSERT INTO Employees " +
                    "VALUES (106, 28, 'Curry', 'Stephen')";
      stmt.executeUpdate(SQL);  

      //STEP 7: INSERT one more row into Employees table
      SQL = "INSERT INTO Employees " +
                    "VALUES (107, 32, 'Kobe', 'Bryant')";
      stmt.executeUpdate(SQL);

      //STEP 8: Commit data here.
      System.out.println("Commiting data here....");
      conn.commit();

      //STEP 9: Now list all the available records.
      String sql = "SELECT id, first, last, age FROM Employees";
      ResultSet rs = stmt.executeQuery(sql);
      System.out.println("List result set for reference....");
      printRs(rs);

      //STEP 10: Clean-up environment
      rs.close();
      stmt.close();
      conn.close();
   }catch(SQLException se){
      //Handle errors for JDBC
      se.printStackTrace();
      // If there is an error then rollback the changes.
      System.out.println("Rolling back data here....");
      try{
         if(conn!=null)
            conn.rollback();
      }catch(SQLException se2){
         se2.printStackTrace();
      }//end try

   }catch(Exception e){
      //Handle errors for Class.forName
      e.printStackTrace();
   }finally{
      //finally block used to close resources
      try{
         if(stmt!=null)
            stmt.close();
      }catch(SQLException se2){
      }// nothing we can do
      try{
         if(conn!=null)
            conn.close();
      }catch(SQLException se){
         se.printStackTrace();
      }//end finally try
   }//end try

穷开心:看到没,java中使用数据库事务需要主动声明。默认情况下数据库连接配置是autoCommit=true。即使你不主动commit数据,每个操作都会自动提交。如果你有两个操作,数据库不会保证两个操作在一个事务中。
开心:说得好像有道理。
开心:问题是JDBC是什么年代的东西啊~~~~~,谁还在自己写代码调用DB Connection?
穷开心:JDBC一点都不老土,现代绝大部分Java orm框架都是基于JDBC的,核心实现代码一样是上面这段。当然我能理解你spring boot党的感受,刚才只是让你涨涨见识。
开心:(-_-)~

springboot的事务支持

在使用JPA作为数据访问技术的时候,spring boot 为我们定义了JpaTransactionManager的bean,并默认开启了度声明式事务的支持。所以在springboot项目中无需显示开启使用@EnableTransactionManagement注解。
因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对非检测异常进行事务回滚,需要在@Transactional 注解里使用
rollbackFor 属性明确指定异常。

    @Transactional(rollbackFor = Exception.class)
    public void addMoney() throws Exception {
        //先增加余额
        accountMapper.addMoney();
        //然后遇到故障
        throw new SQLException("发生异常了..");
    }

在springboot中明明什么都没做,也能实现事务完整性是因为spring boot为我们默默的做了很多。但如果在spring boot中事务完整性被破坏了,有可能是因为事务中发生了非检测异常,也有可能是异常被吃掉导致没有触发回滚。
比如这个代码:

//错误代码
    @Transactional(rollbackFor = Exception.class)
    public void addMoney() throws Exception {
        //先增加余额
        accountMapper.addMoney();
        //谨慎:尽量不要在业务层捕捉异常并处理
        try {
            throw new SQLException("发生异常了..");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

参考
https://www.jianshu.com/p/380a9d980ca5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值