Java JDBC事务机制
首先,我们来看看现有JDBC操作会给我们打来什么重大问题,比如有一个业务:当我们修改一个信息后再去查询这个信息,看是这是一个简单的业务,实现起来也非常容易,但当这个业务放在多线程高并发的平台下,问题自然就出现了,比如当我们执行了一个修改后,在执行查询之前有一个线程也执行了修改语句,这是我们再执行查询,看到的信息就有可能与我们修改的不同,为了解决这一问题,我们必须引入JDBC事务机制,其实代码实现上很简单,一下给出一个原理实现例子供大家参考:
private Connection conn = null; private PreparedStatement ps = null; try { conn.setAutoCommit(false); //将自动提交设置为false ps.executeUpdate("修改SQL"); //执行修改操作 ps.executeQuery("查询SQL"); //执行查询操作 conn.commit(); //当两个操作成功后手动提交 } catch (Exception e) { conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功 e.printStackTrace(); }
与事务相关的理论
1.事务(Transaction)的四个属性(ACID)
原子性(Atomic) 对数据的修改要么全部执行,要么全部不执行。
一致性(Consistent) 在事务执行前后,数据状态保持一致性。
隔离性(Isolated) 一个事务的处理不能影响另一个事务的处理。
持续性(Durable) 事务处理结束,其效果在数据库中持久化。
2.事务并发处理可能引起的问题
脏读(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操作都是一个事务。
一个独立SQL操作什么时候算执行完毕,JDBC规范是这样规定的:
对数据操作语言(DML,如insert,update,delete)和数据定义语言(如create,drop),语句一执行完就视为执行完毕。
对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。
对存储过程或其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。
b. 当auto-commit为false时,每个事务都必须显示调用commit方法进行提交,或者显示调用rollback方法进行回滚。auto-commit默认为true。
默认的JDBC事务是自动提交的,故我们在获取数据库连接后,应该将连接改为非自动提交模式:
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException { PreparedStatement updateSales = null; PreparedStatement updateTotal = null; String updateString = "update " + dbName + ".COFFEES " + "set SALES = ? where COF_NAME = ?"; String updateStatement = "update " + dbName + ".COFFEES " + "set TOTAL = TOTAL + ? " + "where COF_NAME = ?"; try { con.setAutoCommit(false); updateSales = con.prepareStatement(updateString); updateTotal = con.prepareStatement(updateStatement); for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) { updateSales.setInt(1, e.getValue().intValue()); updateSales.setString(2, e.getKey()); updateSales.executeUpdate(); updateTotal.setInt(1, e.getValue().intValue()); updateTotal.setString(2, e.getKey()); updateTotal.executeUpdate(); con.commit(); } } catch (SQLException e ) { JDBCTutorialUtilities.printSQLException(e); if (con != null) { try { System.err.print("Transaction is being rolled back"); con.rollback(); } catch(SQLException excep) { JDBCTutorialUtilities.printSQLException(excep); } } } finally { if (updateSales != null) { updateSales.close(); } if (updateTotal != null) { updateTotal.close(); } con.setAutoCommit(true); } }
(2) 事务隔离级别(Transaction Isolation Levels)
JDBC定义了五种事务隔离级别:
TRANSACTION_NONE JDBC驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 允许脏读、不可重复读和幻读。
TRANSACTION_READ_COMMITTED 禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 禁止脏读和不可重复读,单运行幻读。
TRANSACTION_SERIALIZABLE 禁止脏读、不可重复读和幻读。
Isolation Level | Transactions | Dirty Reads | Non-Repeatable Reads | Phantom Reads |
---|---|---|---|---|
TRANSACTION_NONE |
Not supported | Not applicable | Not applicable | Not applicable |
TRANSACTION_READ_COMMITTED |
Supported | Prevented | Allowed | Allowed |
TRANSACTION_READ_UNCOMMITTED |
Supported | Allowed | Allowed | Allowed |
TRANSACTION_REPEATABLE_READ |
Supported | Prevented | Prevented | Allowed |
TRANSACTION_SERIALIZABLE |
Supported | Prevented | Prevented | Prevented |
Oracle 支持TRANSACTION_READ_UNCOMMITTED和TRANSACTION_SERIALIZABLE,默认为TRANSACTION_READ_UNCOMMITTED。
使用如下方法控制连接的独立性等级:
conn.setTransactionIsolation(Connection.TRANSACTION_XXX);
(3) 保存点(SavePoint)
JDBC定义了SavePoint接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback到该保存点处的状态,而不是rollback整个事务。
Connection接口的setSavepoint和releaseSavepoint方法可以设置和释放保存点。
在JDBC 3.0新增功能中,提供对事务commit和rollback的更好支持,在事务过程中,在两个操作间可以插入一个命名的存储点作为标记,因此可以将事务回滚到那个标记,保留标记有效前的所有操作。
示例:
conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.executeUpdate(update1); Savepoint point1 = conn.setSavepoint("point1"); stmt.executeUpdate(update2); stmt.executeUpdate(update3); conn.rollback(point1); conn.commit();
JDBC规范虽然定义了事务的以上支持行为,但是各个JDBC驱动,数据库厂商对事务的支持程度可能各不相同。如果在程序中任意设置,可能得不到想要的效果。为此,JDBC提供了DatabaseMetaData接口,提供了一系列JDBC特性支持情况的获取方法。比如,通过DatabaseMetaData.supportsTransactionIsolationLevel方法可以判断对事务隔离级别的支持情况,通过DatabaseMetaData.supportsSavepoints方法可以判断对保存点的支持情况。
参考:
【1】Java的JDBC事务详解 http://blog.csdn.net/chenyongsuda/article/details/5641412
【2】JDBC-事务 http://blog.csdn.net/oswin_jiang/article/details/3275370
【3】Using Transactions http://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html