当前位置: 首页 > 知识库问答 >
问题:

如何拦截JTA事务事件并获得与事务关联的当前EntityManager的引用

龚安民
2023-03-14

长话短说:我们开发并维护了一个库,可以在使用JavaEE7/CDI/JPA的其他项目中使用。应用程序将在Glassfish-4.0下运行,并使用Hibernate的JPA实现实现底层的PostgreSQL持久性。这是将用Spring/Struts/Hibernate编写的旧应用程序重写到JavaEE7/CDI/JTA新世界的长期迁移工作的一部分。

问题:出于审计目的,我们的库需要在执行用户语句之前拦截所有数据库事务并包含自定义SQL语句。此时,需要将当前用户名和IP地址插入到临时数据库变量(特定于供应商的功能)中,以便数据库触发器可以读取它们以创建任何行修改的审核跟踪。这篇特别的文章非常有助于提供替代方案,我们的团队因为之前建立的传统而走上了触发之路。

然而:我们对JTA处理事务事件的方式深感失望。拦截交易的方法有很多,但这种情况似乎是不可能的。在旧架构中,使用Spring的事务管理器,我们只需使用Hibernate Interceptor实现Interceptor.afterTransactionBecin(...)。阅读官方JTA-1.2规范,我们发现它确实支持Synchronization.before完成和Synchronization.after完成。经过几个小时的调试,我们清楚地注意到Hibernate的JTA实现正在使用这些功能。但是JTA似乎缺乏像开始之前和开始之后这样的事件(恕我直言,这似乎缺乏常识)。由于没有拦截这些设备的设施,Hibernate完全符合JTA的要求,但它根本不会。期间。

不管我们做什么,我们都找不到办法。例如,我们尝试截取@Transactional注释,并在容器的JTA impl完成打开事务的任务后运行代码。但我们缺乏动态获取与该特定事务相关联的EntityManager的能力。请记住:这是一个库,而不是web应用程序本身。它不能对应用程序声明和使用的持久性单元做出任何假设。而且,据我们所知,我们需要知道将哪个特定的持久化单元名称注入到代码中。我们正试图为其他临时管理机构提供尽可能透明的审计设施。

所以我们谦卑地请求帮助。如果有人有解决方案、变通办法,无论什么意见,我们都很乐意听到。

共有1个答案

宁浩博
2023-03-14

我自己在这篇文章中很快回答了这个问题,但掩盖了这样一个事实:我们花了两个多星期的时间尝试不同的策略来克服这个问题。所以,下面是我们决定使用的最终实现。

基本思想:创建自己的javax实现。坚持不懈spi。通过扩展Hibernate提供的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));
    }

}

这个想法是用自己的实现包装hibernate的EntityManagerFactory和EntityManager版本。因此,您需要创建实现这些接口的类,并将供应商特定的实现保留在内部。

这是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的持久性提供程序。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>

作为旁注,这是CDIBeanUtils,在某些特殊情况下,我们必须帮助bean管理器。在本例中,我们使用它来查找对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;
    }

}

公平地说,这并不完全是拦截事务事件。但是我们可以在事务中包含所需的自定义查询。

希望这能帮助其他人避免我们所经历的痛苦。

 类似资料:
  • 我有一个问题,如果我的服务上有一个客户端调用两个方法,它就会失败,因为第二个方法中的事务没有与之关联的会话。但是,如果我将这两种方法组合到服务中,并从客户机代码中调用其中一种方法,它就会成功。 谁能给我解释一下为什么会这样? 考虑下面的代码: 所以这里发生的事情是,在我的客户机代码中(它不知道事务),如果我调用#getChildrenFor(id),我就没事了。但如果我打电话: 然后hiberna

  • 并用@Transactional注释了具体类。 我们使用Jboss应用服务器支持通过JNDI与MQ集成。这里的问题是,如果监听器中的任何层有任何异常,则整个事务不会回滚,消息也不会移动到退出队列。很明显,当我们使用Hibernate事务管理器时,它不知道其他资源,如JMS事务。 我可以安全地用JTA事务替换它吗,因为Jboss将处理整个事务管理?这样做是否有任何可预见的风险?

  • 问题内容: 我需要从存储过程中写入日志表。现在,此日志信息必须能够在回滚过程中幸免。 我知道以前曾问过这个问题,但是我的情况有所不同,在这些问题中找不到我的问题的答案。 当存储过程中没有错误时,事情就很简单了,日志表中的条目就在那里。 当有错误时,事情就变得复杂了。 在该过程中,我可以在catch中进行回滚,然后将数据插入日志表,我知道并且我已经在这样做了。 但是问题是当存储过程这样调用时: 我知

  • 环境: 我们有一个应用程序部署在 JBoss 4.2.3.GA 服务器中,它使用Hibernate 3.4 和 JTA 1.0。 有一个导入器创建或更新某些实体,然后导入一些数据。由于多种原因,大部分导入是在新事务中完成的,在每个事务中,在外部事务中创建/更新的实体可能会再次更新。 调用序列类似于以下伪代码: 服务1: 服务2: 问题: 现在的问题是,我们最终会遇到一个竞争条件,有几个事务试图锁定

  • 此错误发生在,当方法运行时。 向添加注释不能解决问题-https://stackoverflow.com/a/32552558/3536552 你知道怎么修吗?

  • Infinispan留档说了很多关于它的锁定方案的东西,乐观的和悲观的,但是没有详细说明如何使用事务。可能它被认为是知道的,因为它使用Java的默认API,但是如果是这样,我也没有找到关于JTA使用模式的信息。 我假设,在使用乐观事务时,它将被提交或回滚,这取决于对数据的并发访问。如果回滚,我将能够重播事务,希望现在它将在其他并发访问之前完成,并将成功。我在TransactionManager上有