2012-04-22 93 views
4

我已使用@Transactionalreadonly=true註釋了我的服務方法。Spring:@Transactional with readonly = true not call conn.setReadOnly(true)

因爲那個spring/hibernate沒有調用jdbc連接驅動的setReadonly方法。我能做什麼?

由於我將使用主從複製,並且jdbc池使用連接上的readonly標誌將查詢路由到主節點或從節點。

+0

你確定這是最外面的註釋,即只讀'@ Transactional'不是從其他事務代碼調用的? – 2012-04-22 20:04:37

+7

當您使用Hibernate作爲您的ORM供應商'@ Transactional'時,只會將刷新模式設置爲'MANUAL'。即它是「只讀」的,因爲它們從不調用EntityManager.flush()。如果你問我,這是一個非常殘忍的笑話,但它就是這樣。有關'@ Transactional'陷阱的更多信息,請參見[本頁](http://www.ibm.com/developerworks/java/library/j-ts1/index.html)。 – 2012-04-22 23:41:08

+0

我有完全相同的問題與MySQL複製和hibernate.Did你找到任何解決方案? – 2012-06-04 12:52:09

回答

2

首先,當您的PU事務模式爲RESOURCE_LOCAL時,應該只將readOnly標誌設置爲JDBC連接。如果是JTA,那麼您不得更改該設置,因爲您不會爲事務內的每個jdbc調用(JTA - 而不是Hibernate--將確保事務行爲)獲取相同的jdbc連接實例。當它是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:重置只讀標誌設置爲false之前關閉連接(實際上不是一個真正的connection.close()調用,但只是釋放連接池)。不太清楚是什麼觸發了這個方法的調用,但是它重置readOnly標誌與修改它的地方是一樣的。

2.純休眠

首先,確保HibernateTransactionManager.prepareConnection仍是如此。

然後,我不知道該怎麼做。你必須調試到Spring的HibernateTransactionManager.isSameConnectionForEntireSession():如果方法返回true,connection.setReadOnly()將被調用,因此一切正常。

如果沒有,你可以改變Hibernate的connectionReleaseMode設置爲ON_CLOSE(Hibernate屬性hibernate.transaction.auto_close_session=true,這是休眠3.1之前默認),或以始終返回true覆蓋HibernateTransactionManager.isSameConnectionForEntireSession()(這被認爲是安全的問候HibernateTransactionManager的評論) 。兩者都是「高級調諧」,但應該是安全的AFAIK。實際上,我認爲應該將HibernateTransactionManager.isSameConnectionForEntireSession()更改爲對ON_CLOSE AFTER_TRANSACTION發佈模式返回true:對於HibernateTransactionManager,事務完成後無論如何都會發生清除,因此不會更改Hibernate行爲。

0

兩種解決方案值得探討,這裏提到的:http://www.dragishak.com/?p=307

  1. 使用AOP設置JDBC連接只讀。這是博文的重點。
  2. 使用連接池中的掛鉤根據TransactionSynchronizationManager.isCurrentTransactionReadOnly()選擇不同的連接。這取決於您使用哪個連接池實現(例如,BoneCP,c3p0,不確定它是否支持DBCP)。請參閱上面鏈接的評論部分。
相關問題