标签:
事务的作用是保护数据完整性.它将一系列操作作为一个原子操作来处理, 从而防止多个操作中有的操作失败有的操作成功, 造成数据不完整. 在 JDBC 中, 一个连接默认的提交方式是 Auto-Commit 模式, 在这种模式下, 每个单独的SQL语句都会被视为一个事务, 当SQL执行时(更确切的说是完成时,详见 这里), 就会被提交到数据库. 然而, 在要求多个 SQL 同时成功或者失败的数据完整性问题时, 这种模式不能满足需要.
JDBC 为我们提供了手动管理事务的方式. 在取得 Connection 后, 调用:
conn.setAutoCommit(false);
就可以关闭自动提交, 同时, 这也开启了一个事务.
当 Auto-Commit 禁用时, 我们需要自己管理事务, 提交事务的 API 为:
conn.commit()
调用后, 事务内所有的 SQL 语句将会被视为一个原子被提交.
然而, 执行 SQL 的时候也有可能出现异常, 异常产生后, 我们需要保持数据完整性, 要回滚已经做的改变:
conn.rollback();
调用 conn.rollback() 的时机是在 catch
语句内, 因为当有SQL执行失败时, 会有SQLException
抛出,这也是唯一知道是否成功提交与否的途径. 所以将 conn.rollback() 写在 catch
中吧.
Note: 已经 commit
的事务, 无法再 rollback
.
利用事务,我们可以保证一系列操作的原子性, 但是却不能保证多个事务之间的影响. 考虑以下情况:
1) 当事务A在更新表的一行数据, 当执行更新后但是在提交事务前, 另外一个事务B来读走了数据, 这时事务A发现自己更新的数据有问题, 并没有做提交, 而是回滚了刚才的操作, 事务B实际上读到的是未提交的数据.
2) 当事务A读取了一个行数据, 之后事务B对这行数据进行了修改并提交, 这时事务A又重读了一次这行, 发现同一个事务内两次读到数据不一样.
3) 当事务A根据条件查询了若干行数据, 之后事务B插入/更新/删除了同一个表中的几行数据, 而事务B中修改的数据恰好有几条符合事务A的查询条件, 这时事务A由根据之前的条件重新查询, 发现同一个事务根据同一查询条件查到的结果却不同.
第一种现象叫 Dirty Read(脏读). 第二种现象叫 Repeatable Read(重复读). 第三种现象叫 Phantom Read(幻读).
解决这三种现象是数据库的锁机制, 比如行级锁, 表级锁, 但是各个数据库的实现方法不同. JDBC中我们可以控制事务的隔离级别, 也就是锁的各种机制, 来避免这三种现象. 相关API为:
conn.setTransactionIsolation(/*...*/);
可以传入的隔离级别有:
值得注意的是, 并不是隔离级别越高越好, 隔离级别越高, 数据越精确, 但是由于锁的范围也会相对变大, 数据多的话与数据库I/O的开销也会越大. 所以选择合适的隔离等级也是提升系统性能的关键之一.
对于事务的隔离级别, JDBC 并不是要求每种都支持的, 厂商可以有不同的方式以及实现情况, 可以调用 DatabaseMetaData.supportsTransactionIsolationLevel()
来查看支持情况. 调用 DatabaseMetaData.getDefaultTransactionIsolation
可以得到默认的隔离级别.
以下代码示例如何获取隔离级别的支持情况:
public void printTransactionIsolation() throws SQLException { Connection conn = getConnection(); DatabaseMetaData md = conn.getMetaData(); System.out.println("TRANSACTION NONE: " + md.supportsTransactionIsolationLevel(Connection.TRANSACTION_NONE)); System.out.println("TRANSACTION READ COMMITTED: " + md.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_COMMITTED)); System.out.println("TRANSACTION READ UNCOMMITTED: " + md.supportsTransactionIsolationLevel(Connection.TRANSACTION_READ_UNCOMMITTED)); System.out.println("TRANSACTION REPEATABLE READ: " + md.supportsTransactionIsolationLevel(Connection.TRANSACTION_REPEATABLE_READ)); System.out.println("TRANSACTION SERIALIZABLE: " + md.supportsTransactionIsolationLevel(Connection.TRANSACTION_SERIALIZABLE)); System.out.print("Default Transaction: "); switch(md.getDefaultTransactionIsolation()) { case Connection.TRANSACTION_NONE: System.out.println("None"); break; case Connection.TRANSACTION_READ_COMMITTED: System.out.println("Read Commit"); break; case Connection.TRANSACTION_READ_UNCOMMITTED: System.out.println("Read Uncommit"); break; case Connection.TRANSACTION_REPEATABLE_READ: System.out.println("Repeatable read"); break; case Connection.TRANSACTION_SERIALIZABLE: System.out.println("Serializable"); break; } conn.close(); }
Mysql 和 JavaDB 的输出结果都为:
TRANSACTION NONE: false
TRANSACTION READ COMMITTED: true
TRANSACTION READ UNCOMMITTED: true
TRANSACTION REPEATABLE READ: true
TRANSACTION SERIALIZABLE: true
Default Transaction: Read Commit
JDBC 3.0 引入了 Savepoint 接口. 我们可以在代码中设立 Savepoint 然后根据条件 rollback 到某个点, 这样的好处是可以更灵活的控制事务的提交内容. 创建检查点的API为:
Savepoint svpt1 = conn.setSavepoint();
回滚到某一检查点的 API 为:
conn.rollback(svpt1);
释放某个检查点的 API 为:
conn.releaseSavepoint(svpt1);
Note: 当整个事务提交或回滚时, 事务内的 Savepoint 会自动失效. 当事务回滚到某个 Savepoint 时, 之后创建的 Savepoint 也会释放.
标签:
原文地址:http://www.cnblogs.com/pragmatic/p/4249068.html