4

我已经用@Transactional readonly=true.

因为那个 spring/hibernate 没有调用 jdbc 连接驱动程序的 setReadonly 方法。我能做些什么?

因为我将使用主从复制,并且 jdbc 池使用连接上的只读标志将查询路由到主服务器或从服务器。

4

2 回答 2

2

首先,当您的 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_CLOSEAFTER_TRANSACTION 释放模式HibernateTransactionManager.isSameConnectionForEntireSession()更改为返回 true :关于 HibernateTransactionManager,在事务完成后无论如何都会进行清理,因此不会改变 Hibernate 行为。

于 2013-07-22T09:47:19.520 回答
0

这里提到了两个值得研究的解决方案:http ://www.dragishak.com/?p=307

  1. 使用 AOP 将 JDBC 连接设置为只读。这是博文的重点。
  2. 使用连接池中的钩子根据TransactionSynchronizationManager.isCurrentTransactionReadOnly(). 这取决于您使用的连接池实现(例如 BoneCP、c3p0,不确定 DBCP 是否支持它)。请参阅上面链接的评论部分。
于 2014-02-12T19:18:13.383 回答