Handle multiple EntityManager in Java EE application

时光毁灭记忆、已成空白 提交于 2019-11-29 10:55:27

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) {
        ...
    }
    ...
}

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.

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!