Detta besvarades snabbt här i det här inlägget av mig själv, men döljer det faktum att vi tillbringade över två veckor med att prova olika strategier för att övervinna detta. Så här kommer vår sista implementering som vi bestämde oss för att använda.
Grundidé: Skapa din egen implementering av javax.persistence.spi.PersistenceProvider genom att utöka den som ges av Hibernate. För alla effekter är detta den enda punkten där din kod kommer att kopplas till Hibernate eller någon annan leverantörsspecifik implementering.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Tanken är att slå in hibernates versioner av EntityManagerFactory och EntityManager med din egen implementering. Så du måste skapa klasser som implementerar dessa gränssnitt och hålla den leverantörsspecifika implementeringen inuti.
Detta är 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 är vår avlyssningspunkt. Du måste implementera alla metoder från gränssnittet. Vid varje metod där en entitet kan modifieras inkluderar vi ett anrop till en anpassad fråga för att ställa in lokala variabler i databasen.
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(.....
Nackdelen: Avlyssningsförfrågningar (SET LOCAL) kommer sannolikt att köras flera gånger i en enda transaktion, speciellt om det finns flera uttalanden på ett enda serviceanrop. Med tanke på omständigheterna bestämde vi oss för att behålla det så här på grund av att det är ett enkelt SET LOCAL-minnesanrop till PostgreSQL. Eftersom det inte finns några bord inblandade kan vi leva med prestationshiten.
Nu är det bara att byta ut Hibernates beständighetsleverantör i 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>
Som en sidoanteckning är detta CDIBeanUtils vi har för att hjälpa till med bönhanteraren vid några speciella tillfällen. I det här fallet använder vi den för att slå upp en referens till HttpServletRequest och 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;
}
}
För att vara rättvis är detta inte precis att avlyssna transaktionshändelser. Men vi kan inkludera de anpassade frågor vi behöver i transaktionen.
Förhoppningsvis kan detta hjälpa andra att undvika smärtan vi gick igenom.