JDBC学习:事务

什么是事务

数据库中一些操作的集合是一个独立的单元,事务就是构成单一逻辑工作单位的集合。

为什么需要事务

事务是为解决数据安全操作提出的,事务控制实际上就是控制数据的安全访问。
比如:银行转帐业务,账户A给账户B转帐100元,需要账户A余额减100元,账户B余额加100元,两个需要同时发生。完成这种操作需要保证要么全部成功,要么全部失败。

什么是回滚

未能成功完成的事务成为中止事务,对中止事务造成的变更需要进行撤销处理,称为事务回滚。

事务的特性(ACID 原则)

  • 原子性(atomicity):对事务中的全部操作是不可分割的,要么全部完成,要么都不执行。
  • 一致性(consistency):事务执行之前和执行之后,数据库都必须处于一致性状态。
  • 隔离性(isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
  • 持久性(durability):对于任意已提交的事务,系统必须保证该对数据库的改变不丢失,即使数据库出现故障。

Java JDBC 事务机制
比如有个业务:当我们修改一个信息后再去查询这个信息。这是一个简单的业务,实现起来也非常容易,但是当这个业务放在多线程高并发的平台下,问题自然就出现了。
比如当执行了一个修改后,在查询之前有一个线程也执行了修改语句,这时再执行查询,看到的信息就有可能和我们修改的不同。为了解决这一问题,就引入了引入JDBC事务机制。

如何操作

把事务操作设置为不自动提交,通过手动提交就能实现事务的处理。

搭建实验环境实验,在当前数据库中创建一个测试表:

CREATE TABLE tb (
    id INT UNSIGNED NOT NULL AUTO_INCREMENT KEY COMMENT '编号',
    name VARCHAR(20) NOT NULL COMMENT '姓名',
    sex VARCHAR(4) NOT NULL COMMENT '性别',
    phone VARCHAR(11) NOT NULL COMMENT '手机号码'
);

插入几条测试数据:

INSERT INTO tb
(name, sex, phone)
VALUES
("张三", "男", "139****2234"),
("李四", "女", "130****3239"),
("王二", "男", "136****1234"),
("小王", "女", "137****1735"),
("赵云", "男", "131****1255"),
("关羽", "男", "139****1930");

实验代码
JDBC学习:事务

相关延伸

事务并发处理可能引起的问题

  • 脏读(dirty read):一个事务读取了另一个事务尚未提交的数据。
  • 不可重复读(non-repeatable read):一个事务的操作导致另一个事务前后两次读取到不同的数据。
  • 幻读(phantom read):一个事务的操作导致另一个事务前后两次查询到的结果数据量不同。

举例:

  • 事务A、B并发执行,当A事务update后,B事务select读取到A尚未提交的数据,此时A事务rollback,则B读取到的数据是无效的“脏”数据。
  • 当B事务select读取数据后,A事务update操作更改B事务select到的数据,此时B事务再次读取该数据,发现前后两次的数据不一样。
  • 当B事务select读取数据后,A事务insert或delete了一条满足A事务的select条件的记录,此时B事务再次select,发现查询到前不存在的数据(“幻影”),或者前面的某个记录不见了。

JDBC的事务支持

JDBC对事务的支持体现在三个方面:

1.自动提交模式(Auto-commit mode)

Connection提供了一个auto-commit的属性来指定事务何时结束。

  • a.当auto-commit为true时,当每个独立的SQL操作执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务的。auto-commit默认为true

一个独立SQL操作什么时候什么执行完毕呢?
JDBC规范这样规定

对数据操作语言(DML如insert,update,delete)和数据定义语言(DDL如create,drop),语句一执行完就视为执行完毕。

对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。
对存储过程或者其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。

  • b.当auto-commit为false时,每个事务都必须显式调用commit方法进行提交,或者显式调用commit方法进行回滚。

2.事务隔离级别(Transaction Isolation Levels)

JDBC提供了5种不同的事务隔离级别。在Connection中进行了定义。

  • TRANSACTION_NONE:JDBC不支持事务
  • TRANSACTION_READ_UNCOMMITTED:允许脏读、不可重复读和幻读
  • TRANSACTION_READ_COMMITTED:禁止脏读,但允许不可重复读和幻读
  • TRANSACTION_REPEATABLE_READ:禁止脏读和不可重复读,单可以幻读
  • TRANSACTION_SERIALIZABLE:禁止脏读、不可重复读和幻读

3.保存点(SavePoint)

JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。

连接对象获取和关闭的时机

现状:

  • 连接对象的获取和销毁比较浪费时间
  • 一个事务中多个操作,若每个操作都生成一个连接对象,多用户同时连接数据库时,访问效率会非常的低。

需求:

  • 一个事务中的多个操作应该用同一个连接对象控制,不然无法实现提交和回滚。一个事务开始时获得连接对象,一个事务结束时关闭连接。

解决:

  1. 将连接对象的申请交给专门的连接管理类
    事务申请连接对象时,通过连接管理类申请,而不再通过DriverManager申请
  2. 一个事务一定是由一个线程完成的,使用线程局部变量获得同一个连接对象

接口DataSource

该工厂用于提供到此 DataSource 对象所表示的物理数据源的连接。

作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。

DataSource接口由驱动程序供应商实现。共有三种类型的实现:

  • 基本实现:生成标准的 Connection 对象
  • 连接池实现:生成自动参与连接池的 Connection 对象。此实现与中间层连接池管理器一起使用。
  • 分布式事务实现:生成一个 Connection 对象,该对象可用于分布式事务,大多数情况下总是参与连接池。此实现与中间层事务管理器一起使用,大多数情况下总是与连接池管理器一起使用。

方法:

  • getConnection():尝试建立与此 DataSource 对象所表示的数据源的连接。
  • getConnection(String username, String password):尝试建立与此 DataSource 对象所表示的数据源的连接。

实例:
先在src目录下新建配置文件config.properties:

driver=com.mysql.jdbc.Driver
url=jdbc\:mysql\://127.0.0.1\:3306/empmgs?useUnicode\=true&characterEncoding\=utf-8
username=root
password=root

实例代码:

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;

public class demo4 {
    static Properties prop = new Properties();
    static DataSource ds = null;
    static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
    private static Connection conn = null;
    private static Statement sm = null;
    /**
     * 静态初始化块加载注册驱动
     */
    static {
        try {
            prop.load(new FileInputStream("src/config.properties"));
            ds = BasicDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    static public Connection getConnection(){
        try {
            //先看线程局部变量
            conn = threadLocal.get();
            if(conn == null){//线程局部变量中没有保存连接对象
                conn = ds.getConnection();
                threadLocal.set(conn);//设置连接对象到线程局部变量
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }
    public static void main(String[] args) {
        try{
            conn = getConnection ();
            sm = conn.createStatement();
            // 关闭自动提交
            conn.setAutoCommit(false);
            sm.executeUpdate("UPDATE tb SET name = '老王' WHERE id = 3 ");
            sm.executeUpdate("INSERT INTO tb (name, sex, phone) VALUES ('美丽','女','132****5555')");
            conn.commit();
            System.out.println("运行结束");
        } catch (Exception e) {
            try {
                conn.rollback();
            } catch (Exception ex) {
                e.printStackTrace();
            }
        } finally {
            if (sm != null) {
                try {
                    sm.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }        
            if (conn != null) {
                try {
                    conn.close();
                    conn = null;
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
上一篇:【测试_需求测试】什么是需求测试、如何做好需求测试


下一篇:【信息安全_其他】JAVA(Android),PHP,IOS三平台RSA加解密互通算法