Handle multiple EntityManager in Java EE application

后端 未结 3 869
夕颜
夕颜 2020-12-19 10:29

I have Java EE application with about 10 EntityManagers (number of EMs will probably increase). My application also contains many stateless, statefull and message driven bea

相关标签:
3条回答
  • 2020-12-19 11:23

    Container managed entity managers are automatically propagated with the current JTA transaction and EntityManager references that are mapped to the same persistence unit provide access to the persistence context within that transaction. So it's not good practice to share an entity manager from a singleton, apart from concurrency problems, it would result in using the same transaction context for every method you call on your beans.
    A simple solution to your need is to inject EntityManagerFactory references in your beans and create EntityManager objects calling the createEntityManager() method. The drawback is that you should manage transactions manually, no more relying on the container.
    Otherwise another approach could be inject all of your entity managers in a main enterprise bean and implement business logic in service beans with methods to which you pass the appropriate managers. An example of the latter solution:

    @Stateless
    class MainBean {
        @PersistenceContext EntityManager em1;
        @PersistenceContext EntityManager em2;
        ...
        @EJB WorkerBean1 workerBean1;
        @EJB WorkerBean2 workerBean2;
        ...
        void method1(Object param1, Object param2) {
            workerBean1.method1(em1, param1, param2);
        }
    
        void method2(Object param1, Object param2, Object param3) {
            workerBean2.method2(em2, param1, param2, param3);
        }
        ...
    }
    
    @Stateless
    class WorkerBean1 {
        void method1(EntityManager em, Object param1, Object param2) {
            ...
        }
        ...
    }
    
    @Stateless
    class WorkerBean2 {
        void method2(EntityManager em, Object param1, Object param2, Object param3) {
            ...
        }
        ...
    }
    
    0 讨论(0)
  • 2020-12-19 11:23

    Composite persistence units - Java EE

    The way to handle multiple entity managers, i.e. multiple persistence units, in Java EE is to use composite persistence units (CPUs). Such a composite persistence unit can be assessed from one single point in the EE web-application, a datalayer. This needs to be a @Stateless EE bean though in order to work with the @PersistenceContext.

    Composite persistence units have been introduced to make possible reusing entity classes, among various Java applications. CPUs are a feature of Enterprise architecture. I choose to use EclipseLink as showcase, as I have positive experience with that from a running production application.

    Introduction

    In some cases, entities contain general data that is needed across more web-services in a server landscape. Take for example a general ‘name-address’ entity, a ‘user-password-role’ entity, a ‘document-keyword-index’ entity, etc. A composite persistence unit implementation facilitates that the source of each entity definition is specified in only one place (‘single point of definition’). These entity definitions can subsequently be included in each Java web-application that needs this entity access.

    Working of composite persistence unit

    The working of a composite persistence unit is illustrated by the following tutorial: EclipseLink composite persistence units

    The concept of composite persistence units works by first defining member persistence units. Each member persistence unit may be associated with a different database, but the member persistence units can also all refer to the same actual database. I have experience with the latter, where EclipseLink (version 2.6.4) was used in combination with one Postgress database.

    Maven is needed to make possible the required modular approach.

    Settings in persistence.xml

    A composite persistence unit member is defined as follows: Program a group of related entities (Java @Entity classes), one-by-one, in a dedicated Maven module. Define in this Maven module also a composite persistence unit member (important!). The composite unit member PuPersonData refers to this set of related entities that characterizes person data. Define the member persistence unit PuPersonData as (

    <persistence-unit name="PuPersonData" transaction-type="JTA">
    ...
        <jta-data-source>jdbc/PostgresDs</jta-data-source>
    ...
    

    ).

    In a second Maven module, define another composite persistence unit member, PuWebTraffic (

    <persistence-unit name="PuWebTraffic" transaction-type="JTA">
    ...
        <jta-data-source>jdbc/PostgresDs</jta-data-source>
    ...
    

    ). Include here other entities (Java classes denoted with @Entity) that store data about web-transactions, logon, sessions, etc. Needless to state, the two composite persistence unit members must be disjoint with respect to entities, no overlap is allowed in entity-names.

    Both persistence unit members have in their XML-definitions the property:

    <properties>
        <property name="eclipselink.composite-unit.member" value="true"/>
        ...
    </properties>
    

    Composite persistence unit

    We now define in a third Maven module the composite persistence unit CPuPersonSessionData that includes both the persistence units members PuPersonData and PuWebTraffic.

    <persistence-unit name="CPuPersonSessionData" transaction-type="JTA">
    

    This composite persistence unit CPuPersonSessionData refers to the two persistence unit members, PuPersonData and PuWebTraffic, by means of including the jars that result from compilation of the two pertaining Maven modules.

    ...
        <jar-file>PuPersonData.jar</jar-file>
        <jar-file>PuWebTraffic.jar</jar-file>
    ...
    

    In the XML-definition of the composite persistence unit, the following property needs to be set

    <properties>
        <property name="eclipselink.composite-unit" value="true"/>
        ...
    </properties>
    

    This setting ensures that the composite persistence unit is treated differently by Java EE than its persistence unit members.

    Use of persistence unit in Java

    In the Java web-application that is going to store and retrieve entities with both person-data and traffic-data, only the composite persistence unit is included

    @Stateless
    public class DataLayer {
    
        @PersistenceUnit(unitName="CPuPersonSessionData")
        EntityManager em; 
        ...
    

    The normal 'em' operations such as persist, find and merge can now be performed on each entity, contained in one of the composite entity members.

    Under Payara, no XA-transactions were needed for this composite persistence unit to address the entities pertaining to each of the persistence unit members.

    Maven

    The Maven parent POM file needs to contain the specifications for the pertaining modules.

    ...
        <modules>
                <module>PersonData</module>
                <module>WebTraffic</module>
                <module>PersonSessionData</module>
        </modules>
    ...
    

    The POM-file of each module needs to be configured as a normal Maven-project, referring to the parent POM-file.

    Pitfalls:

    • You need to configure the Maven multi-module project correctly, which can be somewhat tricky. Each composite persistence unit member constitutes a separate Maven module. Also the composite persistence unit is a separate Maven module. The members need to be compiled first, in Maven sequence.
    • The ‘jars’ in the composite persistence unit need to be found when compiling the module of the composite persistence unit.
    • The entities of each composite persistence unit member need to be available in the resulting ‘jar’, directly in the ‘classes’ directory (adding extra paths to the entities, via Maven, is possible but complex).
    • The ‘jars’ of the persistence unit members need to be available in the ‘classes’ directory for the composite persistence unit to find them.

    The benefit gained is a neat Enterprise data-layer that works with reusable entities, each with one central definition. Moreover, it is possible to perform cross-unit native SQL-queries. I got this to work also.

    Documentation states that cross-unit native queries will not work when the composite persistence unit members run on different, actual databases. This should still be verified.

    0 讨论(0)
  • 2020-12-19 11:33

    An entity manager is not supposed to be thread-safe, so you shouldn't share ones via a Singleton. It's the same reason as why you should not inject an entity manager into a Servlet, and why a lookup from JNDI in such a web component -should- return a different instance of the entity manager ever time.

    In practice some implementations may provide an entity manager that is thread-safe, so during testing it may seem to work. However, for the sake of portability and to protect you against upgrade woes, you should never rely on this.

    Instead of inheriting from a common base class, you could define all your entity managers in one bean, and inject that wherever you need an entity manager.

    E.g.

    @Stateless
    public class EntityManagerProviderBean {
    
        @PersistenceContext(unitName="foo")
        private EntityManager entityManagerFoo;
    
        @PersistenceContext(unitName="bar")
        private EntityManager entityManagerBar;
    
        public EntityManager getEntityManager() {
            return ...? entityManagerFoo : entityManagerBar;
        }
    }
    

    (where ... is the logic you use to select the right entity manager)

    Inject this into a bean needing an entity manager:

    @Stateless
    public class MyService {
    
        @EJB
        private EntityManagerProviderBean entityManagerProvider;
    
        public void doStuff(MyEntity myEntity) {
            entityManagerProvider.getEntityManager().update(myEntity);
        }
    
    }
    

    Alternatively the following would perhaps be even neater:

    @Stateless
    @PersistenceContexts({ 
        @PersistenceContext(unitName="foo", name = "fooENC"),
        @PersistenceContext(unitName="bar", name = "barENC") }
    )
    public class EntityManagerProviderBean {
    
        @Resource
        private EJBContext context;
    
        public EntityManager getEntityManager() {
            return (EntityManager) context.lookup(... ? "fooENC" : "barENC");
        }
    }
    

    The last example maps all persistence contexts into the ENC of the bean, where they can be conveniently retrieved programmatically.

    Unfortunately, people forgot to add tests for the latter syntax to the TCK and subsequently major vendors forgot to implement it (see http://java.net/jira/browse/JPA_SPEC-38 and https://issues.jboss.org/browse/AS7-5549), so test if this works on your server.

    0 讨论(0)
提交回复
热议问题