Spring 事务 readOnly 到底是怎么回事?

Spring的事务经常会有这样的配置:

1 <tx:method name="search*" read-only="true" /> 

或者这样的注记:

1 @Transactional(readOnly = true)

正好我正在做的项目中这样配置了,而且偶然发现配置了不生效,本着“不弄明白对不起祖国对不起人民”的精神,参考了不少帖子和文档,总结了网上形形色色的答案,稍有收获,规整如下,不正确请指出。

1 readonly并不是所有数据库都支持的,不同的数据库下会有不同的结果。
2 设置了readonly后,connection都会被赋予readonly,效果取决于数据库的实现。
3 在ORM中,设置了readonly会赋予一些额外的优化,例如在Hibernate中,会被禁止flush等。

经实践,上面的观点基本正确。

环境:Spring-3.1.1、jdk6、oracle-11gR2、mysql-5.6.16、ojdbc6、mysql-connector-java-5.1.31、ibatis-2.3.4.726等,使用的Spring的DataSourceTransactionManager 事务管理器。

查看DataSourceTransactionManager 相关代码可知readOnly值最终是传给Connection的:

复制代码
 1         // Set read-only flag.
 2         if (definition != null && definition.isReadOnly()) {
 3             try {
 4                 if (logger.isDebugEnabled()) {
 5                     logger.debug("Setting JDBC Connection [" + con + "] read-only");
 6                 }
 7                 con.setReadOnly(true);
 8             }
 9             catch (SQLException ex) {
10                 Throwable exToCheck = ex;
11                 while (exToCheck != null) {
12                     if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
13                         // Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0
14                         throw ex;
15                     }
16                     exToCheck = exToCheck.getCause();
17                 }
18                 // "read-only not supported" SQLException -> ignore, it's just a hint anyway
19                 logger.debug("Could not set JDBC Connection read-only", ex);
20             }
21             catch (RuntimeException ex) {
22                 Throwable exToCheck = ex;
23                 while (exToCheck != null) {
24                     if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
25                         // Assume it's a connection timeout that would otherwise get lost: e.g. from Hibernate
26                         throw ex;
27                     }
28                     exToCheck = exToCheck.getCause();
29                 }
30                 // "read-only not supported" UnsupportedOperationException -> ignore, it's just a hint anyway
31                 logger.debug("Could not set JDBC Connection read-only", ex);
32             }
33         }
复制代码

1、在oracle下测试,发现不支持readOnly,也就是不论Connection里的readOnly属性是true还是false均不影响SQL的增删改查;

2、在mysql下测试,发现支持readOnly,设置为true时,只能查询,若增删改会异常:

1 Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
2     at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:910)
3     at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:792)

3、为了排除各种框架封装的影响,写JDBC原生代码也是相同的结论。

====================疑问的分隔线==============================================

网上的各种资料里众说纷纭:

“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。 
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。 
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可。

---------------------------------------------

早期的提问会有这样的结论:

readOnly对oracle不生效是因为:

1 con.setReadOnly(true);
2 con.setAutoCommit(false);

autoCommit与readOlny赋值的顺序对其有影响,readonly在后则生效,readolny在前是无效的可进行insert/update/delete操作。

同样,DataSourceTransactionManager 里也是因为这个原因:

复制代码
 1             Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
 2             txObject.setPreviousIsolationLevel(previousIsolationLevel);
 3 
 4             // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
 5             // so we don't want to do it unnecessarily (for example if we've explicitly
 6             // configured the connection pool to set it already).
 7             if (con.getAutoCommit()) {
 8                 txObject.setMustRestoreAutoCommit(true);
 9                 if (logger.isDebugEnabled()) {
10                     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
11                 }
12                 con.setAutoCommit(false);
13             }
复制代码

因为DataSourceUtils.prepareConnectionForTransaction(con, definition)里会先设置readOnly属性,导致readOnly对oracle不生效;

-------------------------------------------------

他们的实践结果使我不得不相信他们说的真是这样,但以我现在的环境测试,不管什么顺序均无影响,readOnly就是对oracle不生效;看那些帖子时间已是几年前,版本差别太大,已无从考证……

====================疑问的分隔线==============================================

我也看到有人找到oracle的官方文档链接里句子来说原因:

http://docs.oracle.com/cd/B14117_01/java.101/b10979/tips.htm#i1007231

http://docs.oracle.com/cd/B19306_01/java.102/b14355/apxtips.htm#i1007231

http://docs.oracle.com/cd/B28359_01/java.111/b31224/apxtips.htm#i1007231

http://docs.oracle.com/cd/E11882_01/java.112/e16548/apxtips.htm#i1007231

10.1、10.2和11.1的文档里写着:

 1 Read-only connections are supported by the Oracle server, but not by the Oracle JDBC drivers. 

11.2的文档里写着:

 1 Read-only connections are supported by Oracle JDBC drivers, but not by the Oracle server. 

再结合oracle可以设置 “set transaction read only”,搞不清楚哪个对哪个错,反正就是不支持。。。

 

 

 

setReadOnly 是在DataSourceUtils 中设置的。 
DataSourceUtils.prepareConnectionForTransaction 

posted @ 2018-07-12 19:18  邱明成  阅读(1860)  评论(1编辑  收藏  举报