sql >> Database teknologi >  >> RDS >> PostgreSQL

Hvordan kan jeg opsnappe JTA-transaktionshændelser og få en reference til den aktuelle EntityManager forbundet med transaktionen

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.




  1. Mulige returværdier for mysql_affected_rows()

  2. Postgresql-kast null-værdi i kolonne overtræder ikke-null-begrænsning ved brug af oneToMany-relation i JPA

  3. Bruger samme kolonne flere gange i WHERE-sætning

  4. Hvordan opretter jeg tabeller, der ikke ejes af sys i Oracle?