Java 如何正确分层EJB3和servlet?

Java 如何正确分层EJB3和servlet?,java,hibernate,jpa,orm,ejb,Java,Hibernate,Jpa,Orm,Ejb,我正在尝试重构一个旧的应用程序,以便在JPA中使用EJB3 我们有两个客户机层(一个基于servlet,一个不基于servlet),它们都调用委托层,委托层调用EJB层,而EJB层又调用DAO。EJB是EJB2(bean管理的持久性),DAO使用手动滚动的SQL查询、提交事务和手动关闭连接 我想用EJB3替换EJB2,并将所有DAO更改为使用JPA 我首先使用容器管理的事务将EJB2代码替换为EJB3。由于hibernate标准非常简单,并且可以注入EntityManager,因此我可以这样做:

我正在尝试重构一个旧的应用程序,以便在JPA中使用EJB3

我们有两个客户机层(一个基于servlet,一个不基于servlet),它们都调用委托层,委托层调用EJB层,而EJB层又调用DAO。EJB是EJB2(bean管理的持久性),DAO使用手动滚动的SQL查询、提交事务和手动关闭连接

我想用EJB3替换EJB2,并将所有DAO更改为使用JPA

我首先使用容器管理的事务将EJB2代码替换为EJB3。由于hibernate标准非常简单,并且可以注入EntityManager,因此我可以这样做:

@Stateless
public class NewSelfcareBean implements SelfcareTcApi {

@PersistenceContext(unitName="core")
EntityManager em;

public BasicAccount getAccount(String id) {
  Criteria crit = getCriteria(BasicAccount.class);
  crit.add(Restrictions.eq("id", id));
  BasicAccount acc = (BasicAccount) crit.uniqueResult();
  }
}
@Entity
@Table(name="er_accounts")
public class BasicAccount {
    @OneToMany( mappedBy="account", fetch=FetchType.LAZY)
    protected List<Subscription> subscriptions;
}
ResponseBuilder rb;  

public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
   ...
   Account acc = getDelegateLayer().getAccount();
   rb.buildSubscriptionResponse(acc.getSubscriptions());

   ...
}
不需要单独的DAO层。account对象看起来有点像这样:

@Stateless
public class NewSelfcareBean implements SelfcareTcApi {

@PersistenceContext(unitName="core")
EntityManager em;

public BasicAccount getAccount(String id) {
  Criteria crit = getCriteria(BasicAccount.class);
  crit.add(Restrictions.eq("id", id));
  BasicAccount acc = (BasicAccount) crit.uniqueResult();
  }
}
@Entity
@Table(name="er_accounts")
public class BasicAccount {
    @OneToMany( mappedBy="account", fetch=FetchType.LAZY)
    protected List<Subscription> subscriptions;
}
ResponseBuilder rb;  

public void doPost(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
   ...
   Account acc = getDelegateLayer().getAccount();
   rb.buildSubscriptionResponse(acc.getSubscriptions());

   ...
}
显然,这不起作用,因为当我们回到servlet层时,事务和entitymanager已经关闭——我得到一个LazyInitializationException异常

因此,我可以看到几个选项:

  • ServletFilter可手动管理事务。这意味着我失去了EJB容器管理事务的好处,不是吗?另外,我还必须在另一个客户机上实现另一种过滤机制(而不是世界末日)
  • 改用有状态会话bean,然后我可以使用扩展持久性上下文。但我并不真正需要有状态bean,因为事务之间不保留任何数据。因此,它会在服务器上增加不必要的负载,而我会使用Stateful来实现它并非真正设计的目的
  • 调用
    Hibernate.init(acc.getSubscriptions())
    -这将起作用,但需要在EJB中完成。假设我将bean重新用于另一个不需要订阅的客户机方法?不必要的数据库调用
  • 在我的帐户对象上使用类型。对性能不利,会产生不必要的数据库负载
  • 这些选择似乎都不好


    我错过什么了吗?如何做到这一点?我不是第一个遇到这个问题的人…

    两个抓取策略意味着两个用例,因此在这种情况下,最好编写两种方法:

    public BasicAccount getAccount(String id) {
      Criteria crit = getCriteria(BasicAccount.class);
      crit.add(Restrictions.eq("id", id));
      BasicAccount acc = (BasicAccount) crit.uniqueResult();
      }
    }
    
    public BasicAccount getAccountWithSubscriptions(String id) {
      Criteria crit = getCriteria(BasicAccount.class);
      crit.add(Restrictions.eq("id", id));
      crit.setFetchMode("subscriptions", FetchMode.JOIN);
      BasicAccount acc = (BasicAccount) crit.uniqueResult();
      }
    }
    
    获取数据是服务层(EJB)的责任。发现自己侵入web层以增加事务责任是打破应用层界限的标志


    更好的方法是使用DTO。JPA实体与持久性相关,它们泄漏数据库和特定于ORM的抓取检索机制。DTO更适合,因为它可以最大限度地减少提取和发送到web层的数据量,因此非常适合渲染视图。尽管您实际上可以在服务层和web层中使用实体,但在某些情况下,数据投影是一个更好的选择。

    这是一个非常典型的急/懒抓取问题

    解决方案1(不是最好的):创建两个方法,一个是
    getBasicAccountWithSubscription()
    ,另一个是
    getBasicAccount()
    ,就像弗拉德说的那样。这是我的代码。假设你还有10种这样的关系。为每个可能的组合创建这样一个方法将生成2^10(指数)个新方法(如
    GetBasicCountWithSubscriptionWithOutlastLoginWithTelephones…()
    ),这是不好的。当然,您可以生成一个包含10个布尔参数的方法,这些参数告诉您要获取哪个关系,但在本例中,该方法将混合使用DB stuff(要加载的关系)和业务逻辑参数(您案例中的BasicAccount的ID)


    解决方案2(最好的IMHO):在EJB中创建另一个方法:
    List getSubscriptionsForAccount(长accountId)(属性被延迟获取)。在需要订阅帐户的位置调用新方法。为了确保没有人调用您的
    BasicAccount.getSubscriptions()
    方法,您可以将其设置为私有。如果您有10个关系,那么您将为每个关系创建一个新方法(从而减少新方法的数量)。例如,如果您有另一个关系
    私人列表登录
    在您的
    BasicAccount
    实体中,您可以向EJB服务
    公共列表getLoginsForAccount(Long accountId)

    添加另一个方法,在对相关EJB的单独调用(单独事务)中单独查询与帐户关联的订阅列表。您可以在事务中获取基于帐户的订阅列表,并在单独的调用中由EJB方法返回。为什么不创建两个方法,一个用于BasicaCount,另一个用于预取订阅的BasicaCount?然后让您的应用程序选择一个needs@Chris,这可能会起作用-我甚至可以只使用一个方法,但提供一个布尔标志来指示是否需要订阅。但它有一股很难闻的代码味道。是否存在与Hibernate.initialize()等效的JPA?我不知道Hibernate的initialize()如何工作:它是特定于提供程序的,其他提供程序不允许在分离的实例上加载惰性关系。我不知道它在您的情况下会如何工作,就像您可以访问持久化单元一样,您可以使用JPA查询来获取集合,正如Tiny的帖子中所建议的那样。JPA还添加了实体图,以显示应用程序对对象模型的哪些部分感兴趣。我喜欢你在“为什么”的答案中添加额外的信息。这个应用程序已经有12年的历史了,而且非常混乱,所以我不愿意再为DTO添加一层代码。但是,我确实喜欢使用setFetchMode有效地强制惰性连接上的急切加载。我们已经测试过了,它是有效的。谢谢问题是,它只在您只有一个集合时起作用。如果有多个集合,则会得到org.hibernate.loader.multipleBaggFetchException:无法同时获取多个bagsHi,我不清楚解决方案2应该如何工作。另外,t的一个解决方法