Това беше бързо отговорено тук в тази публикация от мен, но скрихме факта, че прекарахме повече от две седмици, опитвайки различни стратегии, за да преодолеем това. И така, ето нашата окончателна реализация, която решихме да използваме.
Основна идея: Създайте своя собствена реализация на javax.persistence.spi.PersistenceProvider чрез разширяване на дадения от Hibernate. За всички ефекти това е единствената точка, в която вашият код ще бъде свързан с Hibernate или друга специфична реализация на доставчика.
public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {
@Override
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
}
}
Идеята е да обвиете версиите на hibernate на EntityManagerFactory и EntityManager със собствена реализация. Така че трябва да създадете класове, които имплементират тези интерфейси и да запазите специфичната им реализация на доставчика вътре.
Това е 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 е нашата точка за прихващане. Ще трябва да приложите всички методи от интерфейса. Във всеки метод, където даден обект може да бъде модифициран, ние включваме извикване на персонализирана заявка за задаване на локални променливи в базата данни.
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(.....
Недостатък: Заявките за прихващане (SET LOCAL) вероятно ще се изпълняват няколко пъти в рамките на една транзакция, особено ако има няколко изявления, направени при едно извикване на услуга. Предвид обстоятелствата решихме да го запазим по този начин поради факта, че това е просто извикване на SET LOCAL в паметта към PostgreSQL. Тъй като няма включени маси, можем да се примирим с хита на производителността.
Сега просто сменете доставчика на Hibernate за постоянство в 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>
Като странична бележка, това е CDIBeanUtils, с който разполагаме, за да помогнем с мениджъра на бобовете при някои специални случаи. В този случай ние го използваме, за да търсим препратка към HttpServletRequest и 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;
}
}
Честно казано, това не е точно прихващане на събития на транзакции. Но можем да включим персонализираните заявки, от които се нуждаем, в транзакцията.
Надяваме се, че това може да помогне на другите да избегнат болката, през която преминахме.