2014-08-28 61 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