赞
踩
前文讲到程序员面对事务ACID特性如何实现一筹莫展,于是本文进入程序员穷开心的表演时间。
众所周知,关系型数据库天生就是解决具有复杂事务场景的问题,关系型数据库完全满足ACID的特性。数据库是怎样实现ACID的呢?
** 数据库实现ACID的核心技术是并发控制和日志技术 **
现代数据库均基于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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
穷开心:看到没,java中使用数据库事务需要主动声明。默认情况下数据库连接配置是autoCommit=true。即使你不主动commit数据,每个操作都会自动提交。如果你有两个操作,数据库不会保证两个操作在一个事务中。
开心:说得好像有道理。
开心:问题是JDBC是什么年代的东西啊~~~~~,谁还在自己写代码调用DB Connection?
穷开心:JDBC一点都不老土,现代绝大部分Java orm框架都是基于JDBC的,核心实现代码一样是上面这段。当然我能理解你spring boot党的感受,刚才只是让你涨涨见识。
开心:(-_-)~
在使用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("发生异常了..");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在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();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。