views:

239

answers:

4

I am using Glassfish v3 server.

Usually the DB connection with EJB3 + JPA (Eclipselink) is 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) ?

+1  A: 

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.

Padmarag
Very interesting. Never used CDI up to now, So I tried to add an @Inject in my Core DAO super class, see above edit, but can't get it to work :-(
spiritoo
CDI is very bugged for now, depending on how you package things, it works or not (see for example https://glassfish.dev.java.net/issues/show_bug.cgi?id=11653 and others). So I abandoned this idea. Thanks anyway.
spiritoo
A: 

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:

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);
        }
    }

}
Blaise Doughan
Thanks, I'll wait for your full series. Not sure how you call the "EntityManagered" bean from the session one, would it be with injection again ?
spiritoo
I have posted more of the REST example on my blog (just missing the client piece). I will get back to you on the session bean interaction aspect.
Blaise Doughan
the difference with your example : you have the REST layer + EJb in the same class. That's not the case for us, because we want to be able to add other interfaces (say SOAP or anything else) on top of the EJB. Also, the main problem is to get an entity manager OUTSIDE of the EJB, in a core class.... So OK, I tried to create another EJB, which contains the entityManager Factory. Instead of doing an @Inject, I do an @EJB in my father "data handling" class... and also get a null pointer ..
spiritoo
Understood. I've added an example of how you can reference another session bean. I think you'll find it easier if you can have a session bean responsible for your persistence operations.
Blaise Doughan
Succeeded to do something inspired from your example. For some odd reason it only works if I do a JNDI lookup of the connection EJB (and not with @EJB)...
spiritoo
A: 

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 ?

spiritoo
No, it doesn't. The above just means you can't inject in JPA entities.
Pascal Thivent
A: 

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

spiritoo