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