轻量级Java EE企业应用开发实战
上QQ阅读APP看书,第一时间看更新

5.4 事务管理

事务用来提供数据集成性、正确的应用语义以及并发访问时数据的一致性视图。所有符合JDBC规范的驱动都必须支持事务,JDBC的事务管理API参照SQL:2003标准并且包含以下概念:

  • 自动提交模式。
  • 事务隔离级别。
  • 保存点(Savepoint)。

5.4.1 事务边界和自动提交

什么时候应该开启一个事务是JDBC驱动或者底层的数据源做的一个隐式的决定,尽管有一些数据源支持begin transaction语句,但这个语句没有对应的JDBC API。如果一条SQL语句要求开启一个事务并且当前没有事务未执行完,那么新事务就会被开启。

Connection有一个属性autocommit来表明什么时候应该结束事务。如果autocommit启用,那么每一条SQL语句完全执行后都会自动执行事务的提交。以下几种情况视为完全执行:

  • DML语句(例如Insert、Update、Delete)和DDL语句在数据源端执行完毕就代表语句完全执行。
  • 对于Select语句来说,完全执行意味着对应的结果集被关闭。
  • 对于CallableStatement对象或者那些返回多个结果集的语句,完全执行意味着所有的结果集都关闭,以及所有的影响行数和出参都被获取到了。

5.4.2 关闭自动提交模式

以下代码示范了如何关闭自动提交模式:

    // Assume con is a Connection object
    con.setAutoCommit(false);

当关闭自动提交时,必须显式地调用Connection的commit方法提交事务或者调用rollback方法回滚事务。这种处理方式是合理的,因为事务的管理工作不是驱动应该做的,应用层应该自己管理事务,例如:

  • 当应用需要将一组SQL组成一个事务的时候。
  • 当应用服务器管理事务的时候。

autocommit的默认值为true,如果在一个事务中autocommit的值被改变了,那么将会导致当前事务被提交。如果调用了setAutocommit方法,但没有改变原来的值,就不会产生其他附加影响,相当于没有调过一样。

如果一条连接参加了分布式事务,那么autocommit不能设置为true。

5.4.3 事务隔离级别

事务隔离级别定义在一个事务中哪些数据是对当前执行的语句“可见”的。在并发访问数据库时,事务隔离级别定义了多个事务之间对于同一个目标数据源访问时的可交叉程度。可交叉程度可分为以下几类:

  • dirty read(脏读):当一个事务能看见另一个事务未提交的数据时,就称为脏读,换言之,一个事务修改数据后,在未提交之前,就能被其他事务看见,如果这个事务被回滚了,而不是提交了,那么其他事务看到的数据是不正确的,是“脏”的。
  • nonrepeatable read(不可重复读):假设事务A读取了一行数据,接下来事务B改变了这行数据,之后事务A再一次读取这行数据,这时候事务A就取到了两个不同的结果。
  • phantom read(幻读):假设事务A通过一个where条件读取到了一个结果集,事务B这时插入了一条符合事务A的where条件的数据,之后事务A通过同样的where条件再次进行查询时会发现多出来一条数据。

JDBC规范增加了TRANSACTION_NONE隔离级别,满足了SQL:2003定义的4种事务隔离级别。隔离级别从最宽松到最严格,排序如下:

  • TRANSACTION_NONE:这意味着当前的JDBC驱动不支持事务,也意味着这个驱动不符合JDBC规范。
  • TRANSACTION_READ_UNCOMMITTED:允许事务看到其他事务修改了但未提交的数据,这意味着有可能是脏读、不可重复读或者幻读。
  • TRANSACTION_READ_COMMITTED:一个事务在未提交之前,所做的修改不会被其他事务看见。这能避免脏读,但避免不了不可重复读和幻读。
  • TRANSACTION_REPEATABLE_READ:避免了脏读和不可重复读,但幻读依然是有可能发生的。
  • TRANSACTION_SERIALIZABLE:避免了脏读、不可重复读以及幻读。

一条连接的默认事务隔离级别是由驱动决定的,这个隔离级别往往是底层的数据源默认的事务隔离级别。应用程序可以使用Connection类里的setTransactionIsolation方法来改变一条连接的事务隔离级别。在一个事务中调用setTransactionIsolation方法会有什么样的结果完全由驱动的实现决定。

可能有些驱动实现并不支持所有的4种事务隔离级别,如果通过setTransactionIsolation方法设置的隔离级别驱动不支持的话,驱动就可以主动将事务隔离级别设置为更高、更严格的事务隔离级别,如果没法设置为更高或者更严格的事务隔离级别,驱动就应该抛出SQLException。可以使用DatabaseMetaData的supportsTransactionIsolationLevel方法来判断驱动是否支持某个事务隔离级别。

5.4.4 性能考虑

事务隔离级别设置得越高,为了保证事务的正确语义,意味着会有更多的锁等待、锁竞争以及DBMS的附加损耗。这反过来也会降低并发访问性,所以应用程序可能会发现事务隔离级别越高,性能反而会下降。为此,事务的管理者应该权衡两者的利弊,设置合理的事务隔离级别。

5.4.5 保存点

我们可以在一个事务的中间设置一个保存点来更灵活地控制事务。一旦事务设置了一个保存点,事务就可以回滚到这个保存点,不会影响保存点之前的操作。可以使用DatabaseMetaData.supportsSavepoints方法来判断驱动或者数据库是否支持这个功能。

1.设置保存点

Connection.setSavepoint方法可以用来在当前事务中设置一个保存点,如果当前方法没有在事务中,则调用这个方法能开启一个事务。Connection.rollback方法有一个重载版本,能够接收一个保存点作为参数。

上面的代码实例中,插入一行数据后,保存一个保存点,再插入一行数据。当事务被回滚到保存点的时候,第二行数据不会被插入,第一行数据依然会被插入。当连接提交后,第一行数据将会保存在表里。

2.释放保存点

Connection.releaseSavepoint方法接收一个保存点作为参数,删除这个保存点以及在它之后的保存点。如果一个保存点已经被释放了,还把它作为rollback的参数,就会导致SQLException。当事务提交或者完全回滚的时候,所有的保存点都会被自动释放。当回滚到某个保存点后,这个保存点以及在它之后定义的保存点都会被自动释放掉。