WEB开发网
开发学院软件开发Java Spring中的持久性 阅读

Spring中的持久性

 2007-12-23 12:31:41 来源:WEB开发网   
核心提示:编者按:简单而有技巧地解决问题总是比蛮力解决要好,这就是最近出版的Better, Faster, Lighter java一书中所遵循的原则,Spring中的持久性,从这本书的第八章中节选出来的这个关于SPRing的两部分的系列文章,也体现了书的作者BrUCe Tate和Justin Gehtland所信奉的这个原则,

编者按:简单而有技巧地解决问题总是比蛮力解决要好。这就是最近出版的Better, Faster, Lighter java一书中所遵循的原则。从这本书的第八章中节选出来的这个关于SPRing的两部分的系列文章,也体现了书的作者BrUCe Tate和Justin Gehtland所信奉的这个原则。本周Bruce和Justin将继续第一部分,向Pet Store示例添加持久性,并探讨Spring框架中的持久性逻辑方面。

添加持久性
  CartItem对象不一定非要是持久的。另一方面,您希望从数据库中获取产品和类别。J2EE应用程序开发人员一直在寻求一种成熟的持久性方法而没有成功。好的持久性框架应该支持透明,并且不侵入域模型。Spring允许您将透明对象与数据访问层分离开来。然后Spring使您可以很容易地在持久性之上建层。可以使用JDBC抽象层,它可以分离掉JDBC中许多冗长乏味、易于出错的方面,比如连接管理和错误处理。Spring JDBC层使用一个称为回调模板 的特性来将控制权由应用程序交给Spring框架。借助于该策略,Spring不再需要管理连接、结果集和特定于RDBMS的错误。当想要使用JDBC直接处理关系查询时,该框架就非常有用了。

通常,我们都宁愿处理对象而不愿处理关系。Spring也有一个用于透明持久性的模型。jPetStore使用Spring的OR映射层,该层提供了许多预包装的选项。现在Spring支持针对基本JDBC DAO、Hibernate和JDO的映射层。这个例子使用一个称为iBATIS SQL Maps的DAO框架来实现Spring DAO层。

模型
  每个Spring解决方案都从一个透明域模型开始。例8-3从透明模型对象(一个产品)开始。

例8-3. Product.java

public class Product implements Serializable {

  private String productId;
  private String categoryId;
  private String name;
  private String description;

  public String getProductId( ) { return productId; }
  public void setProductId(String productId) { this.productId = productId.trim( ); }

  public String getCategoryId( ) { return categoryId; }
  public void setCategoryId(String categoryId) { this.categoryId = categoryId; }

  public String getName( ) { return name; }
  public void setName(String name) { this.name = name; }

  public String getDescription( ) { return description; }
  public void setDescription(String description) { this.description = description; }

  public String toString( ) {
    return getName( );
  }

}

这里没什么特别的。它完全由属性组成,通过getter、setter以及一个实用方法toString进行访问。如果您看一下jPetStore应用程序,就会发现域中其他每个持久性对象都有类似的类:Account、Order、Category、Item和LineItem。

映射
  至于Hibernate,iBATIS SQL Maps框架有一个映射文件。在该文件中,Java bean中的每个持久性属性都映射到一个数据库列。使用SQL Maps,可以在该映射文件中创建所有的SQL,将所有的SQL与xml映射文件隔离开来。例8-4显示了Product的XML映射支持。

例8-4. Product.xml

[1] 
 
[2]  
     

[3]  
       
       
       
       
     

[4]   
       select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where PRODUCTID = #value#
     

[5]   
       select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT where CATEGORY = #value#
     

[6]   
       select PRODUCTID, NAME, DESCN, CATEGORY from PRODUCT
       
          
            lower(name) like #keyWordList[]# OR lower(category) 
like #keywordList[]# OR lower(descn) like #keywordList[]#
          
       
     

    


  下面是注释的含义:

[1]每个映射文件对应于一个域对象。本例中的域对象关系到为该DAO所指定的查询的返回类型。

[2]关于DAO层的其他信息,比如缓存策略,也属于映射文件。这里,iBatis将缓存维持24小时,然后刷新。

[3]当然了,每个查询都返回一个产品。该映射将结果集中的每一列与产品中的一个字段联系起来。

[4]该SQL语句找出给定productID的产品。

[5]该SQL语句找出一个类别中的所有产品。它返回一个产品列表。

[6]该SQL语句是动态的。IBatis迭代关键字列表,形成一个动态查询。

到目前为止,您已经看到了Product及其映射的域模型,包括查询。您已经上路了。

DAO接口
  应用程序必须与Spring和SQL Maps都进行集成。应用程序通过一个DAO接口和一个具体实现将二者联系起来。例8-5就是该接口。

例8-5. ProductDAO.java

public interface ProductDao {

  List getProductListByCategory(String categoryId) throws DataaccessException;

  List searchProductList(String keywords) throws DataAccessException;

  Product getProduct(String productId) throws DataAccessException;

}

这非常简单。可以看到,定义在映射中的每个查询都有一个接口。具体来说,getProduct接口通过ID查找产品,getProductListByCategory接口返回一个类别中的所有产品,还有一个基于关键字的动态查询接口。现在,DAO抛出Spring异常,任何使用DAO的逻辑都有一致的异常,即使您之后决定更改实现。

DAO实现
  余下的就是要使用SQL Map来实现接口。例8-6是Product的SQL Map实现。

例8-6. SqlMapProductDao.java

    public class SqlMapProductDao extends SqlMapDaoSupport implements ProductDao {

[1]  public List getProductListByCategory(String categoryId) throws DataAccessException {
       return getSqlMapTemplate( ).executeQueryForList("getProductListByCategory", 
     }

[1]  public Product getProduct(String productId) throws DataAccessException {
       return (Product) getSqlMapTemplate( ).executeQueryForObject("getProduct", productId);
     }

[1]  public List searchProductList(String keywords) throws DataAccessException {
       Object parameterObject = new ProductSearch(keywords);
       return getSqlMapTemplate( ).executeQueryForList("searchProductList", parameterObject);
     }

     /* Inner Classes */

[2]  public static class ProductSearch {

        private List keywordList = new ArrayList( );

        public ProductSearch(String keywords) {
          StringTokenizer splitter = new StringTokenizer(keywords, " ", false);
          while (splitter.hasMoreTokens( )) {
            this.keywordList.add("%" + splitter.nextToken( ) + "%");
          }
        }

        public List getKeywordList( ) {
          return keywordList;
        }
     }

    }

下面是注释的含义:

[1]这些方法提供了接口的SQL Map实现。其他的实现可能使用Hibernate、JDO或straight JDBC。在本例中,getTemplate调用指示Spring获取支持iBATIS SQL Map的模板,并使用该框架执行适当的查询。

[2]我并非特别喜欢内部类,但是这是通常实现关键字查询的方法。在本例中,内部类通过实现getKeywordList来支持searchProductList方法。使用余下的DAO实现,内部类有助于组织代码基址,将所有的支持都保存在一个地方。

现在我们看到了映射、模型和DAO。有了一个完全持久性的模型。接下来,我们要使用代码访问DAO层。jPetStore通过一个外观层来接受所有的DAO访问。

通过外观使用模型
  正如第3章一样,通常对模型使用一个高级接口非常有用,它称为外观。在本例中,jPetStore外观有三个作用:
  • 合并数据访问层所有的客户端;
  • 为其他的应用程序提供一个公共用户界面;
  • 用作与其他服务(比如事务支持)的连接点。


  在本例中,外观是一个非常薄的层,它环绕所有的DAO。通过配置和方法拦截器,Spring使外观具有声明式事务支持。在本例中,外观存在于两个部分中:接口和实现。接口允许不影响其他代码地更改外观的实现。例8-7显示了接口。

例8-7. PetStoreFacade.java

public interface PetStoreFacade {

  Account getAccount(String username);
  Account getAccount(String username, String password);
  void insertAccount(Account account);
  void updateAccount(Account account);

  List getUsernameList( );

  List getCategoryList( );
  Category getCategory(String categoryId);

  List getProductListByCategory(String categoryId);
  List searchProductList(String keywords);
  Product getProduct(String productId);

  List getItemListByProduct(String productId);
  Item getItem(String itemId);
  boolean isItemInStock(String itemId);

  void insertOrder(Order order);
  Order getOrder(int orderId);
  List getOrdersByUsername(String username);

}

可以将该接口视为所有创建、读、更新或删除任何Pet Store对象的方法的统一列表。注意,您不能看到来自所有DAO的方法,而只能看到我们希望公开给外界的方法。还要注意接口中的命名一致性。这很重要,因为,在我们的配置文件中,您可以看到用来传播以get、search、update或insert开头的方法的事务支持。

实现调用底层的DAO来执行适当的操作。必须在接口中实现所有的方法。例8-8是与ProductDAO相关的方法的实现。

例8-8. Excerpt fromPetStoreImpl.java

[1] private ProductDao productDao;

  ...

    public void setProductDao(ProductDao productDao) {
       this.productDao = productDao;
	}

  ...

[2] public List getProductListByCategory(String categoryId) {
       return this.productDao.getProductListByCategory(categoryId);
    }
	
    public List searchProductList(String keywords) {
       return this.productDao.searchProductList(keywords);
    }
  ...

下面是注释的含义:

[1]显示DAO访问(包括粗体文本)。Spring框架使用反射将DAO插入到外观中。这意味着外观必须支持一个set方法和一个私有成员变量。

[2]提供数据访问的方法使用底层的DAO来做实际工作(包括对粗体文本的处理)。

当然了,我没有给出接口的所有方法的实现。只显示了与产品相关的方法。它们分为两部分。

首先,应用程序上下文将每个DAO连接到外观。Spring使用反射和bean工厂来创建产品DAO,并使用setProductDAO API来对其进行设置。为此,外观需要一个变量来保存DAO以及一个set方法来通过反射访问它。

其次,实现非常简单。外观只将请求传递给下面的模型层。但是,最终的实现要强大得多。外观用作一个支持声明式事务的EJB会话bean。通过配置,POJO变成了一个声明式事务协调程序!它也是整个数据库层的中心控制点。余下的工作就是配置DAO层了。

配置DAO层
  刚才我们只看到了模型的配置。例8-9则显示了一个使用简单事务管理的数据库的数据层配置。正如您所预料的,您将看到JDBC驱动程序的配置和所有DAO bean的声明。

例8-9. dataAccessContext-local.xml

   

[1] 
       /WEB-INF/jdbc.properties
    

[2] 
       
       
       
       
    

[3] 
       
    

[4] 
       
       classpath:/sql-map-config.xml
    

[5] 
       
	   
	
	 
	
	   
	   
	
	 
	
	   
	   
	
	 
	
	   
	   
	
	 
	
	   
	   
	   
    
	
	
	   
	   
	

   


  下面是注释的含义:

[1]这个bean处理JDBC配置。JDBC配置属性在一个标准的JDBC配置文件中,这使得它们的维护和读取更为容易。Spring提供了一个配置类,使得用户可以轻松地读取属性文件,而无需将其转换为XML。

[2]这里是数据源。它是一个标准的J2EE数据源。许多J2EE应用程序或框架都将应用程序或框架硬连接到一个给定的数据源上。而对其进行配置,就可以轻松地选择自己的数据源(以及池化策略)了。

[3]applicationContext.xml配置设置事务策略。该配置指定实现。该应用程序使用数据源事务管理器,它通过JDBC将事务管理委托给数据库(使用提交和回滚)。

[4]必须对构建DAO的iBATIS SQL Map实用工具进行配置。这里配置了。

[5]最后,我们看到了实际的DAO配置。不知道您是否记得,applicationContext.xml文件通过名称引用每个bean。

配置不止从模型或视图中解除了持久性层的耦合。还从持久性层解除事务管理的耦合,将事务策略与实现分离开来,并隔离数据源。下面我们来看除了配置之外的众多好处。

优点
  这就是Product的所有持久性代码。JPetStore其余的代码也都类似于此。应用程序有效地将整个域模型隔离到单独的一个层中。域对任何服务都没有依赖性,包括数据层。我们还将所有的数据访问都封装到一个整洁的DAO层中,该层独立于数据存储。要注意您没有看到的:

数据源配置
  由Spring框架处理。您不必管理一整套单元素集合(针对会话管理)、数据源等等。您还可以将重要决定(如:数据源类型)延迟到部署时。

连接处理
  Spring框架管理所有的连接处理。一个最常见的JDBC错误就是连接泄漏。如果不对连接的关闭非常小心,特别是在异常条件中,应用程序就很容易失去稳定而崩溃。

特化的异常
  许多框架将SQL异常传至顶层。它们通常都将可能特化到您自己的RDBMS中的代码内置,这使得难以编码可移植的应用程序。Spring有它自己的异常层次结构,从而使您远离这些问题。此外,如果换为使用Hibernate或JDO方法,不需要对异常处理做任何更改。

到现在为止,我们有了一个整洁、透明的域模型和一个独立于数据库的、不怎么需要维护的服务层。每个层都巧妙地封装起来了。看完了后台逻辑,现在我们来为应用程序添加一个用户界面。

呈现
  大多数情况下,Spring框架并不重新改造现行的技术。但是,在呈现逻辑方面,Spring引入了一个称为MVC Web的简单的模型-视图-控制器框架,它包括许多竞争性的架构,比如Struts和Java Server Faces (JSF)。使用Spring不一定非得使用MVC Web。但是如果决定使用它,就会得到一些好处:
  • MVC Web不指定视图选择。其他框架可能会对您所喜欢的视图技术提供更好的支持,比如Velocity(专有的)和Struts (jsp)。例如,Struts通过请求属性公开模型。因此,要使用诸如Velocity之类不理解Servlet API的技术,就需要构建一个桥梁servlet。Spring则是通过一种通用的映射来公开模型,所以它可以与任何视图技术一起使用。
  • MVC Web提供Spring应用程序的所有方面的一致配置。它使用与其他框架相同的反向控制范例。
  • MVC Web使测试变得更轻松。因为无需再扩展一个类(比如Struts中的Action或ActionForm),所以可以很容易地模拟请求和响应。

如果曾经使用过Struts,您就应该很熟悉MVC Web的基本范例。图8-4显示了它的工作机制。控制器基本上处理了所有从输入视图传入的请求。如果输入请求是一个提交的表单,控制器就调用一个(由程序员创建和配置的)业务验证例程,并根据结果向用户发送相关的错误视图或者成功视图。

Spring中的持久性(图一)
图8-4. MVC Web框架的工作机制类似于Struts

配置
  就像对Spring框架的其他元素一样,要了解一个新的应用程序,要从配置文件开始深入研究。在本例中,用户界面是在petstore-servlet.xml文件中进行配置的。

考虑搜索一个类别中的产品的Html页面,以及根据关键字搜索产品的HTML页面。配置文件需要两个应用程序上下文文件的控制器。每个记录项指定一个控制器以及模型对象,如例8-10所示。

例8-10. web.xml节选

  
     
  

  
     
  


  还记得吧,所有对数据层的访问都是通过外观进行的。是的,这些bean ID记录项指定了外观,称为petstore。应用程序中的每个表单都以同样的方式工作。我们进一步来看看searchProducts的控制器。

控制器
  对于MVC Web,每个表单共享一个控制器实例,它发送所有与给定表单相关的请求。它还将表单封送到正确的验证逻辑,并向用户返回适当的视图。例8-11显示了searchProducts视图的控制器。

例8-11. SearchProductsController.java

   public class SearchProductsController implements Controller {

[1] private PetStoreFacade petStore;

    public void setPetStore(PetStoreFacade petStore) {
       this.petStore = petStore;
    }

[2] public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response) 
      throws Exception {

[3]    if (request.getParameter("search") != null) {
          String keyword = request.getParameter("keyword");
          if (keyword == null  keyword.length( ) == 0) {
             return new ModelAndView("Error", 
                                 "message", 
      "Please enter a keyword to search for, then press the search button.");
          }
          else {
[4]          PagedListHolder productList = new PagedListHolder(
                     this.petStore.searchProductList(keyword.toLowerCase( )));
             productList.setPageSize(4);
             request.getsession( ).setAttribute(
                     "SearchProductsController_productList", productList);
[5]          return new ModelAndView("SearchProducts", "productList", productList);
          }
       }
       else {
[6]       String page = request.getParameter("page");  
          PagedListHolder productList = (PagedListHolder) request.getSession( ).
              getAttribute("SearchProductsController_productList");
          if ("next".equals(page)) {
             productList.nextPage( );
          }
          else if ("previous".equals(page)) {
             productList.previousPage( );
          }
          return new ModelAndView("SearchProducts", "productList", productList);
       }
      }
	  
   }

下面是注释的含义:

[1]每个控制器都有对适当的域模型的控制权。在本例中,视图自然是通过外观来访问模型的。

[2]控制器有一个类似于servlet但是实际上不是servlet的接口。因此用户请求通过一个调度servlet传入,该servlet将请求发送给适当的控制器,填充request成员。控制器只响应适当的请求,调用业务数据,并将控制权发送给适当的页面。

[3]在本例中,请求是“search”。控制器必须分析出适当的关键字。

[4]控制器使用用户所提供的关键字调用业务逻辑。

[5]控制器将适当的视图(以及适当的模型)发送给用户。

[6]在本例中,请求是“page”。我们的用户界面支持适用于同一个页面的多种产品。

表单
  像Struts一样,Spring可以将HTML表单映射为Java对象。例8-12是当一个Pet Store用户注册一个帐号时所返回的Java bean。

例8-12. AccountForm.java

public class AccountForm {

  private Account account;

  private boolean newAccount;

  private String repeatedPassword;

  public AccountForm(Account account) {
    this.account = account;
    this.newAccount = false;
  }

  public AccountForm( ) {
    this.account = new Account( );
    this.newAccount = true;
  }

  public Account getAccount( ) {
    return account;
  }

  public boolean isNewAccount( ) {
    return newAccount;
  }

  public void setRepeatedPassword(String repeatedPassword) {
    this.repeatedPassword = repeatedPassword;
  }

  public String getRepeatedPassword( ) {
    return repeatedPassword;
  }

}


  这些bean的每个字段都直接对应于一个HTML输入字段或控制权。Spring框架将一个提交请求转换为表单,然后可以将它当作POJO来访问以进行验证、映射输入数据或用于其他用途。与Struts不同的是,Spring表单对象可以是任何Java bean。没有必要扩展ActionForm。这非常重要,因为你不必从一个ActionForm复制属性到域对象或值对象。

验证
  您可能已经注意到了原始applciationContext.xml中的验证逻辑。这些bean通常都被视为业务逻辑,但是它们与用户界面关系密切,而且它们是由Spring框架直接调用的。当用户提交表单后,Spring就激活验证逻辑。Spring根据结果向适当的页面发送控制权。例8-13显示了验证帐号表单的AccountValidator类。

例8-13. AccountValidator.java

public class AccountValidator implements Validator {
   public boolean supports(Class clazz) {
      return Account.class.isAssignableFrom(clazz);
   }

   public void validate(Object obj, Errors errors) {
      ValidationUtils.rejectIfEmpty(errors, "firstName", "FIRST_NAME_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "lastName", "LAST_NAME_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "email", "EMAIL_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "phone", "PHONE_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "address1", "ADDRESS_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "city", "CITY_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "state", "STATE_REQUIRED", 
      ValidationUtils.rejectIfEmpty(errors, "zip", "ZIP_REQUIRED", "ZIP is required.");
      ValidationUtils.rejectIfEmpty(errors, "country", "COUNTRY_REQUIRED", 
   }
}

在本例中,Spring框架从几个方面使开发人员的生活变得更为轻松。开发人员不需要手动编写验证方法。有许多预包装的方法。框架负责验证和发送。框架负责根据成功或失败的结果发送控制权。

仅仅一章还不足以判断Spring框架,但是您已经看到了它的整体要点。该框架——更重要的是,这种编码方式——的优点应该显而易见了。特别要注意整洁的分层架构所提供的清晰性和简单性。您可以预料到,将业务逻辑与一个类似于Spring的透明框架相结合是多么轻松的事情。

小结
  我选择jPetStore应用程序是有很多原因的。最主要的原因是,可以很快就看到一个简单、快速、轻量级的应用程序与其他可选方案之间的区别。如果您还不是Spring的信徒,我建议您看一看Pet Store的EJB版本。如果您还没有看过,您会为二者的差别而感到震惊。Spring版本是透明而独立的,而EJB则是侵入式的,而且还依赖于容器。Spring版本可读性非常好,而J2EE版本则被EJB最佳实践的复杂性所淹没。

我并非一直是Spring的信徒。实际上,在Rod Johnson与我在波士顿的一次会议上被介绍认识之前,我根本不知道他是谁。后来我逐渐欣赏起这个简单、优雅而重要的框架。如果您对Spring还很陌生,那么您只能看到一个应用程序。我希望通过这个应用程序,您能够领会它如何体现本书中的这些原则:

尽可能地简单
  Spring的易用性和可读性。在仅仅一章之中,我们介绍了一个具有事务、持久性、一个完整的web前端以及一个完全的模块化配置引擎的应用程序。

只做一件事,但是要做好
  Spring的框架有许多不同的方面和子框架。但是,它将每个概念很好地分离开来。Spring最基本的意义在于它的bean工厂和配置服务,它们使得用户可以管理依赖性而无需耦合代码。Spring的每个附加层都是完全去耦合并且独立的。

争取透明
  Spring应用程序完全不需要依赖于基础容器。实际上,这些应用程序可以轻松自如地存在于容器外。用户只需手动创建和配置它们。这种能力使得Spring应用程序成为开发人员乐于测试的应用程序。

内容决定形式
  Spring利用它所包含的各种框架为用户提供了灵活的选择。针对数据源和登录的Apache项目为它奠定了良好的基础。Spring提供了多种可配置的选项,使用户可以针对给定的解决方案选择最佳的框架。

支持扩展
  Spring可能是现存的最为开放和可扩展的容器。它允许使用常见的配置服务和整洁的抽象快速有效地进行扩展。

此处对Spring的介绍并不全面。我的目标只是向您说明,利用本书前六章所介绍的概念是可以构建现实世界的应用程序的。如果想了解得更多,一定要研究一下Spring的高级特性:

  • 与Hibernate和JDO的集成
  • 面向方面编程(AOP)的概念
  • 包含JTA支持的事务模板

《Better, Faster, Lighter Java》一书的两位作者将在其他章节中继续探讨使用了本书所介绍的基本原则的实际例子。例如,一个称为Simple Spider的服务的实现,您还将看到该服务被集成到Spring中。之后您将会看到类似框架的更多优点。


  原文出处 Persistence in Spring: http://www.onjava.com/pub/a/onjava/excerpt/BFLJava_chap8/index1.html

Spring中的持久性(图二)  作者简介 Bruce A.ate是一名山地车手和皮划艇手,也是2个孩子的父亲。在闲暇时间里,他还是一名独立顾问。他住在德克萨斯州的首府奥斯汀。他编写了4本书,其中包括最畅销的《Bitter Java》一书,最近出版了《Better,Faster,Lighter Java》(O'Reilly)。 Alexander Ananiev是一个程序员、作者、导师和指导员,他专攻现实世界软件应用程序。

(出处:http://www.cncms.com)


Tags:Spring 持久性

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接