It was quickly responsible here in this post, but hiding the fact that we spent more than two weeks trying to develop strategies to overcome this. So, here is our final implementation, which we decided to use.
Main idea: Create your own implementation of javax.persistence.spi.PersistenceProvider , expanding the value specified by Hibernate. For all effects, this is the only time your code will be tied to Hibernate or any other specific vendor implementation.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider { @Override public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) { return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties)); } }
The idea is to link the EntityManagerFactory and EntityManager hibernation versions to your own implementation. Therefore, you need to create classes that implement these interfaces and save the implementation of a specific provider inside.
This is EntityManagerFactoryWrapper
public class EntityManagerFactoryWrapper implements EntityManagerFactory { private EntityManagerFactory emf; public EntityManagerFactoryWrapper(EntityManagerFactory originalEMF) { emf = originalEMF; } public EntityManager createEntityManager() { return new EntityManagerWrapper(emf.createEntityManager()); }
EntityManagerWrapper is a capture point. You will need to implement all the methods from the interface. For each method in which an object can be modified, we include a user request call to set local variables in the 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 = "";
Downside:. Interception requests (SET LOCAL) will most likely be executed several times within the same transaction, especially if there are several statements for a single service call. Given the circumstances, we decided to store it this way because it is a simple SET LOCAL in a PostgreSQL memory call. Since there are no tables involved, we can live with a performance hit.
Now just replace the Hibernate persistence provider inside 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>
As a side note, this is CDIBeanUtils, we need help with the bean manager in some special cases. In this case, we use it to search for links to HttpServletRequest and 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; } }
To be fair, this does not completely intercept transaction events. But we can include custom queries that we need inside the transaction.
Hope this helps others avoid the pain we have been through.