我正在尝试重构旧的应用程序以将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。
所以我可以看到一些选择:
Hibernate.init(acc.getSubscriptions())
- 这将起作用,但需要在EJB中完成。假设我将bean重新用于另一个不需要订阅的客户端方法?不必要的数据库调用。这些选项似乎没有任何好处。
我错过了什么吗?该怎么做?我不能成为第一个遇到这个问题的人......
答案 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层中使用实体,但是在数据投影是更好的替代方案时会有用例。