写在开头:本文是学习尚硅谷JavaWeb的个人笔记,便于自己后期复习,也供各位参考评论,指出错误共同进步。
事务:一组逻辑单元操作单元,使数据从一种状态转换到另一种状态。(将AA的账户余额转100到B的账户上。这样要经过两个update操作,使得balances由原来的1000、1000转变为900、1100,这就是数据库事务。)
一组逻辑操作单元:一个或多个DML(数据的增删改查)
事务处理原则 : 1. 保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时 ,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(**rollback)**到最初状态。
和上面例子一样,要么两人(AA、BB)数字都改变,如果出现异常就回滚,两者都不改变。
2.数据一旦提交,就不可回滚。(我们要做的操作就是不能让数据提交,后面再提交) 3.哪些操作会导致数据的自动提交?
(1)DDL操作一旦执行,都会自动提交。(DLL操作是对数据表的创建create、修改alter、删除drop等操作),set autocommit = false 对DDL操作失效(反正他就是要自动提交)。 (2)DML默认情况下,一旦执行,就会自动提交。(数据的增删改查),(这里解决办法是我们可以通过set autocommit = false的方式取消DML操作的自动提交。) (3)默认在关闭连接时,会自动的提交数据。(这里解决办法就是:让两个事务用一个连接conn,整个转账操作做完之后在关闭连接)
为了让多个 SQL 语句作为一个事务执行:
1、调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务2、在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务3、在出现异常时,调用 rollback(); 方法回滚事务4、若此时 Connection 没有被关闭, 则需要恢复其自动提交状态实例一:未考虑数据库事务的转账操作 (问题所在:如果程序现故障可能得到的结果就是AA转账成功但是BB没有收到)
public class TransactionTest { /**********************未考虑数据库事务的转账操作*************** * 针对数据表user_table来说, * AA用户给BB用户转账100 */ @Test public void testUpdate() { String sql1 = "update user_table set balance=balance-100 where user=?"; update(sql1,"AA"); //网络异常 System.out.println(10/0);//问题所在:如果程序在这里出现故障那得到的结果就是AA转账成功但是BB没有收到 String sql2 = "update user_table set balance=balance+100 where user=?"; update(sql2,"BB"); System.out.println("转账成功"); } public int update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同! Connection conn = null; PreparedStatement ps = null; try { //1.获取数据库的连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); //3.填充占位符 for(int i = 0;i < args.length;i++){ ps.setObject(i + 1, args[i]);//小心参数声明错误!! } //4.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally{ //5.资源的关闭 JDBCUtils.closeResource(conn, ps); } return 0; }实例二:考虑数据库事务的转账操作
//***************考虑事务的转账操作*********************** @Test public void testUpdatewidthTx() { Connection conn=null;//两步操作用一个连接串联起来了 try { conn = JDBCUtils.getConnection(); System.out.println(conn.getAutoCommit()); //1.取消数据的自动提交 conn.setAutoCommit(false); String sql1 = "update user_table set balance=balance-100 where user=?"; update(conn,sql1,"AA");//两者公用一个conn //网络异常 //System.out.println(10/0); String sql2 = "update user_table set balance=balance+100 where user=?"; update(sql2,"BB"); System.out.println("转账成功"); conn.commit();//2.提交数据 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); }//3.产生异常我们回滚数据 }finally { JDBCUtils.closeResource(conn, null);//最后我们采取关闭连接 } } //通用增删改操作version2.0(从外部传一个连接进来) public int update(Connection conn,String sql,Object ...args){//sql中占位符的个数与可变形参的长度相同! PreparedStatement ps = null; try { //2.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); //3.填充占位符 for(int i = 0;i < args.length;i++){ ps.setObject(i + 1, args[i]);//小心参数声明错误!! } //4.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); }finally{ // 5.恢复每次DML操作的自动提交功能 try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } //5.资源的关闭 JDBCUtils.closeResource(null, ps);//连接不关,后面还要用 } return 0; }对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题: - 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。(一般解决脏读就好了,因为人家都没提交,你读了也没有用)
T1读取了T2 更新但还没有被提交数据,这种没有提交被我们读到的数据就叫脏读,一旦T1回滚,T2再去读取,两次读取的数据就不一样。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
比如在火车站买票,点击进去有200张票,过了一会儿我们并没有关闭这个事务,再刷新,票数就达到了210张。两次读到的数据不一样叫不可重复读。(一般来说这个可以不解决)
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如 果 T1 再次读取同一个表, 就会多出几行。
事务T再去查的时候发现表中多了几行,跟幻觉一样。
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
数据库提供的4种事务隔离级别: 表格复制
隔离级别描述READ UNCOMMITTED(读未提交数据)允许事务读取未被其他事务提交的变更;脏读,不可重复度和幻读的问题都会出现(三个问题都不能解决)READ COMMITED(读已提交数据)只允许事务读取已经被其他事务提交的变更;可以避免脏读,但不可重复读和幻读的问题仍然可能出现(解决脏读)REPEATABLE READ(可重复读)确保事务可以多次从一个字段中读取相同的值;在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在(解决脏读、幻读)SERIALIZABLE(串行化)确保事务可以从一个表中读取相同的行;在这个事务持续期间,禁止其他事务对该表进行插入,更新和删除操作;所有并发问题都可以避免,但性能十分低下(三个问题都能解决)越往下并发性越差,一致越好。
可重复读解决的方法是:如果事务T1去查表结果是1,,现在事务T2改了表的值变成了2,并且提交了,但现在事务T1并没有断掉,T1再去查表仍然是1,只有当事务T1断了,查到的结果才是2. 串行化解决的方法是:同上,只要在同一个事务内,查到的东西都一样。只有关掉这个事务,重新打开新的事务(比如在一个网页上我们关掉网页重新打开)才能看到更新的行,列。
Oracle 支持的 2 种事务隔离级别:READ COMMITED,SERIALIZABLE; Oracle 默认的事务隔离级别为:READ COMMITED(读已经提交了的) Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ(可以反复读的)