Dette blev hurtigt besvaret her i dette indlæg af mig selv, men skjulte det faktum, at vi brugte over to uger på at prøve forskellige strategier for at overvinde dette. Så her kommer vores endelige implementering, vi besluttede at bruge.
Grundlæggende idé: Opret din egen implementering af javax.persistence.spi.PersistenceProvider ved at udvide den, der er givet af Hibernate. For alle effekter er dette det eneste punkt, hvor din kode vil være knyttet til Hibernate eller enhver anden leverandørspecifik implementering.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Ideen er at pakke hibernates versioner af EntityManagerFactory og EntityManager med din egen implementering. Så du skal oprette klasser, der implementerer disse grænseflader og holder den leverandørspecifikke implementering inde.
Dette er 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 er vores aflytningssted. Du skal implementere alle metoder fra grænsefladen. Ved hver metode, hvor en enhed kan ændres, inkluderer vi et kald til en tilpasset forespørgsel for at indstille lokale 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(.....
Ulempe: Aflytningsforespørgsler (SET LOCAL) vil sandsynligvis køre flere gange i en enkelt transaktion, især hvis der er flere erklæringer på et enkelt servicekald. I betragtning af omstændighederne besluttede vi at beholde det på denne måde på grund af det faktum, at det er et simpelt SET LOCAL i hukommelseskald til PostgreSQL. Da der ikke er nogen borde involveret, kan vi leve med præstationshittet.
Nu skal du bare udskifte Hibernates persistensudbyder inde 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 sidebemærkning er dette CDIBeanUtils, vi skal hjælpe med bønnemanageren ved nogle særlige lejligheder. I dette tilfælde bruger vi det til at slå en reference op til HttpServletRequest og 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;
}
}
For at være retfærdig er dette ikke ligefrem at opsnappe transaktionsbegivenheder. Men vi er i stand til at inkludere de tilpassede forespørgsler, vi har brug for, i transaktionen.
Forhåbentlig kan dette hjælpe andre med at undgå den smerte, vi gik igennem.