JDBC隔离级别基础教程

  通过本教程学习JDBC隔离级别的各种情形,在上篇数据库悲观锁乐观锁中,我们演示了乐观锁与悲观锁的使用演示。因此锁的存在,会导致读操作与写操作的各种组合操作的不同情况,本章节讨论 "幻影读phantom reads", "非重复读non-repeatable reads", 或 "脏读dirty reads"。

  在现实中,隔离级别设置依赖于你的数据库和JDBC驱动,不同的数据库有不同的默认设置,H2数据库仅仅允许你设置一个事务隔离级别 READ_COMMITTED: 如果你有两个同时事务,其中一个事务只能看到另外一个事务中确认提交committed过的数据。

  看看下面代码,虽然不能正常工作,但是其中的注解解释了很多原理,你可以更换其他数据库如MySQL可以正常工作。

import org.junit.Before;
import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* @author Marco Behler
*/
public class IsolationLevelExercise {

 

    @Before
    public void setUp() {
        try (Connection connection = getConnection()) {
            createTables(connection);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void isolation_level_exercise() throws SQLException {
        System.out.println("Do we reach the end of the test without a " +
                                        "deadlock?...");
        // 插入一个item
        try (Connection connectionFromJackBauer = getConnection()) {
            connectionFromJackBauer.setAutoCommit(false);
            connectionFromJackBauer.createStatement().execute(
                "insert into items " +
                "(name) values ('CTU Field Agent Report')");
            connectionFromJackBauer.commit();
        }

        //然后更新它
    try (Connection connectionFromJackBauer = getConnection()) {
        connectionFromJackBauer.setAutoCommit(false);
        connectionFromJackBauer.createStatement().executeUpdate(
                "update items set name = 'chloes report' " +
                "where name = 'CTU Field Agent Report'");

        try (Connection connectionFromHabibMarwan = getConnection()) {
            // h2很不幸会忽视这行设置,还是回到READ_COMMITTED,它只有这一种方式
            connectionFromHabibMarwan.setTransactionIsolation(
                        Connection.TRANSACTION_READ_UNCOMMITTED);
            connectionFromHabibMarwan.setAutoCommit(false);

             ResultSet resultSet = connectionFromHabibMarwan
                    .createStatement().executeQuery(
                    "select count(*) as count from items " +
                    "where name = 'chloes report'");
             resultSet.next();
             int itemsCount = resultSet.getInt("count");

             System.out.println("Habib can see how many items in the " +
                          "table?: " + itemsCount);


             // 这里应该是结果是1,但是H2是0
             connectionFromHabibMarwan.commit();
        }

        }

        System.out.println("Yes!");
}

 

    private void createTables(Connection conn) {
        try {
                conn.createStatement().execute("create table bids (id " +
                        "identity, user VARCHAR, time TIMESTAMP ," +
                        " amount NUMBER, currency VARCHAR) ");
                conn.createStatement().execute("create table items (id " +
                        "identity, name VARCHAR)");
        } catch (SQLException e) {
                e.printStackTrace();
        }
      }

    // simply what we did in OpenConnectionExerciseJava6/7.java
        private Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:h2:mem:exercise_db;" +
        "DB_CLOSE_DELAY=-1;");
     }

}

运行结果如下:

上面代码是插入一个新纪录后,再修改它,然后在没有确认提交前,查询这条修改的记录,如果隔离级别是READ_UNCOMMITTED,那么可以查询到,但是如果数据库隔离级别是READ_COMMITTED,只有提交后其他事务才能看到,因为修改后没有提交,那么在这种隔离级别下,是查询不到的。

 

JDBC基础教程

JDBC事务锁基础教程

JDBC悲观锁与乐观锁基础教程

SQL入门教程

数据库事务专题

Hibernate专题