创建DAO 
本章将向你展示怎样创建用来表现Table的数据的Java对象和一个持久化数据的Java类.  
这部分将向你展示怎样创建一个新表, 然后怎样创建访问这个表的Java代码.  
我们将创建一个对象,和一些将这个对象持久化(添加、修改、删除)到数据库的类。根据Java的惯例, 我们称这个对象为Plain Old Java Object (POJO ). 这个对象主要代表数据库中的一个表。其他的类是:  
·   一个Data Access Object (DAO ), 一个Interface 和一个Hibernate的实现类。 
·   一个测试我们的DAO是否能够正常工作的JUnit 类  
注意: 如果你在使用MySQL,并且想使用transactions (which you probably will) 。 
你必须使用InnoDB表.要将如下内容添加到mysql配置文件中(my.cnf或者c:\Windows\my.ini) 
[mysqld] 
default-table-type=InnoDB  
AppFuse使用Hibernate 作为默认的数据持久化层。 Hibernate是一个对象/关系(O/R) 框架,它允许你将一个Java对象同一个表关联。 它可以使你的对象能够很容易的执行CRUD (Create, Retrieve, Update, Delete)。 
让我们开始创建一个Object, DAO和Test测试用例吧!  
·   [1] 创建一个新Object ,添加XDoclet tag 
·   [2] 使用Ant根据对象创建一个新表 
·   [3] 创建一个新的DAOTest用来运行Junit,测试你的DAO 
·   [4] 创建一个新的DAO去执行对象的CRUD 
·   [5] 为Person对象和PersonDAO配置Spring 
·   [6] 运行DAOTest 
创建一个新Object ,添加XDoclet tag  
我们要做的第一件事是创建一个要持久化的对象。我们将在src/dao/**/model目录中创建一个简单的"Person"对象,它拥有id、firstName和lastName (属性)。(ltf:要确保对象的id属性的getter是当前类的第一个方法,否则生成的JSP的页面可能会有一个脚本错误)  
| 
 package org.appfuse.model;
  public class Person extends BaseObject {   private Long id;   private String firstName;   private String lastName;
      /*      Generate your getters and setters using your favorite IDE:       In Eclipse:      Right-click -> Source -> Generate Getters and Setters     */ }   |   
这个类应该继承BaseObject ,BaseObject 有3个抽象方法: (equals(), hashCode() 和 toString()) ,这3个方法必须在Person中实现。 头2个方法是Hibernate必需的。最容易的实现方法是使用Commonclipse ,它可以自动生成这些方法。 
现在我们已经创建完了这个POJO对象,我们需要添加Xdoclet标签以产生Hibernate映射文件。Hibernate用这个映射文件建立对象(属性) → 表(列)的映射。 
第一步, 我们添加一个@hibernate.class 标签,它告诉Hibernate这个对象关联的是什么表:  
| 
 /**  * @hibernate.class table="person"  */ public class Person extends BaseObject {   |   
我们还要加一个主键映射,否则Xdoclet在产生映射文件时将不能正确执行(puke)。  注意:所有的 @hibernate.* tag必须放在你的POJO的getter的Javadoc中。 
| 
 /**      * @return Returns the id.      * @hibernate.id column="id"      *  generator-class="increment" unsaved-value="null"      */     public Long getId() {         return this.id; 
}   |   
我使用generator-class="increment"代替generate-class="native",因为我发现使用"native"时候会有一些问题。如果你计划使用MySQL,我推荐你使用"native" 值。 这篇文章使用 increment。 
现在我们执行"ant setup-db"就可以创建person表了! 这个任务将创建Person.hbm.xml文件和一个名为"person"的表。从Ant控制台可以看Hibernate为你创建的表的schema:  
[schemaexport] create table person ( 
[schemaexport]    id bigint not null, 
[schemaexport]    primary key (id) 
[schemaexport] );  
如果你想看Hibernate为你产生的Person.hbm.xml,你可以从build/dao/gen/**/model目录中查找。下面是到目前为止Person.hbm.xml的内容:  
| 
 <?xml version="1.0"?>
  <!DOCTYPE hibernate-mapping PUBLIC     "-//Hibernate/Hibernate Mapping DTD 2.0//EN"      "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
  <hibernate-mapping>     <class         name="org.appfuse.model.Person"         table="person"         dynamic-update="false"         dynamic-insert="false"     >
          <id             name="id"             column="id"             type="java.lang.Long"             unsaved-value="null"         >             <generator class="increment">             </generator>         </id>
          <!--             To add non XDoclet property mappings, create a file named                 hibernate-properties-Person.xml             containing the additional properties and place it in your merge dir.         -->
      </class>
  </hibernate-mapping>   |   
现在为其他的列(first_name, last_name)添加 @hibernate.property 标签:  
| 
     /**      * @hibernate.property column="first_name" length="50"      */     public String getFirstName() {         return this.firstName;     }
      /**      * @hibernate.property column="last_name" length="50"      */     public String getLastName() {         return this.lastName;     }   |   
在这个例子中, 添加column属性的唯一原因是:表的列名和对象的属性名字不一致。如果他们一致,就不需要添加column属性。如果想了解XdocLet的Hibernate的其他属性,请参见 @hibernate.property 。  
再次运行"ant setup-db",使Hibernate生成新加的列。 
[schemaexport] create table person ( 
[schemaexport]    id bigint not null, 
[schemaexport]    first_name varchar(50), 
[schemaexport]    last_name varchar(50), 
[schemaexport]    primary key (id) 
[schemaexport] );  
如果你想更改列的长度, 更改@hibernate.property标记的length属性。如果你希望它是一个必填的字段(NOT NULL),那么你要添加not-null="true"。 
创建一个新的DAOTest用来运行Junit,测试你的DAO  
注意: 因为我在生成这篇文章的代码过程中遇到很多问题, 所以我想让你知道有这样一个工具: Lance Lavandowska 创建的 daogen ,它能被放入"extras"目录,用于产生这篇文章中提到的DAO和Manager。如果你喜欢它,你应该更新到1.6+.主要原因是:我不想处理"what did I just generate"?  
-通过这个文档,你应该了解到整个程序是怎样运行的。  
我们现在创建一个DAOTest去测试我们的DAO的工作。 "等一下,我们还没有创建DAO呀!",你说的没错,然而我发现Test-Driven Development (测试驱动开发)可以带来更高质量的软件。几年前,我认为写类之前先写测试类是猪食(不好的东西)。它看起来很愚蠢。然而当我试着做的时候,我发现它工作得实在太好了!我在这篇文章中填充所有的测试驱动的唯一原因是我发现使用测试驱动开发的方法可以带来更高的效率和更好的质量的软件。  
开始吧!我们在目录test/dao/**/dao中创建类PersonDAOTest.java。这个类应该继承BaseDAOTestCase (这个类在包中已经存在)。BaseDAOTestCase类用于装载 Spring's 的ApplicationContext (因为Spring和层绑定到一起了)和一个可选的.properties文件(和你的 *Test.class同名的ResourceBundle文件)。在这个例子中,如果你在PersonDAOTest.java所在的目录中放入了一个PersonDAOTest.properties文件,这个文件的属性将可以通过一个"rb"变量来访问。 
我通常复制 (open → save as)一个已经存在的测试文件(如:UserDAOTest.java),然后用[Pp]erson查找/覆盖[Uu]ser。  
| 
 package org.appfuse.dao;
  import org.appfuse.model.Person; import org.springframework.dao.DataAccessException;
  public class PersonDAOTest extends BaseDAOTestCase {          private Person person = null;     private PersonDAO dao = null;
      protected void setUp() throws Exception {         super.setUp();         dao = (PersonDAO) ctx.getBean("personDAO");     }
      protected void tearDown() throws Exception {         super.tearDown();         dao = null;     } }   |   
上面的代码是一个基本的初始化、销毁PersonDAO对象的Junit测试。"ctx" 对象是对Spring的ApplicationContext的一个引用,它在BaseDAOTestCase's 类的静态代码块中被初始化。  
现在我们要测试我们的DAO对象的CRUD (create, retrieve, update, delete)是否工作。为了达到这个目的,我们创建一个"test" (全部小写)开头的方法.。这些方法的作用域public,返回类型为void,并且没有参数。它们将被Ant使用的build.xml文件的<junit>任务调用。  
下面是一个简单的测试CRUD的测试。需要牢记的是:每个方法(也作为一个test)都要自己创建Person。在PersonDAOTest.java文件中添加如下方法:  
| 
     public void testGetPerson() throws Exception {         person = new Person();         person.setFirstName("Matt");         person.setLastName("Raible");
          dao.savePerson(person);         assertNotNull(person.getId());
          person = dao.getPerson(person.getId());         assertEquals(person.getFirstName(), "Matt");     }
      public void testSavePerson() throws Exception {         person = dao.getPerson(new Long(1));         person.setFirstName("Matt");
          person.setLastName("Last Name Updated");
          dao.savePerson(person);
          if (log.isDebugEnabled()) {             log.debug("updated Person: " + person);         }
          assertEquals(person.getLastName(), "Last Name Updated");     }
      public void testAddAndRemovePerson() throws Exception {         person = new Person();         person.setFirstName("Bill");         person.setLastName("Joy");
          dao.savePerson(person);
          assertEquals(person.getFirstName(), "Bill");         assertNotNull(person.getId());
          if (log.isDebugEnabled()) {             log.debug("removing person...");         }
          dao.removePerson(person.getId());
          try {             person = dao.getPerson(person.getId());             fail("Person found in database");         } catch (DataAccessException dae) {             log.debug("Expected exception: " + dae.getMessage());             assertNotNull(dae);         }     }   |   
在testGetPerson方法中,我们创建一个person对象,然后调用一个get。通常我会在数据库中输入一条我总会用到的测试记录。由于DBUnit 会在我们的测试用例执行之前装载数据,所以你可以简单的添加一个新表/记录到metadata/sql/sample-data.xml文件中:  
<table name='person'> 
    <column>id</column> 
    <column>first_name</column> 
    <column>last_name</column> 
    <row> 
      <value>1</value> 
      <value>Matt</value> 
      <value>Raible</value> 
    </row> 
</table>  
使用这种方法,你可以在testGetPerson方法中达到"create new"的目的。  如果你更喜欢通过SQL或GUI来直接向数据库中添加记录, 
那么你可以使用"ant db-export"重建(rebuild) sample-data.xm文件, 
然后执行"cp db-export.xml metadata/sql/sample-data.xml"。 
在上例中,在保存它之前先调用了person.set*(value)去组装(populate)对象。这个例子看起来很简单,但如果持久化对象有10个以上的必填(not-null="true")字段时,这个过程将会很令人厌烦[cumbersome]。这就是为什么我要在BaseDAOTestCase中创建ResourceBundle文件。在PersonDAOTest.java文件的目录中创建一个PersonDAOTest.properties文件,然后定义属性-值:  
我趋向于在Java代码中进行硬编码(hard-code) – 但是使用.properties文件是一个使大型对象工作得更好的备选方案。  
firstName=Matt 
lastName=Raible  
这样我们使用BaseDAOTestCase.populate(java.lang.Object)方法可以达到同样的目的,不用再调用person.set*方法去组装(populate)你的对象了:  
| 
 person = new Person(); person = (Person) populate(person);   |   
此时,PersonDAOTest仍然不能编译,因为我们的类路径中没有PersonDAO.class,现在我们来创建它。PersonDAO.java是一个接口,PersonDAOHibernate.java类是使用Hibernate对这个接口的实现。让我们前进,创建它们! 
我们在目录src/dao/**/dao中创建一个PersonDAO.java接口,然后指定实现这个接口的类必须要实现的CRUD方法。在下面这个类中我去掉了显示这个目的的JavaDoc注释。  
| 
 package org.appfuse.dao;
  import org.appfuse.model.Person;
  public interface PersonDAO extends DAO {     public Person getPerson(Long personId);     public void savePerson(Person person);     public void removePerson(Long personId); }   |   
请注意:在上面的类的方法声明(method signature)中,我没有抛出异常。这是因为Spring 提供了强大功能,它使用RuntimeException封装了异常。现在你可以使用"ant compile-dao"编译所有在目录src/dao and test/dao中的文件了。 
然而,如果你试着运行"ant test-dao -Dtestcase=PersonDAO",你将会看到一个错误: No bean named 'personDAO' is defined。这是一个Spring抛出的错误 – 提示我们应该在applicationContext-hibernate.xml中指定一个名为personDAO的Bean。我们这样做之前,我们要创建PersonDAO的实现类。 
运行DAO测试的Ant任务叫做"test-dao"。如果你传入testcase参数(使用 -Dtestcase=name), 参数的可选值是Person, PersonDAO或者PersonDAOTest,它将会查找**/*${testcase}*– 所有这些将执行PersonDAOTest类。  
现在,开始创建PersonDAOHibernate类,它实现了接口PersonDAO中指定的方法,然后使用 Hibernate获得/保存/删除Person对象。为了达到这个目的,我们要在目录src/dao/**/dao/hibernate中创建一个新类PersonDAOHibernate.java。它应该继承BaseDAOHibernate 并且实现接口PersonDAO。  brevity[简短的] 
| 
 package org.appfuse.dao.hibernate;
  import org.appfuse.model.Person; import org.appfuse.dao.PersonDAO; import org.springframework.orm.ObjectRetrievalFailureException;
  public class PersonDAOHibernate extends BaseDAOHibernate implements PersonDAO {
      public Person getPerson(Long id) {         Person person = (Person) getHibernateTemplate().get(Person.class, id);
          if (person == null) {             throw new ObjectRetrievalFailureException(Person.class, id);            }
          return person;     }
      public void savePerson(Person person) {         getHibernateTemplate().saveOrUpdate(person);     }
      public void removePerson(Long id) {         // object must be loaded before it can be deleted         getHibernateTemplate().delete(getPerson(id));     } }   |   
现在,如果你试着运行"ant test-dao -Dtestcase=PersonDAO",你仍会得到同样的错误。我们需要配置Spring,让它知道PersonDAOHibernate是接口PersonDAO的实现类,,我们也要告诉它关于Person对象的一些信息。 
为Person对象和PersonDAO配置Spring  
首先,我们要告诉Spring:需要的Hibernate映射文件在哪里。打开src/dao/**/dao/hibernate/applicationContext-hibernate.xml ,添加Person.hbm.xml到下面的代码块中: 
| 
 <property name="mappingResources">      <list>          <value>org/appfuse/model/Person.hbm.xml</value>          <value>org/appfuse/model/Role.hbm.xml</value>          <value>org/appfuse/model/User.hbm.xml</value>          <value>org/appfuse/model/UserCookie.hbm.xml</value>       </list>  </property>    |   
为了绑定PersonDAOHibernate到PersonDAO,我们在这个文件底部添加一些XML代码:  
  
<!-- PersonDAO: Hibernate implementation -->  <bean id="personDAO" class="org.appfuse.dao.hibernate.PersonDAOHibernate">      <property name="sessionFactory"><ref local="sessionFactory"/></property>  </bean>    |   
你也可以在<bean>部分添加autowire="byName",然后得到"sessionFactory"属性的rid。我个人喜欢依赖我的objects documented (in XML).  
保存所有你已经修改过的文件,然后试着运行 "ant test-dao -Dtestcase=PersonDAO" 一次或多次.  
Yeah Baby, Yeah:  
BUILD SUCCESSFUL Total time: 9 seconds  
 
 下一步: Part II: 创建Manager – 讲述怎样创建Business Facades. 这些façade用于连接前端和DAO层之间的通讯。  
 
  |