Dies wurde hier in diesem Beitrag schnell von mir selbst beantwortet, aber die Tatsache verschwiegen, dass wir über zwei Wochen damit verbracht haben, verschiedene Strategien auszuprobieren, um dies zu überwinden. Also, hier ist unsere endgültige Implementierung, für die wir uns entschieden haben.
Grundidee: Erstellen Sie Ihre eigene Implementierung von javax.persistence.spi.PersistenceProvider durch Erweitern der von Hibernate gegebenen. Für alle Effekte ist dies der einzige Punkt, an dem Ihr Code an Hibernate oder eine andere herstellerspezifische Implementierung gebunden wird.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Die Idee ist, die Hibernate-Versionen von EntityManagerFactory einzuschließen und EntityManager mit eigener Implementierung. Sie müssen also Klassen erstellen, die diese Schnittstellen implementieren und die herstellerspezifische Implementierung enthalten.
Dies ist der 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.
Der EntityManagerWrapper ist unser Abfangpunkt. Sie müssen alle Methoden der Schnittstelle implementieren. Bei jeder Methode, bei der eine Entität geändert werden kann, fügen wir einen Aufruf einer benutzerdefinierten Abfrage ein, um lokale Variablen in der Datenbank festzulegen.
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(.....
Nachteil: Abfangabfragen (SET LOCAL) werden wahrscheinlich mehrmals innerhalb einer einzelnen Transaktion ausgeführt, insbesondere wenn bei einem einzelnen Dienstaufruf mehrere Anweisungen gemacht werden. Angesichts der Umstände haben wir uns entschieden, dies so zu belassen, da es sich um einen einfachen SET LOCAL-Aufruf im Speicher von PostgreSQL handelt. Da es keine Tabellen gibt, können wir mit dem Performance-Einbruch leben.
Ersetzen Sie jetzt einfach den Persistenzanbieter von Hibernate in 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>
Nebenbei bemerkt, dies sind die CDIBeanUtils, denen wir bei einigen besonderen Anlässen mit dem Bean-Manager helfen müssen. In diesem Fall verwenden wir es, um einen Verweis auf HttpServletRequest und Principal nachzuschlagen.
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;
}
}
Um fair zu sein, dies ist nicht gerade das Abfangen von Transactions-Ereignissen. Wir sind jedoch in der Lage, die benutzerdefinierten Abfragen, die wir benötigen, in die Transaktion aufzunehmen.
Hoffentlich kann dies anderen helfen, den Schmerz zu vermeiden, den wir durchgemacht haben.