背景: 公司的dev的sybase升级的,以前能够好好运行的spring data里面SimpleJpaRepository的方法,现在都报错
Caused by: java.sql.SQLException: Failed to execute COMMIT TRANSACTION, because this is a read-only connection. Read-only connections can only execute SELECT statements. Use 'set readonly off' to execute COMMIT TRANSACTION.
at com.sybase.jdbc4.jdbc.SybConnection.getAllExceptions(SybConnection.java:2780)
at com.sybase.jdbc4.jdbc.SybConnection.handleSQLE(SybConnection.java:2648)
at com.sybase.jdbc4.jdbc.SybConnection.commit(SybConnection.java:1635)
at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:81)
... 106 more
发现错误的原因
一路跟踪代码里面发现SimpleJpaRepository的定义是这样的
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID>
然后@Transactional里面事物的默认propagation是Propagation.REQUIRED;,所以这个类里面的所有方法如果调用方没有事物,就会创建一个新的事物,包括findAll(), findById这些查询方法,并且设置连接的readOnly为true。这种事物叫做只读事物(一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务)
然后看看代码里面sybase设置连接只读属性的地方在类SybConnection
public void setReadOnly(boolean var1) throws SQLException {
if (LogUtil.isLoggingEnabled(LOG)) {
if (LOG.isLoggable(Level.FINER)) {
LOG.finer(this._logId + " setReadOnly(boolean = [" + var1 + "])");
} else if (LOG.isLoggable(Level.FINE)) {
LOG.fine(this._logId + " setReadOnly(boolean)");
}
}
this.checkConnection();
try {
this._protocol.setOption((ProtocolContext)null, 3, var1);
} catch (SQLException var3) {
this.handleSQLE(var3);
}
this._readOnly = var1;
}
然后继续往下追踪发现设置readyOnly执行在sql在这个里面
select query from master..spt_mda where mdinfo='SET_READONLY_FALSE' or mdinfo='SET_READONLY_TRUE'
sybase没升级之前这个查询出来是空的,现在查询出来为set readonly on, 所以当调用SimpleJpaRepository的findAll方法的时候相当是执行下面的sql
set readonly ON
BEGIN TRAN
SELECT * FROM xxxx
COMMIT TRAN
这样就会报下面的错 Error (456) Failed to execute BEGIN TRANSACTION, because this is a read-only connection. Read-only connections can only execute SELECT statements. Use ‘set readonly off’ to execute BEGIN TRANSACTION.
可见sybase升级后不支持spring的只读事物
解决方案
- 最简单的解决方案是在你写的jpa类上面在加一个@Transactional,并在设置propagation为SUPPORTS, spring在找事物注解的时候,是先找单前接口,找不到才找父类的,这样你写的@Transactional注解就会覆盖掉SimpleJpaRepository上面的注解,并且,如果查询没带事物这里就不会创建一个事物。
@Repository
@Transactional(propagation = Propagation.SUPPORTS)
public interface xxxx extends JpaRepository<xx, String>
- 第二种解决方法麻烦点,但是改动更加少,你写一个和SimpleJpaRepository一摸一样的类,什么类名包名完全一样,只是修改下头上的@Transactional注解,改成
@Repository
@Transactional(
readOnly = true,
propagation = Propagation.SUPPORTS
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID>
然后导出这个jar包,注意这个jar包的包路径一定要在org.springframework.data:spring-data-jpa:2.0.0的前面
包路径越靠前,越先被加载。换句话说,如果靠前的jar包里的类被加载了,后面jar包里有同名同路径的类,就会被忽略掉,不会被加载。 这样你就用自己的SimpleJpaRepository替换掉了spring data自带的SimpleJpaRepository
|