开发者

How to use JPA EclipseLink to connect to the DB in a multi layer environment (REST + EJB + Core)?

I am using Glassfish v3 server.

Usually the DB connection with EJB3 + JPA (Eclipselink) is 开发者_JS百科done through injection, with @PersistenceUnit or @Persistencecontext.

However, there are 3 layers in my App :

  • Core (contains business logic, entities, exception handling etc)

  • an EJB on top of it, calling the right core objects and methods to do the job. This EJB is called by other internal modules of our ERP.

  • a REST layer on top of it for uses by frontend web sites.

I do not want to get the entityManager, nor the EMF (EM factory) in the EJB, because I want my middle layer to be unaware that there is a DB used under it. I could after all, later, decide to change my core implementation for a non-DB-using one.

I see only two bad solutions :

  • 1) Add an EM parameter every time i call a method of the core layer that needs DB connection. Very ugly and goes againt what I said above.

  • 2) In every method of core needing DB connection, I create a factory, an EM, use them, and then close them both.

I tried to cut things in the middle, having one factory per class of the Core level, and EMs are created and closed in every method. But I still have memory leaks like this :

javax.servlet.ServletException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DatabaseException

Internal Exception: java.sql.SQLException: Error in allocating a connection. Cause: In-use connections equal max-pool-size and expired max-wait-time. Cannot allocate more connections.

I guess it's because if one of my EJB methods uses 10 different objects, it creates 10 EM factories, and none of them is closed.

Example of typical use in a Core object:

 EntityManager em = emf.createEntityManager();
 em.getTransaction().begin();
 // do some stuff with em; for example persist, etc
 em.flush();
 em.close();

Should I go for solution 2? Is there a way to use a single EM factory at this core level ? I seems like the JPA spec assumes you're going to use the entities at the EJB level only, which is bad in a multi layers apps.

EDIT : here is the current status after trying @Inject :

  • Added an empty beans.xml file in the /META-INF directory on my CORE jar.

  • The parent DAO class is now like this :

    public class ExampleBZL {

     public EntityManagerFactory emf;
     @Inject public Emf emfobject;
    
     public ExampleBZL()
     {
        this.emf = emfobject.emf;
     }
    
  • The Emf class is very simple, and Stateless.

    @Stateless public class Emf implements EmfAbstract {

    @PersistenceUnit(unitName = Setup.persistenceUnitName)
    public EntityManagerFactory emf;
    
    public Emf()
    {
    }
    

    }

I must be doing something wrong, but the injection doesn't work, altough in glassfish I see "[ejb, weld, web]" in the engines list, so the CDI is loaded.

Servlet.service() for servlet Jersey Web Application threw exception
java.lang.NullPointerException
    at com.blablabla.core.bizlogic.ExampleBZL.<init>(ExampleBZL.java:40)

Am I missing other annotations ?? Is it really working to do an inection in a JAR with those two small annotations (Stateless on one side, Inject on the other) ?


With JavaEE 6 you can define your core level classes as Beans and inject resources there. Please check Context and Dependency Injection (CDI) with JavaEE 6.


What if you had two session beans? One with the injected EntityManager that can leverage JTA, and the other is your current session bean.

I am currently putting together a series on my blog using a session bean as the REST service using EclipseLink & Glass Fish v3:

  • Part 1 - The Database Model
  • Part 2 - Mapping the Database Model to JPA Entities
  • Part 3 - Mapping JPA Entities to XML using JAXB
  • Part 4 - The RESTful Service

Below is how I inject the EntityManager on my session bean that is serving as my REST service:

package org.example.customer;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.ws.rs.Path;

import org.eclipse.persistence.rest.JPASingleKeyResource;

@Stateless
@LocalBean
@Path("/customers")
public class CustomerService  {

    @PersistenceContext(unitName="CustomerService", type=PersistenceContextType.TRANSACTION)
    EntityManager entityManager;

}

You can link your session beans using the @EJB annotation:

package org.example;

import javax.ejb.EJB;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.naming.Context;
import javax.naming.InitialContext;

@Stateless
@LocalBean
@EJB(name = "someName", beanInterface = CustomerService.class)
public class OtherSessionBean {

    public Customer read(long id) {
        try {
            Context ctx = new InitialContext();
            CustomerService customerService = (CustomerService) ctx.lookup("java:comp/env/someName");
            return customerService.read(id);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }

}


I am not sure it is a good answer, but I have found this : link of forum saying :

it appears that interaction between JPA and CDI was considered but not made part of the spec for some unknown reason. If I come to know of the reason, I shall update this thread. In the mean while, it has been sent as a feedback to appropriate folks. So, this one is definitely not a bug in GlassFish.

So, does that explain why my @Inject (of a class containing an entity manager) in a java class is not working ?


Here is the final working code, thanks to Blaise :

  • The father class that "receives" the connection

    import com.wiztivi.apps.wsp.billing.interfaces.bin.db.NewInterface; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.naming.Context; import javax.naming.InitialContext; import javax.persistence.EntityManager;

    @Stateless
    @LocalBean
    public class FatherService {
    
     public EntityManager em;
    
     public FatherService()
     {
     }
    
    
    
     public EntityManager getGoodEm()
     {
        try {
            Context ctx = new InitialContext();
            NewInterface dp = (NewInterface) ctx.lookup("java:global/billing-ear/billing-connection/DataProvider");
            em = dp.getEm();
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
         return em;
     }
    
    }
    
  • The class who "provides" the connection (in a separate connection JAR, with the entities)

    import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.PersistenceContextType;

    @Stateless @LocalBean public class DataProvider implements NewInterface {

    @PersistenceContext(unitName=Setup.persistenceUnitName, type=PersistenceContextType.TRANSACTION)
    public EntityManager entityManager;
    
    public DataProvider() {
    }
    
    @Override
    public EntityManager getEm()
    {
        return entityManager;
    }
    

    }

Something important : You have to put @Stateless on any class of "higher level" layer" that will call the FatherService EJB (in my case, the REST classes). The Core layer must be packaged as an EJB, and the connection too, both in an EAR

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜