【问题标题】:How to use JPA EclipseLink to connect to the DB in a multi layer environment (REST + EJB + Core)?如何在多层环境(REST + EJB + Core)中使用 JPA EclipseLink 连接数据库?
【发布时间】:2011-04-01 02:29:41
【问题描述】:

我正在使用 Glassfish v3 服务器。

通常与 EJB3 + JPA (Eclipselink) 的 DB 连接是通过注入完成的,使用 @PersistenceUnit 或 @Persistencecontext。

但是,我的应用中有 3 层:

  • 核心(包含业务逻辑、实体、异常处理等)

  • 在其之上的 EJB,调用正确的核心对象和方法来完成这项工作。这个 EJB 被我们 ERP 的其他内部模块调用。

  • 其上的 REST 层供前端网站使用。

我不想在 EJB 中获得 entityManager,也不想获得 EMF(EM 工厂),因为我希望我的中间层不知道它下面使用了一个 DB。毕竟,我以后可以决定将我的核心实现更改为不使用 DB 的实现。

我只看到两个不好的解决方案:

  • 1) 每次调用核心层需要DB连接的方法时,添加一个EM参数。非常丑陋,与我上面所说的背道而驰。

  • 2) 在需要 DB 连接的每个核心方法中,我创建一个工厂、一个 EM,使用它们,然后将它们都关闭。

我试图在中间切开一些东西,每个核心级别的类都有一个工厂,并且在每种方法中都会创建和关闭 EM。但我仍然有这样的内存泄漏:

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.

我猜是因为如果我的 EJB 方法之一使用 10 个不同的对象,它会创建 10 个 EM 工厂,并且没有一个是关闭的。

Core 对象中的典型使用示例:

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

我应该选择解决方案 2 吗? 有没有办法在这个核心级别使用单个 EM 工厂?我似乎 JPA 规范假设您将仅在 EJB 级别使用实体,这在多层应用程序中很糟糕。

编辑:这是尝试@Inject 后的当前状态:

  • 在我的 CORE jar 的 /META-INF 目录中添加了一个空 beans.xml 文件。

  • 父 DAO 类现在是这样的:

    公共类 ExampleBZL {

     public EntityManagerFactory emf;
     @Inject public Emf emfobject;
    
     public ExampleBZL()
     {
        this.emf = emfobject.emf;
     }
    
  • Emf 类非常简单,而且是无状态的。

    @无状态 公共类 Emf 实现 EmfAbstract {

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

    }

我一定是做错了什么,但注入不起作用,尽管在 glassfish 中我在引擎列表中看到“[ejb,weld,web]”,所以 CDI 已加载。

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

我是否缺少其他注释?用这两个小注释(一侧是无状态,另一侧是注入)在 JAR 中进行注入真的有效吗?

【问题讨论】:

    标签: java jpa persistence eclipselink entitymanager


    【解决方案1】:

    使用 JavaEE 6,您可以将核心级别的类定义为 Bean 并在其中注入资源。 请检查 JavaEE 6 的上下文和依赖注入 (CDI)。

    【讨论】:

    • 非常有趣。到目前为止从未使用过 CDI,所以我尝试在我的 Core DAO 超类中添加一个@Inject,请参阅上面的编辑,但无法让它工作:-(
    • CDI 现在有很多错误,这取决于你如何打包东西,它是否有效(例如参见glassfish.dev.java.net/issues/show_bug.cgi?id=11653 和其他)。所以我放弃了这个想法。还是谢谢。
    【解决方案2】:

    如果你有两个会话 bean 怎么办?一个带有可以利用 JTA 的注入 EntityManager,另一个是您当前的会话 bean。

    我目前正在使用 EclipseLink 和 Glass Fish v3 将会话 bean 用作 REST 服务,在 my blog 上整理一个系列:

    以下是我如何将 EntityManager 注入到充当 REST 服务的会话 bean 中:

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

    您可以使用 @EJB 注释链接会话 bean:

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

    【讨论】:

    • 谢谢,我会等你的完整系列。不确定如何从会话一中调用“EntityManagered”bean,是否会再次注入?
    • 我在我的博客上发布了更多的 REST 示例(只是缺少客户端部分)。我会在会话 bean 交互方面与您联系。
    • 与您的示例的区别:您在同一类中有 REST 层 + EJb。这对我们来说不是这样,因为我们希望能够在 EJB 之上添加其他接口(比如 SOAP 或其他任何东西)。此外,主要问题是在核心类中获取 EJB 外部的实体管理器。好吧,我尝试创建另一个 EJB,其中包含 entityManager 工厂。我没有做@Inject,而是在我父亲的“数据处理”类中做一个@EJB......并且还得到一个空指针..
    • 明白。我添加了一个示例,说明如何引用另一个会话 bean。我认为如果您可以让会话 bean 负责您的持久性操作,您会发现它会更容易。
    • 成功地做了一些从你的例子中得到启发的事情。出于某种奇怪的原因,它仅在我对连接 EJB(而不是 @EJB)进行 JNDI 查找时才有效...
    【解决方案3】:

    我不确定这是一个好的答案,但我发现了这个: link of forum 说:

    似乎 考虑过 JPA 和 CDI,但未考虑 成为一些未知的规范的一部分 原因。如果我知道 原因,我将更新此线程。在 同时,它已作为 反馈给适当的人。所以, 这绝对不是一个错误 玻璃鱼。

    那么,这是否解释了为什么我在 java 类中的@Inject(包含实体管理器的类)不起作用?

    【讨论】:

    • 不,它没有。以上只是意味着您不能注入 JPA 实体。
    【解决方案4】:

    这是最终的工作代码,感谢 Blaise:

    • “接收”连接的父类

      导入 com.wiztivi.apps.wsp.billing.interfaces.bin.db.NewInterface; 导入 javax.ejb.LocalBean; 导入 javax.ejb.Stateless; 导入 javax.naming.Context; 导入 javax.naming.InitialContext; 导入 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;
       }
      
      }
      
    • “提供”连接的类(在单独的连接 JAR 中,带有实体)

      导入 javax.ejb.LocalBean; 导入 javax.ejb.Stateless; 导入 javax.persistence.EntityManager; 导入 javax.persistence.PersistenceContext; 导入 javax.persistence.PersistenceContextType;

      @无状态 @LocalBean 公共类 DataProvider 实现 NewInterface {

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

      }

    一些重要的事情:您必须将@Stateless 放在将调用FatherService EJB 的任何“更高级别”层的类(在我的例子中是REST 类)。 核心层必须打包为 EJB,连接也必须打包在 EAR 中

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-03-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多