2014-08-28 64 views
5

長話短說:我們開發和維護一個庫,可以在其他使用JavaEE7/CDI/JPA的項目中使用庫。應用程序將在Glassfish-4.0下運行,並使用Hibernate的JPA實現來實現底層PostgreSQL持久性。這是將Spring/Struts/Hibernate中編寫的舊應用程序重寫入JavaEE7/CDI/JTA新世界的長期遷移工作的一部分。如何攔截JTA交易事件並獲取與交易相關的當前EntityManager的參考

問題:爲了審計目的,我們的庫需要攔截所有數據庫事務並在用戶語句執行之前包含自定義SQL語句。此時,需要將當前用戶名和IP地址插入臨時數據庫變量(供應商特定功能),以便數據庫觸發器可以讀取它們以創建任何行修改的審計跟蹤。 This particular post was very helpful providing alternatives,由於之前建立的遺產,我們的團隊走下了觸發道路。

但是:我們在JTA如何處理事務的事件深感失望。攔截交易的方式有很多種,但這種特殊情況似乎是不可能的。在舊的架構中,使用Spring的事務管理器,我們只需使用一個Hibernate Interceptor實現Interceptor.afterTransactionBegin(...)。閱讀official JTA-1.2 spec,我們發現它支持Synchronization.beforeCompletionSynchronization.afterCompletion。經過幾個小時的調試會話後,我們清楚地注意到Hibernate的JTA實現正在使用這些工具。但JTA似乎缺少像beforeBeginafterBegin(恕我直言,這似乎是缺乏常識)事件。而且由於沒有設施可以攔截這些,所以Hibernate完全符合JTA,它根本不符合。期。

不管我們做什麼,我們都找不到方法。例如,我們嘗試攔截@Transactional註釋,並在容器的JTA impl完成其工作以打開該事務後運行我們的代碼。但是我們缺乏動態獲取與特定事務關聯的EntityManager的能力。請記住:這是一個庫,而不是Web應用程序本身。它不能對應用程序聲明和使用哪個持久性單元做任何假設。而且,據我們所知,我們需要知道將哪個特定的持久單元名稱注入到我們的代碼中。我們正試圖爲其他儘可能透明的temas提供審計工具。

所以我們謙虛地尋求幫助。如果有人有解決方案,解決方法,無論什麼意見,我們會很高興聽到它。

+1

完全繞過JPA並在實際的數據庫連接池上添加一個攔截器?我只知道如何在Tomcat jdbc.pool中做到這一點,但人們希望Glassfish有辦法。 – Affe 2014-08-28 15:56:36

+0

這可能是這種情況,但在這個級別,我不認爲我們有權訪問Http會話來獲取任何登錄的用戶或他們的客戶端IP地址。 – JulioHM 2014-08-28 18:40:55

回答

3

我很快在這篇文章中回答了這個問題,但隱藏了我們花了兩週時間試圖解決這個問題的不同策略。所以,我們決定使用我們的最終實現。

基本思想:通過擴展休眠給出一個創建自己的實現javax.persistence.spi.PersistenceProvider的。對於所有的效果,這是您的代碼綁定到Hibernate或任何其他供應商特定實現的唯一的一點。

public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider { 

    @Override 
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) { 
     return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties)); 
    } 

} 

的想法是包裹的EntityManagerFactory的EntityManager的Hibernate的版本與自己的實現。所以你需要創建實現這些接口的類並保持供應商特定的實現。

這是EntityManagerFactoryWrapper

public class EntityManagerFactoryWrapper implements EntityManagerFactory { 

    private EntityManagerFactory emf; 

    public EntityManagerFactoryWrapper(EntityManagerFactory originalEMF) { 
     emf = originalEMF; 
    } 

    public EntityManager createEntityManager() { 
     return new EntityManagerWrapper(emf.createEntityManager()); 
    } 

    // Implement all other methods for the interface 
    // providing a callback to the original emf. 

的EntityManagerWrapper是我們的攔截點。您將需要從界面實施所有方法。在每個可以修改實體的方法中,我們都包含對自定義查詢的調用,以便在數據庫中設置局部變量。

public class EntityManagerWrapper implements EntityManager { 

    private EntityManager em; 
    private Principal principal; 

    public EntityManagerWrapper(EntityManager originalEM) { 
     em = originalEM; 
    } 

    public void setAuditVariables() { 
     String userid = getUserId(); 
     String ipaddr = getUserAddr(); 
     String sql = "SET LOCAL application.userid='"+userid+"'; SET LOCAL application.ipaddr='"+ipaddr+"'"; 
     em.createNativeQuery(sql).executeUpdate(); 
    } 

    protected String getUserAddr() { 
     HttpServletRequest httprequest = CDIBeanUtils.getBean(HttpServletRequest.class); 
     String ipaddr = ""; 
     if (httprequest != null) { 
      ipaddr = httprequest.getRemoteAddr(); 
     } 
     return ipaddr; 
    } 

    protected String getUserId() { 
     String userid = ""; 
     // Try to look up a contextual reference 
     if (principal == null) { 
      principal = CDIBeanUtils.getBean(Principal.class); 
     } 

     // Try to assert it from CAS authentication 
     if (principal == null || "anonymous".equalsIgnoreCase(principal.getName())) { 
      if (AssertionHolder.getAssertion() != null) { 
       principal = AssertionHolder.getAssertion().getPrincipal(); 
      } 
     } 
     if (principal != null) { 
      userid = principal.getName(); 
     } 
     return userid; 
    } 

    @Override 
    public void persist(Object entity) { 
     if (em.isJoinedToTransaction()) { 
      setAuditVariables(); 
     } 
     em.persist(entity); 
    } 

    @Override 
    public <T> T merge(T entity) { 
     if (em.isJoinedToTransaction()) { 
      setAuditVariables(); 
     } 
     return em.merge(entity); 
    } 

    @Override 
    public void remove(Object entity) { 
     if (em.isJoinedToTransaction()) { 
      setAuditVariables(); 
     } 
     em.remove(entity); 
    } 

    // Keep implementing all methods that can change 
    // entities so you can setAuditVariables() before 
    // the changes are applied. 
    @Override 
    public void createNamedQuery(..... 

缺點:攔截查詢(SET LOCAL)可能會在單個事務中運行了好幾次,特別是如果有一個單一的服務呼叫作了幾次發言。考慮到這種情況,我們決定保持這種方式,因爲它在內存調用PostgreSQL時是一個簡單的SET LOCAL。由於沒有涉及表格,我們可以接受表演。

現在只需更換內部Hibernate的持久性提供的persistence.xml

<?xml version="1.0" encoding="UTF-8"?> 
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" 
      version="2.1"> 
<persistence-unit name="petstore" transaction-type="JTA"> 
     <provider>my.package.HibernatePersistenceProvider</provider> 
     <jta-data-source>java:app/jdbc/exemplo</jta-data-source> 
     <properties> 
      <property name="hibernate.transaction.jta.platform" value="org.hibernate.service.jta.platform.internal.SunOneJtaPlatform" /> 
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/> 
     </properties> 
</persistence-unit> 

作爲一個方面說明,這是我們要幫助一些特殊場合的bean管理的CDIBeanUtils。在這種情況下,我們使用它來查找對HttpServletRequest和Principal的引用。

public class CDIBeanUtils { 

    public static <T> T getBean(Class<T> beanClass) { 

     BeanManager bm = CDI.current().getBeanManager(); 

     Iterator<Bean<?>> ite = bm.getBeans(beanClass).iterator(); 
     if (!ite.hasNext()) { 
      return null; 
     } 
     final Bean<T> bean = (Bean<T>) ite.next(); 
     final CreationalContext<T> ctx = bm.createCreationalContext(bean); 
     final T t = (T) bm.getReference(bean, beanClass, ctx); 
     return t; 
    } 

} 

公平地說,這並不是完全攔截Transactions事件。但是我們可以在事務中包含我們需要的自定義查詢。

希望這可以幫助別人避免我們經歷的痛苦。

+0

你能做這個工作嗎?我遵循你的模式,並且出現了代理EntityManagerFactory的異常。提前致謝。 – 2015-08-31 20:07:34