我已经用@Transactional
readonly=true
.
因为那个 spring/hibernate 没有调用 jdbc 连接驱动程序的 setReadonly 方法。我能做些什么?
因为我将使用主从复制,并且 jdbc 池使用连接上的只读标志将查询路由到主服务器或从服务器。
我已经用@Transactional
readonly=true
.
因为那个 spring/hibernate 没有调用 jdbc 连接驱动程序的 setReadonly 方法。我能做些什么?
因为我将使用主从复制,并且 jdbc 池使用连接上的只读标志将查询路由到主服务器或从服务器。
首先,当您的 PU 事务模式为 RESOURCE_LOCAL 时,您应该只为 JDBC 连接设置 readOnly 标志。如果它是 JTA,那么您不能更改该设置,因为您不会为事务中的每个 jdbc 调用获得相同的 jdbc 连接实例(JTA - 而不是 Hibernate - 将确保事务行为)。当它是 LOCAL 时,Hibernate 在第一次需要它时打开一个 jdbc 连接,并在事务期间保留它。
1. JPA
如果您将 JPA 与 Hibernate 作为提供程序一起使用,则可以通过将您自己的 JpaDialect 实现提供给 EMF 定义来添加此额外行为。使用 Hibernate 时,通常会注入一个 HibernateJpaDialect。
JpaDialect 接口有一个getJdbcConnection(em, readOnly)
方法可以返回实际 JDBC 连接的句柄。此方法在事务开始时由 JpaTransactionManager 调用。默认情况下,由于这种 JTA/RESOURCE_LOCAL 对偶性,HibernateJpaDialect 不会更改返回连接上的只读设置,但如果您只运行本地事务,则可以这样做。
这是实现您的目标的此类 JpaDialect 的实现:
ResourceLocalReadOnlyAwareHibernateJpaDialect
public class ResourceLocalReadOnlyAwareHibernateJpaDialect extends HibernateJpaDialect {
public ConnectionHandle getJdbcConnection(EntityManager entityManager, boolean readOnly) throws PersistenceException, SQLException {
Session session = getSession(entityManager);
return new HibernateReadOnlyAwareConnectionHandle(session, readOnly);
}
// this is similar to spring's HibernateJpaDialect own internal class,
// except for the readonly flags.
private static class HibernateReadOnlyAwareConnectionHandleimplements ConnectionHandle {
private final Session session;
private final boolean readOnly;
private static volatile Method connectionMethod;
public HibernateConnectionHandle(Session session, boolean readOnly) {
this.session = session;
this.readOnly = readOnly;
}
public Connection getConnection() {
try {
if (connectionMethod == null) {
// reflective lookup to bridge between Hibernate 3.x and 4.x
connectionMethod = this.session.getClass().getMethod("connection");
}
Connection con = (Connection) ReflectionUtils.invokeMethod(connectionMethod, this.session);
con.setReadOnly(this.readOnly);
return con;
} catch (NoSuchMethodException ex) {
throw new IllegalStateException("Cannot find connection() method on Hibernate session", ex);
}
}
public void releaseConnection(Connection con) { // #1
con.setReadOnly(false);
JdbcUtils.closeConnection(con);
}
}
}
注意#1:在关闭连接之前将 readOnly 标志重置为 false (实际上不是真正的connection.close()
调用,只是将连接释放到池中)。不太确定是什么触发了这个方法调用,但是在与更改位置相同的类中重置 readOnly 标志看起来是合法的。
2.纯休眠
首先,确保HibernateTransactionManager.prepareConnection
保持真实。
然后,我不知道该怎么办。您必须调试 Spring 的HibernateTransactionManager.isSameConnectionForEntireSession()
:如果该方法返回 true,将调用 connection.setReadOnly(),因此一切正常。
如果没有,您可以将 Hibernate 的 connectionReleaseMode 设置更改为 ON_CLOSE(hibernate 属性hibernate.transaction.auto_close_session=true
,这是 Hibernate 3.1 之前的默认值),或者覆盖HibernateTransactionManager.isSameConnectionForEntireSession()
以始终返回 true(对于 HibernateTransactionManager 注释而言,这被认为是安全的)。两者都是“高级调整”,但应该是安全的 AFAIK。实际上,我认为应该将 ON_CLOSE和AFTER_TRANSACTION 释放模式HibernateTransactionManager.isSameConnectionForEntireSession()
更改为返回 true :关于 HibernateTransactionManager,在事务完成后无论如何都会进行清理,因此不会改变 Hibernate 行为。
这里提到了两个值得研究的解决方案:http ://www.dragishak.com/?p=307
TransactionSynchronizationManager.isCurrentTransactionReadOnly()
. 这取决于您使用的连接池实现(例如 BoneCP、c3p0,不确定 DBCP 是否支持它)。请参阅上面链接的评论部分。