PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Jak mogę przechwycić zdarzenia transakcji JTA i uzyskać odniesienie do aktualnego EntityManager powiązanego z transakcją?

Na to szybko odpowiedziałem w tym poście, ale ukrywając fakt, że spędziliśmy ponad dwa tygodnie próbując różnych strategii, aby to przezwyciężyć. Oto nasza ostateczna implementacja, którą zdecydowaliśmy się użyć.

Podstawowy pomysł: Utwórz własną implementację javax.persistence.spi.PersistenceProvider rozszerzając tę ​​podaną przez Hibernate. W przypadku wszystkich efektów jest to jedyny punkt, w którym Twój kod będzie powiązany z Hibernate lub inną implementacją specyficzną dla dostawcy.

public class MyHibernatePersistenceProvider extends org.hibernate.jpa.HibernatePersistenceProvider {

    @Override
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info, Map properties) {
        return new EntityManagerFactoryWrapper(super.createContainerEntityManagerFactory(info, properties));
    }

}

Chodzi o to, aby zawinąć wersje hibernacji EntityManagerFactory i EntityManager z własną realizacją. Musisz więc stworzyć klasy, które implementują te interfejsy i zachować implementację specyficzną dla dostawcy.

To jest 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 to nasz punkt przechwycenia. Będziesz musiał zaimplementować wszystkie metody z interfejsu. W każdej metodzie, w której można zmodyfikować encję, dołączamy wywołanie niestandardowego zapytania, aby ustawić zmienne lokalne w bazie danych.

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(.....

Wady: Zapytania przechwytujące (SET LOCAL) prawdopodobnie zostaną uruchomione kilka razy w ramach jednej transakcji, szczególnie jeśli w jednym wywołaniu usługi zostanie złożonych kilka instrukcji. Biorąc pod uwagę okoliczności, postanowiliśmy zachować to w ten sposób, ponieważ jest to proste wywołanie SET LOCAL w pamięci do PostgreSQL. Ponieważ nie ma żadnych stołów, możemy żyć z hitem wydajności.

Teraz wystarczy zastąpić dostawcę trwałości Hibernate w 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>

Na marginesie, to jest CDIBeanUtils, które musimy pomóc z menedżerem fasoli przy niektórych specjalnych okazjach. W tym przypadku używamy go do wyszukania odniesienia do HttpServletRequest i 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;
    }

}

Aby być uczciwym, nie jest to dokładnie przechwytywanie zdarzeń Transakcji. Ale jesteśmy w stanie uwzględnić niestandardowe zapytania, których potrzebujemy w transakcji.

Mam nadzieję, że pomoże to innym uniknąć bólu, przez który przeszliśmy.




  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak zachować porządek, by dzieci pojawiały się po rodzicach?

  2. Jak sprawdzić wersję PostgreSQL

  3. Problemy z dostępem do zdalnej bazy Postgres DB na Heroku z lokalnej aplikacji Node.js Webapp

  4. Zapisywanie danych wyjściowych zapytania dynamicznego używającego instrukcji przygotowania do tabeli

  5. Dlaczego postgresql 9.1 nie działa z rails 3.0?