Dit werd hier in dit bericht snel door mezelf beantwoord, maar verbergde het feit dat we meer dan twee weken bezig waren met het proberen van verschillende strategieën om dit te overwinnen. Dus hier komt onze laatste implementatie die we besloten te gebruiken.
Basisidee: Maak uw eigen implementatie van javax.persistence.spi.PersistenceProvider door die van Hibernate uit te breiden. Voor alle effecten is dit het enige punt waarop uw code wordt gekoppeld aan Hibernate of een andere leverancierspecifieke implementatie.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Het idee is om de hibernate-versies van EntityManagerFactory in te pakken en EntityManager met uw eigen uitvoering. Je moet dus klassen maken die deze interfaces implementeren en de leverancierspecifieke implementatie erin houden.
Dit is de 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.
De EntityManagerWrapper is ons onderscheppingspunt. U moet alle methoden vanuit de interface implementeren. Bij elke methode waarbij een entiteit kan worden gewijzigd, voegen we een aanroep toe aan een aangepaste query om lokale variabelen in de database in te stellen.
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(.....
Nadeel: Onderscheppingsquery's (SET LOCAL) zullen waarschijnlijk meerdere keren binnen een enkele transactie worden uitgevoerd, vooral als er meerdere verklaringen worden afgelegd over een enkele serviceaanroep. Gezien de omstandigheden hebben we besloten om het zo te houden vanwege het feit dat het een eenvoudige SET LOCAL in geheugenaanroep naar PostgreSQL is. Aangezien er geen tafels bij betrokken zijn, kunnen we leven met de prestatiehit.
Vervang nu gewoon de persistentie-provider van 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>
Even terzijde:dit is de CDIBeanUtils die we bij sommige speciale gelegenheden moeten helpen met de bonenmanager. In dit geval gebruiken we het om een verwijzing naar HttpServletRequest en Principal op te zoeken.
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;
}
}
Om eerlijk te zijn, dit is niet bepaald het onderscheppen van Transacties-gebeurtenissen. Maar we kunnen de aangepaste zoekopdrachten die we nodig hebben in de transactie opnemen.
Hopelijk kan dit anderen helpen de pijn te vermijden die we hebben doorgemaakt.