数据库事务(java)

1.事务概述

一组要么同时执行成功,要么同时失败的SQL语句。是数据库操作的一个不能分割执行单元。

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

2.事务的四大特性

原子性 A: 不能分割

一致性 C:事务执行前后保持一致

隔离型 I: 事务与事务之间对数据的读取控制

持久性 D:事务提交或回滚之后,永久保存数据库。

案例:转账操作,如果"张三"账户给"李四"账户转钱1000元,当张三账户的钱被减掉,这时程序突然出现异常,从而导致李四没有收到这笔钱,张三亏损1000元。如果想不发生这种情况,必须保证事物的四个特性。一旦中间出现任何异常或者错误,程序回滚到转账前的状态,以确保双方都无损失。

public class Demo1 {
    public static void main(String[] args) {
        Connection conn = DbUtils.getConnection();
        PreparedStatement pstat=null;
        PreparedStatement pstat1 = null;
        try {
            conn.setAutoCommit(false);
            String sql = "update account set money=moeney-1000 where id=?;";
            String sql1 = "update account set money=money+1000 where id=?;";
            pstat = conn.prepareStatement(sql);
            //给第一个执行命令对象的参数赋值
            pstat.setObject(1,1);
            pstat.executeUpdate();
            pstat1 = conn.prepareStatement(sql1);
            //导致程序异常的点,这时回去执行catch语句,进行回滚操作
            int a=10/0;
            //给第二个执行命令对象的参数赋值
            pstat1.setObject(1,2);
            pstat1.executeUpdate();
            conn.commit();
        } catch (SQLException e) {
            System.out.println(e.getMessage());
            try {
                //若发生异常进行回滚,并在此提交正确信息
                conn.rollback();
                conn.commit();
                System.out.println("转账失败!");
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }finally {
            try {
                if(pstat!=null){
                    pstat.close();
                }
                if(pstat1!=null){
                    pstat1.close();
                }
                if(conn!=null){
                    conn.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

Mysql支持的事务语句

#开启事务
START TRANSACTION;     # connection.setAutoCommit(false);
UPDATE account SET money=money-1000 WHERE id=1;
UPDATE account SET money=money+1000 WHERE id=2;
#提交事务
COMMIT;#connection.commit();
#回滚
ROLLBACK; #connection.rollback();

3.事务隔离级别

 

Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别出现不可重复读(Nonrepeatable Read)问题,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read 可重读

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻读” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable 可串行化 ​ 这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。效率最低的。

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。

#修改事务的隔离级别:
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
#查看事务隔离级别
SELECT @@tx_isolation;

不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

案例:详解在代码中注释

# A方 买 本伟
#(1)
SELECT @@tx_isolation;
START TRANSACTION;
UPDATE account SET money=money-2000 WHERE id=1;
UPDATE account SET money=money+2000 WHERE id=2;
#如果卖家隔离级别为读取未提交内容,买家执行完上边语句后,如果不提交,卖家这时也能看到钱数改变,但没有真正写入到数据库中,等到卖家发货,买家进行回滚,钱数就会变回原来的数目。要想避免这种情况发生,必须改变卖家隔离级别。
COMMIT;
ROLLBACK;

(2)
START TRANSACTION;
UPDATE account SET money=money+1000 WHERE id=2;
COMMIT;





# B方  卖  郑帅
#(修改隔离级别)(1)与上边(1)对应,将隔离级别修改为比买家低,将会被骗
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
#查看隔离级别
SELECT @@tx_isolation;
SELECT *FROM account;
#发货
#修改隔离级别 (1)与最上边(1)对应,将隔离级别调成跟买家一样,解决被骗问题
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT *FROM account;

#不可重复读
(2)对应上边(2),如果隔离级别小于repeatable read,可能会使三次计算总钱数不一样,因为在第一次计算总钱数之后,可能会有账户存钱或者取钱,那么第二次总钱数将会变化。要想三次总钱数不受账户存取钱的影响,可以设置为repeatable read级别。
START TRANSACTION;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
COMMIT;	

#再次修改隔离级别

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;


START TRANSACTION;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
	SELECT SUM(money) FROM account;
COMMIT;	

4.JDBC中事物的应用

事务的提交和回滚以及事务保存点的使用

conn.commit();

conn.rollback();

事务保存点:SavePoint  设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。  

Connection对象有两种新的方法来帮助您管理保存点 -

  • setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。

  • releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。(暂时不知道如何使用,在使用该方法后不齐作用,还是回滚到该保存点位置)

案例:

public class Demo2 {
    public static void main(String[] args) {
        Connection conn = DbUtils.getConnection();
        Statement stat = null;
        Savepoint sp1 = null;
        Savepoint sp2 = null;

        try {
            //设置为手动提交
            conn.setAutoCommit(false);
            stat = conn.createStatement();
            stat.executeUpdate("insert into account values(3,'王五',5000);");
            //创建保存点,用连接对象调用方法获得
            sp1 = conn.setSavepoint();
            int a=10/0;
            stat.executeUpdate("insert into account values(4,'赵六',6000);");
            sp2 = conn.setSavepoint();
            stat.executeUpdate("insert into account values(5,'小明',8000);");

            conn.commit();
            System.out.println("插入成功!");
        } catch (Exception e) {
//            e.printStackTrace();
            System.out.println("发生异常....");
            try {
                //如果出现问题,回滚,回滚到保存点
//                conn.rollback();//回滚到初始状态,只要发生异常都不会插入
                conn.rollback(sp1);//回滚到保存点sp1,只要是在保存点sp1之后发生异常,都会插入第一条
//                conn.rollback(sp2);//回滚到保存点sp2,只要是在保存点sp2之后发生异常,都会插入前两条
                conn.commit();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }
}

 

你可能感兴趣的:(Java第二阶段学习总结)