如何正确分层EJB3和servlet?

时间:2015-06-12 15:25:17

标签: java hibernate jpa orm ejb

我正在尝试重构旧的应用程序以将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();
  }
}

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

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

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

servlet层如下所示:

ResponseBuilder rb;  

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

   ...
}

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

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

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

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

1 个答案:

答案 0 :(得分: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();
  }
}

Eager fetching is most often a code smell并且它是获取数据的服务层(EJB)职责。发现自己乱砍网络层以增加交易责任是打破应用层边界的一个标志。

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