使用模型虚包

状态:在 Early Access 版本阶段
作者:Sean Brydon 及 Smitha Kangath

问题描述

Java EE 5 应用程序可以使用 Java 持久性 API 来创建域模型。在设计用来访问模型层中的实体的代码时,调用客户端的代码(如 Servlet、JSP 页或 JSF 受管 Bean)将访问实体 API。通过 Java 持久性实体,可以向客户端公开事务 API、实体管理器 API 以及访问模型中实体所需的持久性 API 的其他功能。另外,客户端代码可能需要与几个实体进行交互,每个实体具有不同的 API 并且可能具有不同的事务和实体管理器操作要求。随着应用程序变得越来越大,代码可能包含多个供模型客户端使用的 API,当客户端代码与实体模型紧密耦合在一起时,可能很难对其进行管理。

解决方案

“虚包”模式定义了一个更高级别的类,它对进行调用的客户端与模型实体和 Java 持久性 API 操作之间的交互进行了封装和集中管理。为一系列实体操作提供了一个简单的接口。使用此虚包的客户端不必了解每个实体的 API 全部细节,也不必了解在访问实体管理器或其他事务管理器时使用的任何持久性机制。通过在进行调用的客户端和模型层中的实体之间引入虚包,可使代码与模型的耦合更松散,因而更易于维护。

正如代码示例 1 所示,使用虚包的代码更加简单明了。不使用虚包时,实体客户端需要了解实体的细节,每次需要访问实体时,您可能需要在其他地方剪切并粘贴类似的代码。引入虚包后,代码将隐藏模型层中实体的很多细节。

之前的代码 引入虚包后的代码
public class MyWebClient ... {
  
  ...


  public void doGet(HttpServletRequest request,
            HttpServletResponse response)
{
...
//processing a web request to
//add a new Item to the database
if (selectedURL.equals("additem.do")) {
  String desc = request.getParameter("item_desc");
  String name = request.getParameter("item_name");
  ...
  Item item = new Item();
  item.setName(name);
  item.setDescription(desc);

 
//now access entities in model tier
  EntityManager em =
          emf.createEntityManager();
    try{
      utx.begin();
      em.joinTransaction();
      em.persist(item);
      utx.commit();
    } catch(Exception exe){
            System.out.println("Error persisting item: "+exe);
        try {
            utx.rollback();
        } catch (Exception e) {}
    } finally {
        em.close();
    }
  }
...

public class MyWebClient { ...

...

  public void doGet(HttpServletRequest request,
            HttpServletResponse response)
{
...
//processing a web request to
//add a new Item to the database
if (selectedURL.equals("additem.do")) {
  String desc = request.getParameter("item_desc");
  String name = request.getParameter("item_name");
  ...
  Item item = new Item();
  item.setName(name);
  item.setDescription(desc);
  //now access entities in model tier
  MyModelFacade myfacade = new 
MyModelFacade(); 
  myfacade.addItem(item);
  }
代码示例 1:通过引入虚包来访问模型层中实体以重构客户端的调用代码

以下是设计虚包时应考虑的一些设计选择: 让我们考虑一下,在使用的虚包中所做的一些选择。在示例应用程序中,我们展示了 Web 虚包和会话 Bean 虚包。下面对这些示例进行了更详细的讨论。

策略 1:使用 Web 组件虚包

实现虚包的一种策略是使用 Web 组件作为虚包类。如果要采用仅限 Web 的体系结构并且不想使用 EJB 容器,或者只想在 Web 容器中保存虚包,此策略是非常有用的。如果应用程序使用仅限 Web 的体系结构,并且包含访问实体的 Web 组件,则使用虚包可有助于简化代码。

下面讨论了生成 Web 虚包的实现细节

public class CatalogFacade implements ServletContextListener {

 @PersistenceUnit(unitName="CatalogPu") private EntityManagerFactory emf;
 @Resource UserTransaction utx;

 public CatalogFacade(){}

 public void contextDestroyed(ServletContextEvent sce) {
   if (emf.isOpen()) {
     emf.close();
   }
 }

 public void contextInitialized(ServletContextEvent sce) {
   ServletContext context = sce.getServletContext();
   context.setAttribute("CatalogFacade", this);
 }

 public void addItem(Item item) throws InvalidItemException {
   EntityManager em = emf.createEntityManager();
   if(item.getName().length() == 0)
     throw new InvalidItemException("The item" + " name cannot be empty. Please specify a name for the item. ");

   try {
     utx.begin();
     em.joinTransaction();
     em.persist(item);
     utx.commit();
   } catch(Exception exe){
     System.err.println("Error persisting item: "+exe);
     try {
       utx.rollback();
     } catch (Exception e) {
       throw new RuntimeException("Error persisting item: "+ e.getMessage(), e);
     }
     throw new RuntimeException("Error persisting item: "+ exe.getMessage(), exe);
   } finally {
     em.close();
   }
 }

 public Item getItem(int itemID){
   EntityManager em = emf.createEntityManager();
   Item item = em.find(Item.class,itemID);
   em.close();
   return item;
 }

 public List getAllItems(){
   EntityManager em = emf.createEntityManager();
   List items = em.createQuery("SELECT OBJECT(i) FROM Item i").getResultList();
   em.close();
   return items;
 }

}

代码示例 2:将 Web 组件作为实体 Bean 和模型的虚包

代码示例 2 中,需要注意的一点是,我们选择让虚包来实现 ServletContextListener 接口。这样,它便可使用标注,这在访问 Java EE 平台的 Java 持久性实体和相关服务时非常有用。现在,让我们看一下,Web 组件(如 Servlet)是如何查找并使用此虚包的。例如,在 addItem 方法中,需要使用用于设定事务界限的 JTA API 来访问实体。另外,还需要使用实体管理器 API 在数据库中存储新条目。将此代码统一放在虚包类中,比在每次需要实体访问时到处在应用程序中剪切和粘贴类似代码要好得多。
entity access is required.

public class CatalogServlet extends HttpServlet {
  private ServletContext context;
  private CatalogFacade cf; 

  public void init(ServletConfig config)throws ServletException {              
context = config.getServletContext();
cf = (CatalogFacade)context.getAttribute("CatalogFacade");
initPathMapping();
  }

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {     
  ...       
try {           
if (selectedURL.equals("additem.do")) {          
    //get values from request and set in a
//new Item to then add to database              
String desc = request.getParameter("item_desc");              
String name = request.getParameter("item_name");              
...               
Item item = new Item();               
item.setName(name);            
item.setDescription(desc);              
  ...
cf.addItem(item);  //now call facade             
  ...           
}
}
代码示例 3:使用模型虚包访问 Java 持久性实体的 Web 组件

在代码示例 3 中,需要注意的一点是,虚包是由 Servlet 的 init 方法获取的,然后作为字段存储在 Servlet 中,以便由其他客户端请求使用和共享。在处理请求时,Servlet 可以使用虚包,而不用理会实体细节和访问实体所需的任何持久性工作。

策略 2:使用会话 Bean 虚包

另一种实现虚包的策略是使用会话 Bean 作为虚包。在此设计中,您需要使用 EJB 容器,因为它提供了会话 Bean 支持。此策略的一个优点是,会话 Bean 可以利用容器服务,如容器管理的事务。因此,代码不需要包含通过 JTA API 管理事务的任何额外代码。

@Stateless
public class CatalogFacadeBean implements CatalogFacade {  

  @PersistenceContext(unitName="CatalogPu")
  private EntityManager em;

  public void addItem(Item item) throws InvalidItemException {
    if(item.getName().length() == 0)
      throw new InvalidItemException("The item" +
        " name cannot be empty. Please specify a name for the item. ");
    em.persist(item);
  }

  public Item getItem(int itemID) {
    Item item =  em.find(Item.class,itemID);
    return item;
  }

  public List<Item> getAllItems() {
    List<Item> items = em.createQuery("SELECT OBJECT(i) FROM Item i").getResultList();
    return items;
  }
}

代码示例 4:作为实体和模型层虚包的会话 Bean

请注意,代码示例 4 中没有用于设置事务界限的代码。在这种情况下,我们决定利用 EJB 容器的容器管理的事务服务。所需的事务行为可以通过标注(即我们此处使用的缺省设置)指定,或者也可以选择在部署描述符中进行指定。另外,在本示例中,我们决定使用容器管理的实体管理器,以使代码不需要包含任何实体管理器 API 调用,而是由 EJB 容器完成相应的操作。如果选择使用应用程序管理的实体管理器和事务,则仍需要添加代码以便以编程方式管理这些服务。

public class CatalogServlet extends HttpServlet {
  private Map nameSpace;
  private ServletContext context;
  @EJB(name="CatalogFacadeBean") private CatalogFacade cf;  

  public void init(ServletConfig config) throws ServletException {
    context = config.getServletContext();       
    initPathMapping();
  }  

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ...       
    try {
    if (selectedURL.equals("additem.do")) {
      //get values from request and place them into new Item
      String desc = request.getParameter("item_desc");
      String name = request.getParameter("item_name");
      ...
      Item item = new Item();
      item.setName(name);
      item.setDescription(desc);
      ...
      //use facade to add new Item to database
      cf.addItem(item);
      ...         
    }
    ...
  }

代码示例 5:使用会话 Bean 虚包访问 Java 持久性实体的 Web 组件

代码示例 5 中,Servlet 使用依赖关系注入(@EJB(name="CatalogFacadeBean"))来查找和获取虚包。还要注意,虚包是作为字段进行存储的,以便供其他客户端请求共享和使用。使用虚包可隐藏模型层访问的细节。

参考资料

以下是一些值得参考的资料:

© Sun Microsystems 2006。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。