如何正确分层EJB3和servlet?

mda*_*win 5 java orm hibernate ejb jpa

我试图重构一个旧的应用程序,以使用EJB3和JPA.

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

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

我首先使用容器管理的事务用EJB3替换EJB2代码.由于hibernate Criteria非常简单,并且可以注入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();
  }
}
Run Code Online (Sandbox Code Playgroud)

无需单独的DAO层.帐户对象看起来有点像这样:

@Entity
@Table(name="er_accounts")
public class BasicAccount {
    @OneToMany( mappedBy="account", fetch=FetchType.LAZY)
    protected List<Subscription> subscriptions;
}
Run Code Online (Sandbox Code Playgroud)

但是在我调用EJB来获取帐户对象的servlet层中,我想构建一个可能(或可能不)包含来自BasicAccount的子订阅的响应:

servlet层看起来像这样:

ResponseBuilder rb;  

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

   ...
}
Run Code Online (Sandbox Code Playgroud)

显然这不起作用,因为当我们返回到servlet层时,事务和实体管理器已经关闭 - 我得到一个LazyInitializationException.

所以我可以看到一些选择:

  1. ServletFilter手动管理事务.这意味着我失去了EJB容器管理事务的好处,不是吗?另外,我必须在另一个客户端(而不是世界末端)上实现另一个过滤机制.
  2. 使用有状态会话bean,然后我可以使用扩展持久性上下文.但我真的不需要有状态bean,因为事务之间没有数据保留.所以它会给服务器带来不必要的负担,而我会使用Stateful来实现它并不是真正的设计用途.
  3. 调用Hibernate.init(acc.getSubscriptions())- 这将工作,但需要在EJB中完成.假设我重新使用bean作为另一个不需要订阅的客户端方法?不必要的DB调用.
  4. 在我的帐户对象上使用EAGER FetchType.性能不佳并产生不必要的DB负载.

这些选项似乎没有任何好处.

我错过了什么吗?该怎么做?我不能成为第一个遇到这个问题的人......

Vla*_*cea 6

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

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

渴望获取通常是代码气味,而服务层(EJB)负责获取数据.发现自己乱砍网络层以添加交易责任是打破应用程序层边界的标志.

更好的方法是使用DTO.JPA实体与持久性相关,它们泄漏数据库和ORM特定的获取检索机制.DTO更适合,因为它可以最小化获取和发送到Web层的数据量,因此是渲染视图的理想选择.虽然您实际上可以在服务层和Web层中使用实体,但是在数据投影是更好的替代方案时存在用例.

  • 很棒的解释.喜欢你在"为什么"的答案中包含其他信息. (2认同)