web-dev-qa-db-fra.com

Pourquoi différentes unités de persistance avec des sources de données séparées interrogent la même source de données?

Je développe une webapp qui a besoin d'accéder à deux serveurs de base de données différents (H2 et Oracle). Le conteneur est un Apache Tomee 1.5.1 et j'utilise la pile Java EE avec les bibliothèques fournies (JSF, JPA, CDI, EJB, etc.) ).

J'essaie d'utiliser deux gestionnaires d'entités dans une transaction XA pour extraire des données de la base de données Oracle et les conserver dans le H2 après les avoir transformées, MAIS toutes les requêtes sont exécutées sur la base de données H2, quel que soit le gestionnaire d'entités que j'utilise. De l'aide?

[~ # ~] edit [~ # ~] : J'ai trouvé que si j'essaie d'accéder aux gestionnaires d'entités dans l'ordre inverse, leur comportement est le même mais l'accès à Oracle. C'est-à-dire: les gestionnaires d'entités restent avec la première base de données consultée.

L'EJB où cela se produit (appel de service.getFoo() à partir de JSF):

@Named
@Stateless
public class Service {
    @Inject
    @OracleDatabase
    private EntityManager emOracle;

    @Inject
    @H2Database
    private EntityManager emH2;

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public List<Foo> getFoo() {
        TypedQuery<Foo> q = emH2.createQuery(
                "SELECT x FROM Foo f", Foo.class);
        List<Foo> l = q.getResultList();
        if (l == null || l.isEmpty()) {
            update();
        }

        return q.getResultList();
    }

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void update() {
        // FAIL: This query executes against H2 with Oracle entity manager!
        List<Object[]> l = emOracle.createNativeQuery("SELECT * FROM bar ").getResultList(); 

        //more stuff...
    }
}

Le producteur de ressources (CDI) pour les gestionnaires d'entités (où @ H2Database et @OracleDatabase sont qualificatifs ):

public class Resources {
    @Produces
    @PersistenceContext(unitName = "OraclePU")
    @OracleDatabase
    private EntityManager emOracle;

    @Produces
    @PersistenceContext(unitName = "H2PU")
    @H2Database
    private EntityManager emH2;
}

Mon peristence.xml ressemble à ceci:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://Java.Sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://Java.Sun.com/xml/ns/persistence http://Java.Sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="H2PU"
        transaction-type="JTA">
        <provider>org.Apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <jta-data-source>H2DS</jta-data-source>
        <class>my.app.h2.Foo</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>

    <persistence-unit name="OraclePU" transaction-type="JTA">
        <provider>org.Apache.openjpa.persistence.PersistenceProviderImpl</provider>
        <jta-data-source>OracleDS</jta-data-source>
        <class>my.app.Oracle.Bar</class>
        <exclude-unlisted-classes>true</exclude-unlisted-classes>
    </persistence-unit>
</persistence>

Et enfin, les sources de données à l'intérieur tomee.xml (aucune autre source de données n'est configurée à l'intérieur de ce fichier):

<Resource id="OracleDS" type="javax.sql.DataSource">
    jdbcDriver = Oracle.jdbc.xa.client.OracleXADataSource
    jdbcUrl = jdbc:Oracle:thin:@server:port:instance
    jtaManaged = true
    password = abcde
    userName = user
</Resource>

<Resource id="H2DS" type="javax.sql.DataSource">
    jdbcDriver=org.h2.jdbcx.JdbcDataSource
    jdbcUrl=jdbc:h2:h2/db;AUTO_SERVER=TRUE
    jtaManaged = true
    password = edcba
    userName = user
</Resource>
24
Alberto Segura

Contextes de persistance gérée par conteneur

Lorsque vous utilisez des contextes de persistance gérés par conteneur (comme vous le faites via les annotations @PersistenceContext), la spécification JPA spécifie qu'un seul contexte de persistance peut être associé à une transaction JTA.

Le contexte de persistance est créé par le Java EE. Malgré les apparences du code (les annotations @PersistenceContext semblent suggérer que PC est injecté directement dans vos variables d'instance EntityManager), le contexte de persistance est réellement stocké comme référence DANS LA TRANSACTION JTA. Chaque fois qu'une opération EntityManager se produit, elle ne fait pas référence à son propre contexte de persistance interne. Au lieu de cela, elle effectue une opération spéciale car elle est gérée par conteneur - elle recherche toujours le contexte de persistance dans la transaction JTA et utilise C'est ce qu'on appelle la propagation du contexte de persistance JTA.

Quelques citations de la spécification JPA:

Lorsqu'un gestionnaire d'entités géré par conteneur est utilisé, le cycle de vie du contexte de persistance est toujours géré automatiquement, de manière transparente pour l'application, et le contexte de persistance est propagé avec la transaction JTA.

Contexte de persistance à portée de transaction géré par conteneur

... Un nouveau contexte de persistance commence lorsque le gestionnaire d'entités gérées par conteneur est appelé [76] dans le cadre d'une transaction JTA active, et aucun contexte de persistance actuel n'est déjà associé au JTA transaction. Le contexte de persistance est créé puis associé à la transaction JTA.

Contexte de persistance étendue géré par conteneur

... Un contexte de persistance étendue géré par conteneur ne peut être initié que dans le cadre d'un bean session avec état. Il existe à partir du moment où le bean session avec état qui déclare une dépendance à un gestionnaire d'entités de type PersistenceContextType.EXTENDED est créé et est dit lié au bean avec session avec état. La dépendance à l'égard du contexte de persistance étendu est déclarée au moyen de l'annotation PersistenceContext ou de l'élément descripteur de déploiement persistence-context-ref. Le contexte de persistance est fermé par le conteneur lorsque la méthode @Remove du bean session avec état se termine (ou que l'instance du bean session avec état est sinon détruite).

Conditions requises pour la propagation du contexte de persistance

... Si un composant est appelé et qu'il n'y a pas de transaction JTA ..., le contexte de persistance n'est pas propagé. • L'appel d'un gestionnaire d'entités défini avec PersistenceContext- Type.TRANSACTION entraînera l'utilisation d'un nouveau contexte de persistance. • L'appel d'un gestionnaire d'entités défini avec PersistenceContext- Type.EXTENDED entraînera l'utilisation du contexte de persistance étendu existant lié à ce composant.

... Si un composant est appelé et que la transaction JTA est propagée dans ce composant: • Si le composant est un bean session avec état auquel un contexte de persistance étendu a été lié et qu'il existe un contexte de persistance différent lié à la transaction JTA, un EJBException est levée par le conteneur. • Sinon, s'il existe un contexte de persistance lié à la transaction JTA, ce contexte de persistance est propagé et utilisé.

C'est donc votre problème. La question évidente de 64 $: POURQUOI la spécification le demande-t-elle ???

Eh bien, c'est parce que c'est un compromis délibéré qui apporte une puissante magie EntityManager aux EJB.

L'utilisation de transactions JTA pour propager un contexte de persistance unique a une limitation: les transactions ne peuvent pas chevaucher plusieurs contextes de persistance, donc ne peuvent pas chevaucher plusieurs bases de données.

Cependant, il présente également un énorme avantage: tout gestionnaire d'entités déclaré dans les EJB peut automatiquement partager le même contexte de persistance et peut donc fonctionner sur le même ensemble d'entités JPA et participer à la même transaction. Vous pouvez avoir une chaîne d'EJB appelant d'autres EJB de toute complexité et ils se comportent tous de manière sensée et cohérente par rapport aux données d'entité JPA. De plus, ils n'ont pas besoin de la complexité de l'initialisation/partage cohérent des références de gestionnaire d'entités à travers les appels de méthode - les EntityManagers peuvent être déclarés en privé dans chaque méthode. La logique d'implémentation peut être très simple.

La réponse à votre problème: utilisez des contextes de persistance gérés par l'application (via EntityManagers gérés par l'application)

Déclarez votre entityManager via l'une de ces approches:

// "Java EE style" declaration of EM
@PersistenceUnit(unitName="H2PU")
EntityManagerFactory emfH2;
EntityManager emH2 = emfH2.createEntityManager();

OR

// "JSE style" declaration of EM
EntityManagerFactory emfH2 = javax.persistence.Persistence.createEntityManagerFactory("H2PU");
EntityManager emH2 = emfH2.createEntityManager();

and the same for emfOracle & emOracle.    

Vous devez appeler em.close () lorsque vous avez terminé avec chaque EM - de préférence via une clause finale {} ou via une instruction Java 7 try-with-resources).

Les EM gérés par l'application participent toujours (c'est-à-dire se synchronisent avec) les transactions JTA. Un nombre illimité d'EM gérés par application peut participer à une seule transaction JTA - mais aucun d'entre eux n'aura jamais son contexte de persistance associé à ou propagé à un EM géré par conteneur .

Si l'EntityManager est créé en dehors du contexte d'une transaction JTA (avant le début de la transaction), vous devez lui demander explicitement de rejoindre la transaction JTA:

// must be run from within Java EE code scope that already has a JTA 
// transaction active:
em.joinTransaction();  

Ou encore plus simple, si l'EntityManager est créé dans le contexte d'une transaction JTA, l'EntityManager géré par l'application rejoint automatiquement l'implicité de la transaction JTA - aucune joinTransaction () n'est nécessaire.

Les EM gérés par application peuvent donc avoir une transaction JTA qui chevauche plusieurs bases de données. Bien sûr, vous pouvez toujours exécuter une transaction JDBC de ressource locale indépendante de JTA:

EntityTransaction tx = em.getTransaction();  
tx.begin();

// ....

tx.commit();

MODIFIER: DÉTAILS SUPPLÉMENTAIRES pour la gestion des transactions avec les gestionnaires d'entités gérées par l'application

AVERTISSEMENT: les exemples de code ci-dessous sont à usage éducatif - je les ai tapés du haut de ma tête pour aider à expliquer mes points et je n'ai pas eu le temps de compiler/déboguer/tester.

Le paramètre par défaut @TransactionManagement pour les EJB est TransactionManagement.CONTAINER et le paramètre par défaut @TransactionAttribute pour les méthodes EJB est TransactionAttribute.REQUIRED.

Il existe quatre permutations pour la gestion des transactions:

  • A) EJB avec des transactions JTA gérées par CONTAINER

    C'est l'approche préférée Java EE.
    Annotation @TransactionManagement de la classe EJB:
    doit être défini sur TransactionManagement.CONTAINER explicitement ou omis pour utiliser implicitement la valeur par défaut.
    Méthode EJB @TransactionAttribute annotation: doit définir explicitement TransactionAttribute.REQUIRED ou l'omettre pour implicitement utiliser la valeur par défaut. (Remarque: si vous aviez un scénario commercial différent, vous pourriez utiliser TransactionAttribute.MANDATORY ou TransactionAttribute.REQUIRES_NEW si leur sémantique correspondait à vos besoins.)
    Gestionnaires d'entités gérées par l'application:
    ils doivent être créés via Persistence.createEntityManagerFactory ("unitName") et emf.createEntityManager (), comme décrit ci-dessus.
    Rejoignez les EntityManagers avec la transaction JTA:
    Créez les EntityManagers DANS une méthode EJB transactionnelle et ils se joindront automatiquement à la transaction JTA. OR si les EntityManagers sont créés au préalable, appelez em.joinTransaction () dans une méthode EJB de transaction.
    Appelez EntityManager.close () lorsque vous avez fini de les utiliser. Cela devrait suffire.

    Exemples de base - utilisez simplement plus d'EntityManagers pour les transactions sur plusieurs bases de données:

    @Stateless  
    public class EmployeeServiceBean implements EmployeeService {
    
        // Transactional method
        public void createEmployee() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
            EntityManager em = emf.createEntityManager();
            Employee emp = ...; // set some data
            // No need for manual join - em created in active tx context, automatic join:
            // em.joinTransaction();         
            em.persist(emp);
            // other data & em operations ...
            // call other EJBs to partake in same transaction ...
            em.close();    // Note: em can be closed before JTA tx committed. 
                       // Persistence Context will still exist & be propagated 
                       // within JTA tx.  Another EM instance could be declared and it 
                       // would propagate & associate the persistence context to it.
                       // Some time later when tx is committed [at end of this 
                       // method], Data will still be flushed and committed and 
                       // Persistence Context removed .
        emf.close();
        }
    
    }
    
    
    
    @Stateful  
    public class EmployeeServiceBean implements EmployeeService {  
    
        // Because bean is stateful, can store as instance vars and use in multiple methods  
        private EntityManagerFactory emf;
        private EntityManager em;
    
        @PostConstruct      // automatically called when EJB constructed and session starts
        public void init() {
            emf = Persistence.createEntityManagerFactory("EmployeeService");
            em = emf.createEntityManager();
        }
    
        // Transactional method
        public void createEmployee() {
            Employee emp = ...; // set some data
            em.joinTransaction();         // em created before JTA tx - manual join
            em.persist(emp);
        }
    
        // Transactional method
        public void updateEmployee() {
            Employee emp = em.find(...);  // load the employee
            // don't do join if both methods called in same session - can only call once: 
            // em.joinTransaction();         // em created before JTA tx - manual join
            emp.set(...);                 // change some data
                                 // no persist call - automatically flushed with commit
        }
    
        @Remove                           // automatically called when EJB session ends
        public void cleanup() {
            em.close();
            emf.close();
        }
    // ...
    }
    
  • B) EJB avec transactions JTA gérées par BEAN

    Utilisez @ TransactionManagement.BEAN.
    Injectez l'interface JTA UserTransaction, afin que le bean puisse directement marquer les transactions JTA.
    Marquer/synchroniser manuellement la transaction via UserTransaction.begin ()/commit ()/rollback ().
    Assurez-vous que l'EntityManager rejoint la transaction JTA - créez l'EM dans un contexte de transaction JTA actif OR appelez em.joinTransaction ().

    Exemples:

    @TransactionManagement(TransactionManagement.BEAN)  
    @Stateless  
    public class EmployeeServiceBean implements EmployeeService {
    
        // inject the JTA transaction interface
        @Resource UserTransaction jtaTx;
    
        public void createEmployee() {
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");
            EntityManager em = emf.createEntityManager();
            try {
                jtaTx.begin();
                try {
                   em.joinTransaction();         
                   Employee emp = ...; // set some data
                   em.persist(emp);
                   // other data & em operations ...
                   // call other EJBs to partake in same transaction ...
                } finally {
                    jtaTx.commit();
                }
            } catch (Exception e) {
               // handle exceptions from UserTransaction methods
               // ...
            }
    
            Employee emp = ...; // set some data
            // No need for manual join - em created in active tx context, automatic join:
            // em.joinTransaction();         
            em.persist(emp);
            em.close();    // Note: em can be closed before JTA tx committed. 
                       // Persistence Context will still exist inside JTA tx.
                       // Data will still be flushed and committed and Persistence 
                       // Context removed some time later when tx is committed.
            emf.close();
        }
    
    }
    
  • C) POJO/non-EJB avec des transactions locales de ressources codées à la main (gérées par le bean) (pas JTA)

    Utilisez simplement l'interface JPA EntityTransaction pour la démarcation tx (obtenue via em.getTransaction ()).

    Exemple:

    public class ProjectServlet extends HttpServlet {
    
        @EJB ProjectService bean;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
            // ...
            try {
                EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
                EntityManager em = emf.createEntityManager();
                EntityTransaction tx = em.getTransaction();
                tx.begin();
                try {
                    bean.assignEmployeeToProject(projectId, empId);
                    bean.updateProjectStatistics();
                } finally {
                    tx.commit();
                }
            } catch (Exception e) {
               // handle exceptions from EntityTransaction methods
               // ...
            }
        // ...
        }
    }
    
  • D) POJO/non-EJB avec transactions JTA codées à la main (gérées par POJO)

    Cela suppose que le POJO/composant s'exécute dans un conteneur prenant en charge JTA.
    Si dans un conteneur Java EE, peut utiliser Java EE injection de ressources de l'interface JTA UserTransaction.
    (Alternativement, peut rechercher explicitement un handle vers l'interface JTA et faire une démarcation dessus, puis appeler em.getTransaction (). JoinTransaction () - voir spécification JTA.)

    Exemple:

    public class ProjectServlet extends HttpServlet {
    
        @Resource UserTransaction tx;
        @EJB ProjectService bean;
    
        protected void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
            // ...
            try {
                tx.begin();
                try {
                    bean.assignEmployeeToProject(projectId, empId);
                    bean.updateProjectStatistics();
                    EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
                    EntityManager em = emf.createEntityManager();
                    // Should be able to avoid explicit call to join transaction.
                    // Should automatically join because EM created in active tx context.
                    // em.joinTransaction();
                    // em operations on data here
                    em.close();
                    emf.close();
                } finally {
                    tx.commit();
                }
            } catch (Exception e) {
               // handle exceptions from UserTransaction methods
               // ...
            }
        // ...
        }
    }
    
45
Glen Best