sql >> Base de Datos >  >> RDS >> PostgreSQL

¿Cómo puedo interceptar eventos de transacciones JTA y obtener una referencia al EntityManager actual asociado con la transacción?

Esto fue respondido rápidamente aquí en esta publicación por mí mismo, pero ocultando el hecho de que pasamos más de dos semanas probando diferentes estrategias para superar esto. Entonces, aquí va nuestra implementación final que decidimos usar.

Idea básica: Cree su propia implementación de javax.persistence.spi.PersistenceProvider extendiendo el dado por Hibernate. Para todos los efectos, este es el único punto donde su código estará vinculado a Hibernate o cualquier otra implementación específica del proveedor.

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

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

}

La idea es envolver las versiones de hibernate de EntityManagerFactory y EntityManager con su propia implementación. Por lo tanto, debe crear clases que implementen estas interfaces y mantener dentro la implementación específica del proveedor.

Este es el 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.

El EntityManagerWrapper es nuestro punto de intercepción. Deberá implementar todos los métodos desde la interfaz. En cada método en el que se puede modificar una entidad, incluimos una llamada a una consulta personalizada para establecer variables locales en la base de datos.

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

Desventaja: Es probable que las consultas de intercepción (SET LOCAL) se ejecuten varias veces dentro de una sola transacción, especialmente si se realizan varias declaraciones en una sola llamada de servicio. Dadas las circunstancias, decidimos mantenerlo así debido al hecho de que es un simple SET LOCAL en una llamada de memoria a PostgreSQL. Como no hay mesas involucradas, podemos vivir con el golpe de rendimiento.

Ahora simplemente reemplace el proveedor de persistencia de Hibernate dentro de 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>

Como nota al margen, este es el CDIBeanUtils que tenemos para ayudar con el administrador de beans en algunas ocasiones especiales. En este caso, lo estamos usando para buscar una referencia a HttpServletRequest y 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;
    }

}

Para ser justos, esto no es exactamente interceptar eventos de Transacciones. Pero podemos incluir las consultas personalizadas que necesitamos dentro de la transacción.

Con suerte, esto puede ayudar a otros a evitar el dolor por el que pasamos.