Spring:@Transactional with readonly = true not call conn.setReadOnly(true)

时间:2012-04-22 20:01:27

标签: spring hibernate transactions readonly

我已使用@Transactional readonly=true注释了我的服务方法。

因为spring / hibernate没有调用jdbc连接驱动程序的setReadonly方法。我该怎么办?

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

2 个答案:

答案 0 :(得分:2)

首先,只有在PU事务模式为RESOURCE_LOCAL时,才应将readOnly标志设置为JDBC连接。如果它是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不会更改返回连接的readOnly设置,但如果仅运行本地事务,则可以执行此操作。

以下是实现目标的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。实际上,我认为应该更改HibernateTransactionManager.isSameConnectionForEntireSession()以对ON_CLOSE AFTER_TRANSACTION释放模式返回true:对于HibernateTransactionManager,在事务完成后无论如何都会发生清理,因此不会改变Hibernate行为。 / p>

答案 1 :(得分:0)

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

  1. 使用AOP将JDBC连接设置为readOnly。这是博客文章的重点。
  2. 使用连接池中的挂钩根据TransactionSynchronizationManager.isCurrentTransactionReadOnly()选择不同的连接。这取决于您正在使用的连接池实现(例如BoneCP,c3p0,不确定它是否在DBCP中受支持)。请参阅上面链接的评论部分。