Dashboard > AppFuseCN > Appfuse中文化 > Using iBATIS
AppFuseCN Log In   View a printable version of the current page.
Using iBATIS
Added by meetrice, last edited by meetrice on Jun 07, 2007  (view change)
Labels: 
(None)

About this Tutorial关于这篇教程

This tutorial will show you two things:
这篇教程讲了两个部分:

  1. You don't need to write DAOs if you just need generic CRUD functionality.你不需要写DAO,如果你只是需要一般性CRUD的功能.
  2. How to write DAOs when you need custom functionality.当需要自定义功能的时候,如何写DAO.
    If you're new to iBATIS, you might want to read the iBATIS Reference Guide before starting this tutorial. 如果你是刚接触iBATIS,你可能首先要读一下<iBATIS参考指南>.

    This tutorial has only been tested with AppFuse 2.0 M5 and above. 这篇教程仅仅在AppFuse 2.0 M5 及以上版本测试过.

Table of Contents 目录

  1. Setup your project to use iBATIS 创建使用iBATIS的项目
  2. Register a personDao bean definition 注册一个personDao的bean定义
  3. Create a DAO Test to test finder functionality 创建一个DAO测试用例,来测试finder的功能.
  4. Create a DAO Interface and implementation 创建一个DAO接口和实现
  5. Run the DAO Test 运行DAO的测试用例

Setup your project to use iBATIS 创建使用iBATIS的项目

To change a newly-created project to use iBATIS instead of Hibernate (the default), you'll need to perform the following steps:要把一个新建的工程转成使用iBATIS而不是Hibernate(默认),你需要按下面的步骤来:

  1. Change the <dao.framework> property in your pom.xml to be ibatis instead of hibernate.改变pom.xml里的<dao.framework>属性为ibatis来代替hibernate
  2. The *SQL.xml mapping files are currently MySQL-only, so if you want to use them with another database, you'll want to download them into your project and put them in a src/main/resources/sqlmaps directory. You can right-click, save as the following links: LookupSQL.xml, RoleSQL.xml and UserSQL.xml.*SQL.xml映射文件现在仅支持MySQL,所以,如果你想使用别的数据库,你要把那些映射文件下载到你的工程里,放在src/main/resources/sqlmaps 目录下,你可以点右键,将下面的几个链接另存为:LookupSQL.xml, RoleSQL.xml 和 UserSQL.xml
  3. At this point, if you use JPA annotations on your POJOs, you can use the Hibernate3 Maven Plugin to generate your schema. To do this, change <implementation>annotationconfiguration</implementation> to <implementation>jpaconfiguration</implementation> in your pom.xml. If you'd like more control of your schema, see the instructions below.现在,如果你在POJO里使用JPA,你可以使用Hibernate3 Maven 插件来生成数据库脚本,要做这个,请把pom.xml里的<implementation>annotationconfiguration</implementation> 改成 <implementation>jpaconfiguration</implementation>,如果你想了解对生成数据库脚本更多的控制,请看下面的介绍.
  4. Delete src/main/resources/hibernate.cfg.xml. If you're using the sql-maven-plugin (step #2 below), you can delete the src/main/resources/META-INF directory from your project as well. If you want to use the AppFuse Maven Plugin to generate code, you will need to keep hibernate.cfg.xml in your project (at least for the M5 release).删除src/main/resources/hibernate.cfg.xml,如果你用了 sql-maven-plugin (下面第二步),你也可以把 src/main/resources/META-INF 目录从你的工程中删除,如果你想使用AppFuse Maven Plugin 来生成代码,你需要在工程中保留hibernate.cfg.xml (至少在M5发行版中需要)

Using SQL Maven Plugin to Generate Schema 使用SQL Maven Plugin 生成数据库脚本

  1. Download the schema creation DDL and put it in src/test/resources: MySQL or PostgreSQL.下载创建数据库的DDL,放在 src/test/resources:MySQL 或 PostgreSQL
  2. Remove the hibernate3-maven-plugin from your pom.xml and replace it with the sql-maven-plugin: 在pom.xml中删除hibernate3-maven-plugin ,用sql-maven-plugin 替换.
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>sql-maven-plugin</artifactId>
        <version>1.0</version>
        <configuration>
            <driver>${jdbc.driverClassName}</driver>
            <username>${jdbc.username}</username>
            <password>${jdbc.password}</password>
            <url>${jdbc.url}</url>
            <autocommit>true</autocommit>
            <skip>${maven.test.skip}</skip>
        </configuration>
        <executions>
            <execution>
                <id>create-schema</id>
                <phase>process-test-resources</phase>
                <goals>
                    <goal>execute</goal>
                </goals>
                <configuration>
                    <autocommit>true</autocommit>
                    <srcFiles>
                        <srcFile>src/test/resources/${jdbc.groupId}-schema.sql</srcFile>
                    </srcFiles>
                </configuration>
            </execution>
        </executions>
        <dependencies>
            <dependency>
                <groupId>${jdbc.groupId}</groupId>
                <artifactId>${jdbc.artifactId}</artifactId>
                <version>${jdbc.version}</version>
            </dependency>
        </dependencies>
    </plugin>
    源代码

    The code for this tutorial is located in the "tutorial-ibatis" module of the appfuse-demos project on Google Code. Use the following command to check it out from Subversion:
    svn checkout http://appfuse-demos.googlecode.com/svn/trunk/tutorial-ibatis 这篇教程的源码在google code里叫"tutorial-ibatis"组件型appfuse示例工程,用下面的命令可以从Subversion里检出代码:svn checkout http://appfuse-demos.googlecode.com/svn/trunk/tutorial-ibatis

Register a personDao bean definition 注册一个personDao的bean定义

AppFuse 2.x doesn't require you to write a DAO to persist a POJO. You can use one of the pre-existing classes if all you need is CRUD on an object: AppFuse 2.x 不需要你写 DAO来持久化 POJO. 你可以使用一个已存在的类,如果你仅需要 CRUD的功能:

  • GenericDaoiBatis: A generics-based class that requires you to create a Spring bean definition.   GenericDaoiBatis:通用的基础类,需要你在Spring里创建一个BEAN定义.
  • UniversalDaoiBatis: A class that requires you to cast to the specific object type.UniversalDaoiBatis :这个类需要申明为一个特定的对象类型.
    The UniversalDaoiBatis class is already registered as a "dao" bean, so you can easily use it without any additional configuration. However, many developers prefer the generics-based DAO because it provides type safety. To register a personDao bean, create src/main/webapp/WEB-INF/applicationContext.xml (or core/src/main/resources/applicationContext.xml for a modular archetype) and add the following to it:  UniversalDaoiBatis 类已尼被注册为dao的bean,所以你可以很容易的使用他,不需要额外配置,不过,一些开发人员选择使用通用的基础DAO,因为他提供了类型安全,要注册personDao的bean,创建src/main/webapp/WEB-INF/applicationContext.xml ,添加以下内容:

    The applicationContext.xml file should already exist if you're using AppFuse 2.0 M5+. 如果你使用AppFuse 2.0 M5+, applicationContext.xml文件应该已经存在
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
    
        <bean id="personDao" class="org.appfuse.dao.ibatis.GenericDaoiBatis">
            <constructor-arg value="org.appfuse.tutorial.model.Person"/>
            <property name="dataSource" ref="dataSource"/>
            <property name="sqlMapClient" ref="sqlMapClient"/>
        </bean>
    </beans>

    You will also need to create a PersonSQL.xml file in src/main/resources/sqlmaps:你还需要创建PersonSQL.xml ,保存到 src/main/resources/sqlmaps:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN"
        "http://ibatis.apache.org/dtd/sql-map-2.dtd">
    
    <sqlMap namespace="PersonSQL">
        <typeAlias alias="person" type="org.appfuse.tutorial.model.Person"/>
    
        <parameterMap id="addParam" class="person">
            <parameter property="firstName" jdbcType="VARCHAR" javaType="java.lang.String"/>
            <parameter property="lastName" jdbcType="VARCHAR" javaType="java.lang.String"/>
        </parameterMap>
    
        <parameterMap id="updateParam" class="person">
            <parameter property="id" jdbcType="INTEGER" javaType="java.lang.Long"/>
            <parameter property="firstName" jdbcType="VARCHAR" javaType="java.lang.String"/>
            <parameter property="lastName" jdbcType="VARCHAR" javaType="java.lang.String"/>
        </parameterMap>
    
        <resultMap id="personResult" class="person">
            <result property="id" column="id"/>
            <result property="firstName" column="first_name"/>
            <result property="lastName" column="last_name"/>
        </resultMap>
    
        <select id="getPersons" resultMap="personResult">
        <![CDATA[
            select * from person
        ]]>
        </select>
    
        <select id="getPerson" resultMap="personResult">
        <![CDATA[
            select * from person where id = #value#
        ]]>
        </select>
    
        <insert id="addPerson" parameterMap="addParam">
            <![CDATA[
                insert into person (first_name,last_name) values ( ?,? )
            ]]>
            <selectKey resultClass="java.lang.Long" keyProperty="id" type="post">
                SELECT LAST_INSERT_ID() AS id
            </selectKey>
        </insert>
    
        <update id="updatePerson" parameterMap="updateParam">
        <![CDATA[
            update person set first_name = ?, last_name = ? where id = ?
        ]]>
        </update>
    
        <delete id="deletePerson">
        <![CDATA[
            delete from person where id = #value#
        ]]>
        </delete>
    </sqlMap>

    Next, add a reference to the new SQL Map in src/main/resources/sql-map-config.xml:下一步,在新的SQL映射文件(src/main/resources/sql-map-config.xml)里添加一个引用:

    <sqlMap resource="sqlmaps/PersonSQL.xml"/>

    If you're using the sql-maven-plugin, you'll also need to add a "create table" statement to src/test/resources/mysql-schema.sql:如果你使用sql-maven-plugin,你需要在 src/test/resources/mysql-schema.sql里添加"create table"的申明:

    drop table if exists person;
    
    CREATE TABLE person (
        id int(8) auto_increment,
        first_name varchar(50) NOT NULL,
        last_name varchar(50) NOT NULL,
        PRIMARY KEY (id)
    );

    After doing this, you can use this bean on an object by adding the following setter method:下面给这个类添加setter方法,然后你就可以使用这个bean了.

    public void setPersonDao(GenericDaopersonDao) {
    this.personDao = personDao;
    }

    If you need more than just CRUD functionality, you'll want to continue reading below. If not, you can continue to Creating new Managers. This is a tutorial for creating Business Facades, which are similar to Session Facades, but don't use EJBs. These facades are used to provide communication from the front-end to the DAO layer.

    Create a DAO Test to test finder functionality

Now you'll create a DaoTest to test that your DAO works. "Wait a minute," you say, "I haven't created a DAO!" You are correct. However, I've found that Test-Driven Development breeds higher quality software. For years, I thought write your test before your class was hogwash. It just seemed stupid. Then I tried it and I found that it works great. The only reason I do test-driven stuff now is because I've found it rapidly speeds up the process of software development.

To start, create a PersonDaoTest.java class in your src/test/java/*/dao directory (or core/src/test/java/*/dao directory for a modular archetype). This class should extend org.appfuse.dao.BaseDaoTestCase, a subclass of Spring's AbstractTransactionalDataSourceSpringContextTests. This parent class is used to load Spring's ApplicationContext (since Spring binds interfaces to implementations), and for (optionally) loading a .properties file that has the same name as your *Test.class. In this example, if you put a PersonDaoTest.properties file in src/test/resources/org/appfuse/tutorial/dao, this file's properties will be available via an "rb" variable.

package org.appfuse.tutorial.dao;

import java.util.List;

import org.appfuse.dao.BaseDaoTestCase;
import org.appfuse.tutorial.model.Person;
import org.springframework.dao.DataAccessException;

public class PersonDaoTest extends BaseDaoTestCase {
private PersonDao personDao = null;

public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}

The code you see above is what you need for a basic Spring integration test that initializes and configures an implementation of PersonDao. Spring will use autowiring by name to call the setPersonDao() method and set the "personDao" bean as a dependency of this class.

Now you need test that the finder method works in your DAO. To do this, create a method that begin with "test" (all lower case). As long as this method is public, has a void return type and take no arguments, it invoked and run by JUnit. Add the following method to your PersonDaoTest.java file:

public void testFindPersonByLastName() throws Exception {
    List<Person> people = personDao.findByLastName("Raible");
    assertTrue(people.size() > 0);
}

You'll notice that this method relies on pre-existing data in order to pass. The DbUnit Maven Plugin is used to populate the database with test data before the tests are run, so you can simply add the new table/record to the src/test/resources/sample-data.xml file (or core/src/test/resources/sample-data.xml for a modular archetype).

<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>

Since the PersonDao you're about to write includes CRUD functionality, you can also write a test to verify CRUD works properly.

public void testAddAndRemovePerson() throws Exception {
Person person = new Person();
person.setFirstName("Country");
person.setLastName("Bry");

personDao.save(person);

person = personDao.get(person.getId());

assertEquals("Country", person.getFirstName());
assertNotNull(person.getId());

log.debug("removing person...");

personDao.remove(person.getId());

try {
personDao.get(person.getId());
fail("Person found in database");
} catch (DataAccessException dae) {
log.debug("Expected exception: " + dae.getMessage());
assertNotNull(dae);
}
}

In the above example, you can see that person.set*(value) is being called to populate the Person object before saving it. This is easy in this example, but it could get quite cumbersome if you're persisting an object with 10 required fields. This is why a ResourceBundle exists in BaseDaoTestCase. Simply create a PersonDaoTest.properties file in the same directory as PersonDaoTest.java and define your property values inside it:

firstName=Matt
lastName=Raible

I tend to just hard-code test values into Java code - but the .properties file is an option that works great for large objects.

Then, rather than calling person.set* to populate your objects, you can use the BaseDaoTestCase.populate(java.lang.Object) method:

Person person = new Person();
person = (Person) populate(person);

At this point, the PersonDaoTest class won't compile yet because there is no PersonDao.class in your classpath, you need to create it.

Create a DAO Interface and implementation

Create a PersonDao.java interface in the src/main/java/*/dao (or core/src/main/java/*/dao for a modular archetype) directory and specify the finder method for any implementation classes.

package org.appfuse.tutorial.dao;

import org.appfuse.dao.GenericDao;
import org.appfuse.tutorial.model.Person;

import java.util.List;

public interface PersonDao extends GenericDao<Person, Long> {
public List<Person> findByLastName(String lastName);
}

Notice in the class above there is no exception on the method signature. This is due to the power of Spring and how it wraps Exceptions with RuntimeExceptions. At this point, you should be able to compile all your code using your IDE or mvn test-compile. However, if you try to run mvn test -Dtest=PersonDaoTest, you will get an error:

Running org.appfuse.tutorial.dao.PersonDaoTest
INFO - AbstractSingleSpringContextTests.loadContextLocations(179) | Loading context for: classpath*:/applicationContext-*.xml
Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.449 sec <<< FAILURE!

Unfortunately, this doesn't tell you much about what went wrong. To find the real problem, you need to open target/surefire-reports/org.appfuse.tutorial.dao.PersonDaoTest.txt (or core/target/surefire-reports/org.appfuse.tutorial.dao.PersonDaoTest.txt for a modular archetype). In this file, the real problem is shown:

\------------------------------------------------------------------------------\-
Test set: org.appfuse.tutorial.dao.PersonDaoTest
\------------------------------------------------------------------------------\-
Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.444 sec <<< FAILURE\!
testFindPersonByLastName(org.appfuse.tutorial.dao.PersonDaoTest) Time elapsed: 0.401 sec <<< ERROR\!
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean
with name 'org.appfuse.tutorial.dao.PersonDaoTest': Unsatisfied dependency expressed
through bean property 'personDao': Set this property value or disable dependency
checking for this bean.
Showing errors in your console

To show testing errors in your console, append -Dsurefire.useFile=false to your mvn test command.

This is an error message from Spring - indicating that you need to specify a bean named "personDao" in a Spring context file. Before you do that, you need to create the PersonDao implementation.

Create a PersonDaoiBatis class that implements the finder method in PersonDao. To do this, create a new class in src/main/java/*/dao/hibernate (or core/src/main/java/*/dao/hibernate for the modular archetype) and name it PersonDaoHibernate.java. It should extend GenericDaoiBatis and implement PersonDao. Javadocs eliminated for brevity.

package org.appfuse.tutorial.dao.ibatis;

import org.appfuse.tutorial.dao.PersonDao;
import org.appfuse.tutorial.model.Person;
import org.appfuse.dao.ibatis.GenericDaoiBatis;

import java.util.List;

public class PersonDaoiBatis extends GenericDaoiBatis<Person, Long> implements PersonDao {

public PersonDaoiBatis() {
super(Person.class);
}

@SuppressWarnings("unchecked")
public List<Person> findByLastName(String lastName) {
return (List<Person>) getSqlMapClientTemplate().queryForList("findByLastName", lastName);
}
}

You'll also need to add the findByLastName query to your PersonSQL.xml:

<select id="findByLastName" resultMap="personResult">
    select * from person where last_name = #value#
</select>
<select id="findByLastName" resultMap="personResult">
    select * from person where last_name = #value#
</select>

Now, if you try to run mvn test -Dtest=PersonDaoTest, you will get the same error. You need to configure Spring so it knows that PersonDaoiBatis is the implementation of PersonDao.

Create an applicationContext.xml file in src/main/webapp/WEB-INF (or core/src/main/resources for a modular archetype) and add the following XML to it:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
[http://www.springframework.org/schema/beans/spring-beans-2.0.xsd]">

<bean id="personDao" class="org.appfuse.tutorial.dao.ibatis.PersonDaoiBatis">
<property name="dataSource" ref="dataSource"/>
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
</beans>

Run the DAO Test

Save all your edited files and try running mvn test -Dtest=PersonDaoTest one more time.

Yeah Baby, Yeah:

BUILD SUCCESSFUL
Total time: 9 seconds
*Incidentally, you can try running the tests from your IDE. Make sure you regenerate your project files after changing the pom dependencies. (run mvn idea:idea after any changes to dependencies. This is necessary in Idea though I'm not sure about Eclipse.)

Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.1.3 Build:#408 Jan 23, 2006) - Bug/feature request - Contact Administrators