Na to jsem zde v tomto příspěvku rychle odpověděl sám, ale skrýval jsem skutečnost, že jsme strávili více než dva týdny zkoušením různých strategií, jak to překonat. Zde je naše konečná implementace, kterou jsme se rozhodli použít.
Základní myšlenka: Vytvořte si vlastní implementaci javax.persistence.spi.PersistenceProvider prodloužením té, kterou poskytuje Hibernate. U všech efektů je to jediný bod, kde bude váš kód propojen s Hibernatem nebo jinou implementací specifickou pro dodavatele.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Cílem je zabalit verze EntityManagerFactory do režimu spánku a EntityManager s vlastní implementací. Takže musíte vytvořit třídy, které implementují tato rozhraní, a ponechat implementaci specifickou pro dodavatele uvnitř.
Toto je 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 je naším záchytným bodem. Budete muset implementovat všechny metody z rozhraní. U každé metody, kde lze entitu upravit, zahrnujeme volání vlastního dotazu pro nastavení lokálních proměnných v databázi.
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(.....
Nevýhoda: Dotazy na zachycení (SET LOCAL) se pravděpodobně v rámci jedné transakce spustí několikrát, zvláště pokud je v jednom servisním volání provedeno několik příkazů. Vzhledem k okolnostem jsme se rozhodli to takto zachovat, protože jde o jednoduché volání SET LOCAL v paměti do PostgreSQL. Vzhledem k tomu, že nejsou zapojeny žádné stoly, můžeme žít s výkonem.
Nyní stačí nahradit poskytovatele persistence Hibernate uvnitř 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>
Jako vedlejší poznámku, toto je CDIBeanUtils, kterému musíme pomoci s manažerem fazolí při některých zvláštních příležitostech. V tomto případě jej používáme k vyhledání odkazu na HttpServletRequest a 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;
}
}
Abychom byli spravedliví, není to přesně zachycení událostí transakcí. Jsme však schopni do transakce zahrnout vlastní dotazy, které potřebujeme.
Doufejme, že to pomůže ostatním vyhnout se bolesti, kterou jsme prošli.