Appfuse开发实践(二)——创建DAO对象
第一部分: 在Appfuse中创建新的DAO对象 - 本部分描述如何创建一个POJO并且创建一个Java类把这个对象存储到数据库中实现持久化。
我们将使用Hibernate作为示例,如果需要使用其他的持久化方案,可以参考Appfuse的相关文档。
内容提要
第一部分: 在Appfuse中创建新的DAO对象 - 本部分描述如何创建一个POJO并且创建一个Java类把这个对象存储到数据库中实现持久化。
我们将使用Hibernate作为示例,如果需要使用其他的持久化方案,可以参考Appfuse的相关文档。
内容提要
[1] 创建一个新的POJO对象并且加入xdoclet标签
[2] Create a new database table from the object using Ant
[3] Create a new DAOTest to run JUnit tests on the DAO
[4] Create a new DAO to perform CRUD on the object
[5] Configure Spring for the Person object and PersonDAO
[6] Run the DAOTest
1、创建一个新的POJO对象并且加入xdoclet标签
首先要创建一个需要持久化的对象。我们将创建一个简单的Person对象(包括id,firstName,lastName三个属性)把这个对象放在src/dao/**/model目录中
public class Person extends BaseObject {
private Long id;
private String firstName;
private String lastName;
/**//*产生三个属性对应的setter/getter方法
产 */
}
这个类必须继承基类org.appfuse.model.BaseObject,必须实现三个抽象方法equals(), hashCode() and toString().前两个方法是Hibernate需要的.比较容易的方法是使用Commonclipse 插件自动产生着几个方法的实现.
使用IntelliJ IDEA可以产生generate equals() 和 hashCode()方法, 不过没有toString()方法. 可以使用 ToStringPlugin 插件。
现在创建了POJO对象,接下来需要加入XDoclet标签以便产生Hibernate映射文件。这个映射文件描述了对象→表和属性 →字段的映射关系。
用@hibernate.class标签说明表和对象的对应关系:
* @hibernate.class table="person"
*/
public class Person extends BaseObject {
We also have to add a primary key mapping or XDoclet will puke when generating the mapping file. Note that all @hibernate.* tags should be placed in the getters' Javadocs of your POJOs.
/**//**
* @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" 因为作者在使用某些数据库时发现了一些问题。如果仅仅使用MySQL,建议使用"native" value。
2、使用Ant创建数据库表
这是,可以运行"ant setup-db"任务来创建Person表,这个任务将创建Person.hbm.xml 文件并且在数据库中创建"person."表。 在控制台上,你可以看见Hibernate创建的table schema:
[schemaexport] create table person (
[schemaexport] id bigint not null,
[schemaexport] primary key (id)
[schemaexport] );
如果你Hibernate产生的Person.hbm.xml的文件,可以在build/dao/gen/**/hibernate 目录下找到它,这是这个文件的具体内容 :
<! 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.myApp.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 >
现在加入@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属性是因为创建的数据表的字段名和对应的类中的属性名不一样,如果是一样的,不需要加入这个属性说明。
运行 "ant setup-db"创建数据表.
[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 标签对应的属性值。如果希望他是一个必须的字段(NOT NULL),,加上not-null="true"这样的属性。
3、创建一个DAOTest对象并且用JUnit测试你的DAO对象
现在我们创建一个DAOTest类来测试我们的DAO的工作状况。 “等一下”你说,“我们还没有创建一个DAO对象”你是对的。然而我们发现测试驱动开发(Test-Driven Development)是开发高质量软件的有效手段。在过去的几年里,我认为先编写测试再编写实现是无稽之谈。现在看起来我错了。现在我发现这是一个很伟大的方法。我现在推崇测试驱动的开发方式是因为我发现使用这种方法可以极大地加速软件开发过程。
首先,在test/dao/**/dao目录下创建一个PersonDAOTest.java对象。这个对象必须继承 BaseDAOTestCase对象。这个父对象用来加载一个Spring的.properties文件 (资源邦定文件),此文件的文件名
和*Test.class一样。在这个例子里面如果你把PersonDAOTest.properties、PersonDAOTest.java放在一个目录下面, 这个文件的属性可以自动被赋值。
import org.myApp.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;
}
}
现在我们需要编写代码测试DAO对象中的CRUD (create, retrieve, update, delete)方法。我们创建的这个方法必须以"test" (all 全部小写字母)开头。只要这些方法是public类型的,它必须是一个void类型返回值,并且不带任何参数,这些测试方法将会被定义在Ant build.xml的<junit>任务自动调用。这是一个对CRUD方法的简单测试方法。一个重要的需要记住的事情是每个方法应该是独立的。在这个例子里我们在PersonDAOTest.java文件中加入下面的代码:
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方法。实际上我们这样做必须保证在数据库中有一条对应的纪录。因为采用了DBUni可以以我们需要的测试数据自动填充数据库,你只需要在metadata/sql/sample-data.xml文件中简单的说明需要增加的纪录详细信息即可:
<column > id </ column >
< column > first_name </ column >
< column > last_name </ column >
< row >
< value > 1 </ value >
< value > Matt </ value >
< value > Raible </ value >
</ row >
</ table >
person = new Person();
person = (Person) populate(person);
此时,PersonDAOTest类无法通过编译,因为我们没有创建 PersonDAO.class,我们要创建它。 PersonDAO.java是一个接口,PersonDAOHibernate.java是一个基于这个接口的Hibernate持久化实现。下面我们将继续创建这些对象。
4、创建一个DAO对象执行CRUD操作
首先在src/dao/**/dao目录中创建一个PersonDAO.java接口,并且说明实现类必须实现的CRUD方法。为了显示方便这里去掉了JavaDoc信息。
package org.myApp.dao;
import org.myApp.model.Person;
public interface PersonDAO extends DAO {
public Person getPerson(Long personId);
public void savePerson(Person person);
public void removePerson(Long personId);
}
你可能注意到上面的类的方法说明上没有exception处理。这是因为Spring的强大例外处理功能能把Exception封装成为RuntimeExceptions。此时,您可以运行"ant compile-dao"来编译源文件。然而如果你运行"ant test-dao -Dtestcase=PersonDAO",你将看到一个错误提示:No bean named 'personDAO' is defined。这是一个Spring产生的错误信息 - 我们需要给applicationContext-hibernate.xml指定一个具体的java Bean 到personDAO属性上。在此之前我们首先要创建一个PersonDAO的实现类。
执行测试任务的ant命令是"test-dao"。如果传递特定的参数(用 -Dtestcase=name), 他将会搜索 **/*${testcase}*目录 - 允许我们执行PersonDAOTest测试。
接下来我们创建PersonDAOHibernate类实现PersonDAO接口使用Hibernate来实现Person对象的get/save/delete操作。为了实现这个目标在src/dao/**/dao/hibernate 目录下创建PersonDAOHibernate.java.
他需要继承 BaseDAOHibernate 对象并且实现PersonDAO接口。
import org.myApp.model.Person;
import org.myApp.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对象的信息。
5、在Spring配置Person和PersonDAO对象
首先要告诉Spring Hibernate mapping映射文件的位置。打开src/dao/**/dao/hibernate/applicationContext-hibernate.xml并且加入Person.hbm.xml信息。
< list >
< value > org/myApp/model/Person.hbm.xml </ value >
< value > org/myApp/model/Role.hbm.xml </ value >
< value > org/myApp/model/User.hbm.xml </ value >
< value > org/myApp/model/UserCookie.hbm.xml </ value >
</ list >
</ property >
现在我们需要增加一些XML标记绑定PersonDAOHibernate到PersonDAO。在文件的结尾处加入下面的内容:
< bean id ="personDAO" class ="org.myApp.dao.hibernate.PersonDAOHibernate" >
< property name ="sessionFactory" >< ref local ="sessionFactory" /></ property >
</ bean >
你也可以使用autowire="byName" 这样不用对"sessionFactory"属性赋值。个人习惯,我喜欢在xml文档说明对应关系。
6、执行DAOTest
保存文件再一次运行 "ant test-dao -Dtestcase=PersonDAO"。
OK,成功了^_^
BUILD SUCCESSFUL
Total time: 9 seconds