PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Come posso intercettare gli eventi delle transazioni JTA e ottenere un riferimento all'EntityManager corrente associato alla transazione

A questo ho risposto rapidamente qui in questo post da solo, ma nascondendo il fatto che abbiamo trascorso più di due settimane a provare diverse strategie per superare questo problema. Quindi, ecco la nostra implementazione finale che abbiamo deciso di utilizzare.

Idea di base: Crea la tua implementazione di javax.persistence.spi.PersistenceProvider estendendo quello dato da Hibernate. A tutti gli effetti, questo è l'unico punto in cui il tuo codice sarà legato a Hibernate oa qualsiasi altra implementazione specifica del fornitore.

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

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

}

L'idea è di eseguire il wrapping delle versioni di Hibernate di EntityManagerFactory e EntityManager con la tua implementazione. Quindi è necessario creare classi che implementino queste interfacce e mantengano l'implementazione specifica del fornitore all'interno.

Questo è 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 è il nostro punto di intercettazione. Dovrai implementare tutti i metodi dall'interfaccia. In ogni metodo in cui un'entità può essere modificata, includiamo una chiamata a una query personalizzata per impostare variabili locali nel database.

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(.....

Lato negativo: È probabile che le query di intercettazione (SET LOCAL) vengano eseguite più volte all'interno di una singola transazione, specialmente se sono presenti diverse istruzioni in una singola chiamata di servizio. Date le circostanze, abbiamo deciso di mantenerlo in questo modo poiché si tratta di una semplice chiamata SET LOCAL in memoria a PostgreSQL. Dal momento che non ci sono tavoli coinvolti, possiamo convivere con il successo della performance.

Ora sostituisci semplicemente il provider di persistenza di Hibernate all'interno di 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>

Come nota a margine, questo è il CDIBeanUtils che dobbiamo aiutare con il bean manager in alcune occasioni speciali. In questo caso, lo utilizziamo per cercare un riferimento a HttpServletRequest e 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;
    }

}

Ad essere onesti, questo non è esattamente intercettare gli eventi Transactions. Ma siamo in grado di includere le query personalizzate di cui abbiamo bisogno all'interno della transazione.

Speriamo che questo possa aiutare gli altri a evitare il dolore che abbiamo attraversato.