关系型数据库和SQL是经受时间考验和验证的数据存储机制。和其他的ORM 框架如Hibernate不同,MyBatis鼓励开发者可以直接使用数据库,而不是将其对开发者隐藏,因为这样可以充分发挥数据库服务器所提供的SQL语句的巨大威力。与此同时,MyBaits消除了书写大量冗余代码的痛苦,它使使用SQL更容易。

    在代码里直接嵌套SQL语句是很差的编码实践,并且维护起来困难。MyBaits使用了映射器配置文件或注解来配置SQL语句。在本章中,我们会看到具体怎样使用映射器配置文件来配置映射SQL语句。

 

本章将涵盖以下话题:

  • l  映射器配置文件映射器接口
  • l  映射语句

           ž配置INSERT, UPDATE, DELETE,and SELECT语句

  • l  结果映射ResultMaps

           ž简单 ResultMaps

           ž使用内嵌select语句子查询的一对一映射

           ž使用内嵌的结果集查询的一对一映射

           ž使用内嵌select语句子查询的一对多映射

           ž使用内嵌的结果集查询的一对一映射

  • l  动态SQL语句

           žIf 条件

           žchoose (when, otherwise) 条件

           žtrim (where, set) 条件

           žforeach 循环

  • l  MyBatis 菜谱

3.1 映射器配置文件和映射器接口

    在前几章中,我们已经看见了一些在映射器配置文件中配置基本的映射语句,以及怎样使用SqlSession对象调用它们的例子。

    现在让我们看一下在com.mybatis3.mappers包中的StudentMapper.xml 配置文件内,是如何配置 id 为”findStudentById”的SQL语句的,代码如下:

[html]  view plain  copy
 print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
  3. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">  
  4. <mapper namespace="com.mybatis3.mappers.StudentMapper">  
  5.   <select id="findStudentById" parameterType="int" resultType="Student">  
  6.     select stud_id as studId, name, email, dob   
  7.     from Students where stud_id=#{studId}  
  8.   </select>  
  9. </mapper>  

我们可以通过下列代码调用findStudentById映射的SQL语句:

[java]  view plain  copy
 print ?
  1. public Student findStudentById(Integer studId)  
  2. {  
  3.     SqlSession sqlSession = MyBatisUtil.getSqlSession();  
  4.     try  
  5.     {  
  6.         Student student =  
  7.             sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.  
  8.                                  findStudentById", studId);  
  9.         return student;  
  10.     }  
  11.     finally  
  12.     {  
  13.         sqlSession.close();  
  14.     }  
  15. }  

    我们可以通过字符串(字符串形式为:映射器配置文件所在的包名namespace + 在文件内定义的语句id,如上,即包名com.mybatis3.mappers.StudentMapper 和语句idfindStudentById 组成)调用映射的SQL语句,但是这种方式容易出错。你需要检查映射器配置文件中的定义,以保证你的输入参数类型和结果返回类型是有效的。

    MyBatis通过使用映射器Mapper接口提供了更好的调用映射语句的方法。一旦我们通过映射器配置文件配置了映射语句,我们可以创建一个完全对应的一个映射器接口,接口名跟配置文件名相同,接口所在包名也跟配置文件所在包名完全一样(如StudentMapper.xml所在的包名是com.mybatis3.mappers,对应的接口名就是com.mybatis3.mappers.StudentMapper.java)。映射器接口中的方法签名也跟映射器配置文件中完全对应:方法名为配置文件中id值;方法参数类型为parameterType对应值;方法返回值类型为returnType对应值。

 

    对于上述的 StudentMapper.xml 文件,我们可以创建一个映射器接口StudentMapper.java 如下:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     Student findStudentById(Integer id);  
  5. }  

    在StudentMapper.xml 映射器配置文件中,其名空间namespace 应该跟StudentMapper接口的完全限定名保持一致。另外,StudentMapper.xml中语句id,parameterType,returnType应该分别和StudentMapper接口中的方法名,参数类型,返回值相对应。

 

    使用映射器接口我们可以以类型安全的形式调用调用映射语句。如下所示:

[java]  view plain  copy
 print ?
  1. public Student findStudentById(Integer studId)  
  2.   
  3.    SqlSession sqlSession = MyBatisUtil.getSqlSession();  
  4.    try  
  5.    {  
  6.        StudentMapper studentMapper =  
  7.            sqlSession.getMapper(StudentMapper.class);  
  8.        return studentMapper.findStudentById(studId);  
  9.    }  
  10.    finally  
  11.    {  
  12.        sqlSession.close();  
  13.    }  

3.2 映射语句

MyBatis提供了多种元素来配置不同类型的语句,如SELECT,INSERT,UPDATE,DELETE。接下来让我们看看如何具体配置映射语句。

3.2.1 INSERT语句

一个INSERT SQL语句可以在<insert>元素在映射器XML配置文件中配置,如下所示:

[html]  view plain  copy
 print ?
  1. <insert id="insertStudent" parameterType="Student">  
  2.     INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)  
  3.         VALUES(#{studId},#{name},#{email},#{phone})  
  4. </insert>  

    这里我们使用一个ID insertStudent,可以在名空间com.mybatis3.mappers.StudentMapper.insertStudent中唯一标识。parameterType属性应该是一个完全限定类名或者是一个类型别名(alias)。

 

我们可以如下调用这个语句:
[java]  view plain  copy
 print ?
  1. int count =sqlSession.insert("com.mybatis3.mappers.StudentMapper.insertStudent", student);  

sqlSession.insert()方法返回执行INSERT语句后所影响的行数。

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     int insertStudent(Student student);  
  5. }  

你可以如下调用 insertStudent 映射语句:
[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. int count = mapper.insertStudent(student);  

[自动生成主键]

在上述的INSERT语句中,我们为可以自动生成(auto-generated)主键的列STUD_ID插入值。我们可以使用useGeneratedKeys 和 keyProperty属性让数据库生成 auto_increment列的值,并将生成的值设置到其中一个输入对象属性内,如下所示:

[html]  view plain  copy
 print ?
  1. <insert id="insertStudent" parameterType="Student" useGeneratedKeys="true"  
  2.          keyProperty="studId">  
  3.     INSERT INTO STUDENTS(NAME, EMAIL, PHONE)   
  4.     VALUES(#{name},#{email},#{phone})  
  5. </insert>  

这里 STUD_ID列值将会被MySQL 数据库自动生成,并且生成的值会被设置到student对象的studId属性上。

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. mapper.insertStudent(student);  

现在可以如下获取插入的STUDENT 记录的STUD_ID的值:

[java]  view plain  copy
 print ?
  1. int studentId = student.getStudId();  

有些数据库如Oracle并不支持 AUTO_INCREMENT 列,其使用序列(SEQUENCE)来生成主键值。

假设我们有一个名为 STUD_ID_SEQ 的序列来生成SUTD_ID主键值。使用如下代码来生成主键:
[html]  view plain  copy
 print ?
  1. <insert id="insertStudent" parameterType="Student">  
  2.     <selectKey keyProperty="studId" resultType="int" order="BEFORE">  
  3.         SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL  
  4.     </selectKey>  
  5.     INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)  
  6.         VALUES(#{studId},#{name},#{email},#{phone})  
  7. </insert>  

    这里我们使用了<selectKey>子元素来生成主键值,并将值保存到Student对象的studId属性上。 属性order=“before”表示MyBatis将取得序列的下一个值作为主键值,并且在执行INSERTSQL语句之前将值设置到studId属性上。

    我们也可以在获取序列的下一个值时,使用触发器(trigger)来设置主键值,并且在执行INSERT SQL语句之前将值设置到主键列上。如果你采取这样的方式,则对应的INSERT映射语句如下所示:

[html]  view plain  copy
 print ?
  1. <insert id="insertStudent" parameterType="Student">  
  2.     INSERT INTO STUDENTS(NAME,EMAIL, PHONE)  
  3.         VALUES(#{name},#{email},#{phone})  
  4.     <selectKey keyProperty="studId" resultType="int" order="AFTER">  
  5.         SELECT ELEARNING.STUD_ID_SEQ.CURRVAL FROM DUAL  
  6.     </selectKey>  
  7. </insert>  

3.2.2 UPDATE语句

一个UPDATE SQL语句可以在<update>元素在映射器XML配置文件中配置,如下所示:

[html]  view plain  copy
 print ?
  1. <update id="updateStudent" parameterType="Student">  
  2.     UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}  
  3.     WHERE STUD_ID=#{studId}  
  4. </update>  

我们可以如下调用此语句:

[java]  view plain  copy
 print ?
  1. int noOfRowsUpdated =  
  2. sqlSession.update("com.mybatis3.mappers.StudentMapper.updateStudent", student);  

sqlSession.update() 方法返回执行UPDATE语句之后影响的行数。

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     int updateStudent(Student student);  
  5. }  

你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. int noOfRowsUpdated = mapper.updateStudent(student);  

3.2.3 DELETE 语句

一个UPDATE SQL语句可以在<update>元素在映射器XML配置文件中配置,如下所示:

[html]  view plain  copy
 print ?
  1. <delete id="deleteStudent" parameterType="int">  
  2.    DELETE FROM STUDENTS WHERE STUD_ID=#{studId}  
  3. </delete>  

我们可以如下调用此语句:

[java]  view plain  copy
 print ?
  1. int studId = 1;  
  2. int noOfRowsDeleted =  
  3.     sqlSession.delete("com.mybatis3.mappers.StudentMapper.deleteStudent", studId);  

sqlSession.delete()方法返回delete语句执行后影响的行数。

 

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     int deleteStudent(int studId);  
  5. }  

你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. int noOfRowsDeleted = mapper.deleteStudent(studId);  

3.2.4 SELECT语句

MyBatis真正强大的功能,在于映射SELECT查询结果到JavaBeans 方面的极大灵活性。

让我们看看一个简单的select查询是如何(在MyBatis中)配置的,如下所示:

[html]  view plain  copy
 print ?
  1. <select id="findStudentById" parameterType="int"   
  2. resultType="Student">  
  3.     SELECT STUD_ID, NAME, EMAIL, PHONE   
  4.         FROM STUDENTS   
  5.     WHERE STUD_ID=#{studId}  
  6. </select>  

我们可以如下调用此语句:

[java]  view plain  copy
 print ?
  1. int studId =1;  
  2. Student student = sqlSession.selectOne("com.mybatis3.mappers.  
  3. StudentMapper.findStudentById", studId);  

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     Student findStudentById(Integer studId);  
  5. }  

你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. Student student = mapper.findStudentById(studId);  

    如果你检查Student对象的属性值,你会发现studId属性值并没有被stud_id列值填充。这是因为MyBatis自动对JavaBean中和列名匹配的属性进行填充。这就是为什么name ,email,和phone属性被填充,而studId属性没有被填充。

    为了解决这一问题,我们可以为列名起一个可以与JavaBean中属性名匹配的别名,如下所示:

[html]  view plain  copy
 print ?
  1. <select id="findStudentById" parameterType="int"   
  2. resultType="Student">  
  3.     SELECT STUD_ID AS studId, NAME,EMAIL, PHONE   
  4.         FROM STUDENTS   
  5.     WHERE STUD_ID=#{studId}  
  6. </select>  

现在,Student 这个Bean对象中的值将会恰当地被stud_id,name,email,phone列填充了。

现在,让我们看一下如何执行返回多条结果的SELECT语句查询,如下所示:

[html]  view plain  copy
 print ?
  1. <select id="findAllStudents" resultType="Student">  
  2.     SELECT STUD_ID AS studId, NAME,EMAIL, PHONE   
  3.     FROM STUDENTS  
  4. </select>  

[java]  view plain  copy
 print ?
  1. List<Student> students =   
  2. sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllStudents");  

映射器Mapper接口StudentMapper可以如下定义:

[java]  view plain  copy
 print ?
  1. package com.mybatis3.mappers;  
  2. public interface StudentMapper  
  3. {  
  4.     List<Student> findAllStudents();  
  5. }  

使用上述代码,我们可以如下调用 findAllStudents语句

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. List<Student> students = mapper.findAllStudents();  

如果你注意到上述的SELECT映射定义,你可以看到,我们为所有的映射语句中的stud_id起了别名。

我们可以使用ResultMaps,来避免上述的到处重复别名。我们稍后会继续讨论。

除了java.util.List,你也可以是由其他类型的集合类,如Set,Map,以及(SortedSet)。MyBatis根据集合的类型,会采用适当的集合实现,如下所示:

  • l 对于List,Collection,Iterable类型,MyBatis将返回java.util.ArrayList
  • l 对于Map类型,MyBatis将返回java.util.HashMap
  • l 对于Set类型,MyBatis将返回 java.util.HashSet
  • l 对于SortedSet类型,MyBatis将返回java.util.TreeSet

3.3 结果集映射ResultMaps

ResultMaps被用来 将SQL SELECT语句的结果集映射到 JavaBeans的属性中。我们可以定义结果集映射ResultMaps并且在一些SELECT语句上引用resultMap。MyBatis的结果集映射ResultMaps特性非常强大,你可以使用它将简单的SELECT语句映射到复杂的一对一和一对多关系的SELECT语句上。

3.3.1 简单ResultMap

一个映射了查询结果和Student JavaBean的简单的resultMap定义如下:

[html]  view plain  copy
 print ?
  1. <resultMap id="StudentResult" type="com.mybatis3.domain.Student">  
  2.   <id property="studId" column="stud_id" />  
  3.   <result property="name" column="name" />  
  4.   <result property="email" column="email" />  
  5.   <result property="phone" column="phone" />  
  6. </resultMap>  
  7.   
  8. <select id="findAllStudents" resultMap="StudentResult">  
  9.     SELECT * FROM STUDENTS  
  10. </select>  
  11.   
  12. <select id="findStudentById" parameterType="int" resultMap="StudentResult">  
  13.     SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}  
  14. </select>  

表示resultMap的StudentResult id值应该在此名空间内是唯一的。并且type属性应该是完全限定类名或者是返回类型的别名。

<result>子元素被用来将一个resultset列映射到JavaBean的一个属性中。

<id>元素和<result>元素功能相同,不过它被用来映射到唯一标识属性,用来区分和比较对象(一般和主键列相对应)。

在<select>语句中,我们使用了resultMap属性,而不是resultType来引用StudentResult映射。当<select>语句中配置了resutlMap属性,MyBatis会使用此数据库列名与对象属性映射关系来填充JavaBean中的属性。


 让我们来看另外一个<select>映射语句定义的例子,怎样将查询结果填充到HashMap中。如下所示:

[html]  view plain  copy
 print ?
  1. <select id="findStudentById" parameterType="int" resultType="map">  
  2.     SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}  
  3. </select>  

在上述的<select>语句中,我们将resultType配置成map,即java.util.HashMap的别名。在这种情况下,结果集的列名将会作为Map中的key值,而列值将作为Map的value值。

[java]  view plain  copy
 print ?
  1. HashMap<String,Object> studentMap = sqlSession.selectOne("com.  
  2. mybatis3.mappers.StudentMapper.findStudentById", studId);  
  3. System.out.println("stud_id :"+studentMap.get("stud_id"));  
  4. System.out.println("name :"+studentMap.get("name"));  
  5. System.out.println("email :"+studentMap.get("email"));  
  6. System.out.println("phone :"+studentMap.get("phone"));  

让我们再看一个 使用resultType=”map”,返回多行结果的例子:

[html]  view plain  copy
 print ?
  1. <select id="findAllStudents" resultType="map">  
  2.     SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS  
  3. </select>  

由于resultType=”map”和语句返回多行,则最终返回的数据类型应该是List<HashMap<String,Object>>,如下所示:

[java]  view plain  copy
 print ?
  1. List<HashMap<String, Object>> studentMapList =  
  2.     sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllS  
  3.                           tudents");  
  4. for(HashMap<String, Object> studentMap : studentMapList)  
  5. {  
  6.     System.out.println("studId :" + studentMap.get("stud_id"));  
  7.     System.out.println("name :" + studentMap.get("name"));  
  8.     System.out.println("email :" + studentMap.get("email"));  
  9.     System.out.println("phone :" + studentMap.get("phone"));  
  10. }  

3.2.2 拓展ResultMap

我们可以从从另外一个<resultMap>,拓展出一个新的<resultMap>,这样,原先的属性映射可以继承过来,以实现。

[html]  view plain  copy
 print ?
  1. <resultMap type="Student" id="StudentResult">  
  2.   <id property="studId" column="stud_id" />  
  3.   <result property="name" column="name" />  
  4.   <result property="email" column="email" />  
  5.   <result property="phone" column="phone" />  
  6. </resultMap>  
  7. <resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">  
  8.   <result property="address.addrId" column="addr_id" />  
  9.   <result property="address.street" column="street" />  
  10.   <result property="address.city" column="city" />  
  11.   <result property="address.state" column="state" />  
  12.   <result property="address.zip" column="zip" />  
  13.   <result property="address.country" column="country" />  
  14. </resultMap>  

id为StudentWithAddressResult的resultMap拓展了id为StudentResult的resultMap。

如果你只想映射Student数据,你可以使用id为StudentResult的resultMap,如下所示:

[html]  view plain  copy
 print ?
  1. <select id="findStudentById" parameterType="int"   
  2. resultMap="StudentResult">  
  3.     SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}  
  4. </select>  

如果你想将映射Student数据和Address数据,你可以使用id为StudentWithAddressResult的resultMap:

[html]  view plain  copy
 print ?
  1. <select id="selectStudentWithAddress" parameterType="int"   
  2. resultMap="StudentWithAddressResult">  
  3. SELECT STUD_ID, NAME, EMAIL, PHONE, A.ADDR_ID, STREET, CITY,    
  4.         STATE, ZIP, COUNTRY  
  5.     FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON   
  6.             S.ADDR_ID=A.ADDR_ID  
  7.     WHERE STUD_ID=#{studId}  
  8. </select>  

3.4 一对一映射

在我们的域模型样例中,每一个学生都有一个与之关联的地址信息。表STUDENTS有一个ADDR_ID列,是ADDRESSES表的外键。

STUDENTS表的样例数据如下所示:

STUD_ID

NAME

EMAIL

PHONE

ADDR_ID

1

John

john@gmail.com

123-456-7890

1

2

Paul

paul@gmail.com

111-222-3333

2

 

ADDRESSES表的样例输入如下所示:

ADDR_ID

STREET

CITY

STATE

ZIP

COUNTRY

1

Naperville

CHICAGO

IL

60515

USA

2

Paul

CHICAGO

IL

60515

USA

 

下面让我们看一下怎样取Student明细和其Address明细。

 

Student和Address 的JavaBean以及映射器Mapper XML文件定义如下所示:

[java]  view plain  copy
 print ?
  1. public class Address  
  2. {  
  3.     private Integer addrId;  
  4.     private String street;  
  5.     private String city;  
  6.     private String state;  
  7.     private String zip;  
  8.     private String country;  
  9.     // setters & getters  
  10. }  
  11. public class Student  
  12. {  
  13.     private Integer studId;  
  14.     private String name;  
  15.     private String email;  
  16.     private PhoneNumber phone;  
  17.     private Address address;  
  18.     //setters & getters  
  19. }  

[html]  view plain  copy
 print ?
  1. <resultMap type="Student" id="StudentWithAddressResult">  
  2.   <id property="studId" column="stud_id" />  
  3.   <result property="name" column="name" />  
  4.   <result property="email" column="email" />  
  5.   <result property="phone" column="phone" />  
  6.   <result property="address.addrId" column="addr_id" />  
  7.   <result property="address.street" column="street" />  
  8.   <result property="address.city" column="city" />  
  9.   <result property="address.state" column="state" />  
  10.   <result property="address.zip" column="zip" />  
  11.   <result property="address.country" column="country" />  
  12. </resultMap>  
  13.   
  14. <select id="selectStudentWithAddress" parameterType="int"   
  15. resultMap="StudentWithAddressResult">  
  16.     SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,   
  17.         ZIP, COUNTRY  
  18.     FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON   
  19.         S.ADDR_ID=A.ADDR_ID  
  20.     WHERE STUD_ID=#{studId}  
  21. </select>  

    我们可以使用圆点记法为内嵌的对象的属性赋值。在上述的resultMap中,Student的address属性使用了圆点记法被赋上了address对应列的值。同样地,我们可以访问任意深度的内嵌对象的属性。我们可以如下访问内嵌对象属性:
[java]  view plain  copy
 print ?
  1. //接口定义  
  2. public interface StudentMapper  
  3. {  
  4.     Student selectStudentWithAddress(int studId);  
  5. }  
  6.   
  7.   
  8. //使用  
  9. int studId = 1;  
  10. StudentMapper studentMapper =  
  11.     sqlSession.getMapper(StudentMapper.class);  
  12. Student student = studentMapper.selectStudentWithAddress(studId);  
  13. System.out.println("Student :" + student);  
  14. System.out.println("Address :" + student.getAddress());  

     上述样例展示了一对一关联映射的一种方法。然而,使用这种方式映射,如果address结果需要在其他的SELECT映射语句中映射成Address对象,我们需要为每一个语句重复这种映射关系。MyBatis提供了更好地实现一对一关联映射的方法:嵌套结果ResultMap和嵌套select查询语句。接下来,我们将讨论这两种方式。

3.4.1 使用嵌套结果ResultMap实现一对一关系映射

我们可以使用一个嵌套结果ResultMap方式来获取Student及其Address信息,代码如下:

[html]  view plain  copy
 print ?
  1. <resultMap type="Address" id="AddressResult">  
  2.   <id property="addrId" column="addr_id" />  
  3.   <result property="street" column="street" />  
  4.   <result property="city" column="city" />  
  5.   <result property="state" column="state" />  
  6.   <result property="zip" column="zip" />  
  7.   <result property="country" column="country" />  
  8. </resultMap>  
  9. <resultMap type="Student" id="StudentWithAddressResult">  
  10.   <id property="studId" column="stud_id" />  
  11.   <result property="name" column="name" />  
  12.   <result property="email" column="email" />  
  13.   <association property="address" resultMap="AddressResult" />  
  14. </resultMap>  
  15.   
  16. <select id="findStudentWithAddress" parameterType="int"   
  17. resultMap="StudentWithAddressResult">  
  18.     SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,   
  19.     ZIP, COUNTRY  
  20.     FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON   
  21.     S.ADDR_ID=A.ADDR_ID  
  22.     WHERE STUD_ID=#{studId}  
  23. </select>  

元素<association>被用来导入“有一个”(has-one)类型的关联。在上述的例子中,我们使用了<association>元素引用了另外的在同一个XML文件中定义的<resultMap>。

我们也可以使用<association定义内联的resultMap,代码如下所示:

[html]  view plain  copy
 print ?
  1. <resultMap type="Student" id="StudentWithAddressResult">  
  2.   <id property="studId" column="stud_id" />  
  3.   <result property="name" column="name" />  
  4.   <result property="email" column="email" />  
  5.   <association property="address" javaType="Address">  
  6.     <id property="addrId" column="addr_id" />  
  7.     <result property="street" column="street" />  
  8.     <result property="city" column="city" />  
  9.     <result property="state" column="state" />  
  10.     <result property="zip" column="zip" />  
  11.     <result property="country" column="country" />  
  12.   </association>  
  13. </resultMap>  

使用嵌套结果ResultMap方式,关联的数据可以通过简单的查询语句(如果需要的话,需要与joins 连接操作配合)进行加载。

 

3.4.2 使用嵌套查询实现一对一关系映射

我们可以通过使用嵌套select查询来获取Student及其Address信息,代码如下:

[html]  view plain  copy
 print ?
  1. <resultMap type="Address" id="AddressResult">  
  2.   <id property="addrId" column="addr_id" />  
  3.   <result property="street" column="street" />  
  4.   <result property="city" column="city" />  
  5.   <result property="state" column="state" />  
  6.   <result property="zip" column="zip" />  
  7.   <result property="country" column="country" />  
  8. </resultMap>  
  9.   
  10. <select id="findAddressById" parameterType="int"   
  11. resultMap="AddressResult">  
  12.     SELECT * FROM ADDRESSES WHERE ADDR_ID=#{id}  
  13. </select>  
  14.   
  15. <resultMap type="Student" id="StudentWithAddressResult">  
  16.   <id property="studId" column="stud_id" />  
  17.   <result property="name" column="name" />  
  18.   <result property="email" column="email" />  
  19.   <association property="address" column="addr_id" select="findAddressById" />  
  20. </resultMap>  
  21.   
  22. <select id="findStudentWithAddress" parameterType="int"   
  23. resultMap="StudentWithAddressResult">  
  24.     SELECT * FROM STUDENTS WHERE STUD_ID=#{Id}  
  25. </select>  

在此方式中,<association>元素的 select属性被设置成了id为 findAddressById的语句。这里,两个分开的SQL语句将会在数据库中执行,第一个调用findStudentById加载student信息,而第二个调用findAddressById来加载address信息。

Addr_id列的值将会被作为输入参数传递给selectAddressById语句。

 我们可以如下调用findStudentWithAddress映射语句:

[java]  view plain  copy
 print ?
  1. StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);  
  2. Student student = mapper.selectStudentWithAddress(studId);  
  3. System.out.println(student);  
  4. System.out.println(student.getAddress());  

3.5 一对多映射

在我们的域模型样例中,一个讲师可以教授一个或者多个课程。这意味着讲师和课程之间存在一对多的映射关系。

我们可以使用<collection>元素将 一对多类型的结果 映射到 一个对象集合上。

TUTORS表的样例数据如下:

TUTOR_ID

NAME

EMAIL

PHONE

ADDR_ID

1

John

john@gmail.com

123-456-7890

1

2

Ying

ying@gmail.com

111-222-3333

2

COURSE表的样例数据如下:

COURSE_ID

NAME

DESCRIPTION

START_DATE

END_DATE

TUTOR_ID

1

JavaSE

Java SE

2013-01-10

2013-02-10

1

2

JavaEE

Java EE 6

2013-01-10

2013-03-10

2

3

MyBatis

MyBatis

2013-01-10

2013-02-20

2

 

在上述的表数据中,John讲师教授一个课程,而Ying讲师教授两个课程。

 

Course和Tutor的JavaBean定义如下:

[java]  view plain  copy
 print ?
  1. public class Course  
  2. {  
  3.     private Integer courseId;  
  4.     private String name;  
  5.     private String description;  
  6.     private Date startDate;  
  7.     private Date endDate;  
  8.     private Integer tutorId;  
  9.     //setters & getters  
  10. }  
  11. public class Tutor  
  12. {  
  13.     private Integer tutorId;  
  14.     private String name;  
  15.     private String email;  
  16.     private Address address;  
  17.     private List<Course> courses;  
  18.     / setters & getters  
  19. }  

现在让我们看看如何获取讲师信息以及其所教授的课程列表信息。

<collection>元素被用来将多行课程结果映射成一个课程Course对象的一个集合。和一对一映射一样,我们可以使用嵌套结果ResultMap嵌套Select语句两种方式映射实现一对多映射。

3.5.1 使用内嵌结果ResultMap实现一对多映射

我们可以使用嵌套结果resultMap方式获得讲师及其课程信息,代码如下:

[html]  view plain  copy
 print ?
  1. <resultMap type="Course" id="CourseResult">  
  2.   <id column="course_id" property="courseId" />  
  3.   <result column="name" property="name" />  
  4.   <result column="description" property="description" />  
  5.   <result column="start_date" property="startDate" />  
  6.   <result column="end_date" property="endDate" />  
  7. </resultMap>  
  8. <resultMap type="Tutor" id="TutorResult">  
  9.   <id column="tutor_id" property="tutorId" />  
  10.   <result column="tutor_name" property="name" />  
  11.   <result column="email" property="email" />  
  12.   <collection property="courses" resultMap="CourseResult" />  
  13. </resultMap>  
  14.   
  15. <select id="findTutorById" parameterType="int"   
  16. resultMap="TutorResult">  
  17. SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL, C.COURSE_ID,   
  18. C.NAME, DESCRIPTION, START_DATE, END_DATE  
  19. FROM TUTORS T LEFT OUTER JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID  
  20. LEFT OUTER JOIN COURSES C ON T.TUTOR_ID=C.TUTOR_ID  
  21. WHERE T.TUTOR_ID=#{tutorId}  
  22. </select>  

这里我们使用了一个简单的使用了JOINS连接的Select语句获取讲师及其所教课程信息。<collection>元素的resultMap属性设置成了CourseResult,CourseResult包含了Course对象属性与表列名之间的映射。

3.5.2 使用嵌套Select语句实现一对多映射

我们可以使用嵌套Select语句方式获得讲师及其课程信息,代码如下:

[html]  view plain  copy
 print ?
  1. <resultMap type="Course" id="CourseResult">  
  2.   <id column="course_id" property="courseId" />  
  3.   <result column="name" property="name" />  
  4.   <result column="description" property="description" />  
  5.   <result column="start_date" property="startDate" />  
  6.   <result column="end_date" property="endDate" />  
  7. </resultMap>  
  8.   
  9. <resultMap type="Tutor" id="TutorResult">  
  10.   <id column="tutor_id" property="tutorId" />  
  11.   <result column="tutor_name" property="name" />  
  12.   <result column="email" property="email" />  
  13.   <association property="address" resultMap="AddressResult" />  
  14.   <collection property="courses" column="tutor_id" select="findCoursesByTutor" />  
  15. </resultMap>  
  16.   
  17. <select id="findTutorById" parameterType="int" resultMap="TutorResult">  
  18.     SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL   
  19.     FROM TUTORS T WHERE T.TUTOR_ID=#{tutorId}  
  20. </select>  
  21. <select id="findCoursesByTutor" parameterType="int" resultMap="CourseResult">  
  22.     SELECT * FROM COURSES WHERE TUTOR_ID=#{tutorId}  
  23. </select>  

    在这种方式中,<aossication>元素的select属性被设置为id 为findCourseByTutor的语句,用来触发单独的SQL查询加载课程信息。tutor_id这一列值将会作为输入参数传递给findCouresByTutor语句。

[java]  view plain  copy
 print ?
  1. public interface TutorMapper  
  2. {  
  3.     Tutor findTutorById(int tutorId);  
  4. }  
  5. TutorMapper mapper = sqlSession.getMapper(TutorMapper.class);  
  6. Tutor tutor = mapper.findTutorById(tutorId);  
  7. System.out.println(tutor);  
  8. List<Course> courses = tutor.getCourses();  
  9. for (Course course : courses)  
  10. {  
  11.     System.out.println(course);  
  12. }  


3.6 动态SQL

有时候,静态的SQL语句并不能满足应用程序的需求。我们可以根据一些条件,来动态地构建SQL语句。

例如,在Web应用程序中,有可能有一些搜索界面,需要输入一个或多个选项,然后根据这些已选择的条件去执行检索操作。在实现这种类型的搜索功能,我们可能需要根据这些条件来构建动态的SQL语句。如果用户提供了任何输入条件,我们需要将那个条件 添加到SQL语句的WHERE子句中。

MyBatis通过使用<if>,<choose>,<where>,<foreach>,<trim>元素提供了对构造动态SQL语句的高级别支持。

3.6.1 If 条件

<if>元素被用来有条件地嵌入SQL片段,如果测试条件被赋值为true,则相应地SQL片段将会被添加到SQL语句中。

假定我们有一个课程搜索界面,设置了 讲师(Tutor)下拉列表框,课程名称(CourseName)文本输入框,开始时间(StartDate)输入框,结束时间(EndDate)输入框,作为搜索条件。假定课讲师下拉列表是必须选的,其他的都是可选的。

当用户点击 搜索按钮时,我们需要显示符合以下条件的成列表:

  • l   特定讲师的课程
  • l   课程名 包含输入的课程名称关键字的课程;如果课程名称输入为空,则取所有课程
  • l   在开始时间和结束时间段内的课程

我们可以对应的映射语句,如下所示:

[html]  view plain  copy
 print ?
  1. <resultMap type="Course" id="CourseResult">  
  2.   <id column="course_id" property="courseId" />  
  3.   <result column="name" property="name" />  
  4.   <result column="description" property="description" />  
  5.   <result column="start_date" property="startDate" />  
  6.   <result column="end_date" property="endDate" />  
  7. </resultMap>  
  8.   
  9. <select id="searchCourses" parameterType="hashmap" resultMap="CourseResult"></select>  
  10.     SELECT * FROM COURSES  
  11.         WHERE TUTOR_ID= #{tutorId}  
  12.     <if test="courseName != null">  
  13.     AND NAME LIKE #{courseName}  
  14.     </if>  
  15.     <if test="startDate != null">  
  16.     AND START_DATE >= #{startDate}  
  17.     </if>  
  18.     <if test="endDate != null">  
  19.     AND END_DATE <= #{endDate}  
  20.     </if>  
  21. </select>  

[java]  view plain  copy
 print ?
  1. public interface CourseMapper  
  2. {  
  3.     List<Course> searchCourses(Map<String, Object> map);  
  4. }  
  5. public void searchCourses()  
  6. {  
  7.     Map<String, Object> map = new HashMap<String, Object>();  
  8.     map.put("tutorId"1);  
  9.     map.put("courseName""%java%");  
  10.     map.put("startDate"new Date());  
  11.     CourseMapper mapper = sqlSession.getMapper(CourseMapper.class);  
  12.     List<Course> courses = mapper.searchCourses(map);  
  13.     for (Course course : courses)  
  14.     {  
  15.         System.out.println(course);  
  16.     }  
  17. }  

此处将生成查询语句 SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like? AND START_DATE >= ?。准备根据给定条件的动态SQL查询将会派上用场。

3.6.2 choose,when 和otherwise 条件

有时候,查询功能是以查询类别为基础的。首先,用户需要选择是否希望通过选择讲师,课程名称,开始时间,或结束时间作为查询条件类别来进行查询,然后根据选择的查询类别,输入相应的参数。在这样的情景中,我们需要只使用其中一种查询类别。

MyBatis 提供了<choose>元素支持此类型的SQL预处理。

现在让我们书写一个适用此情景的SQL映射语句。如果没有选择查询类别,则查询开始时间在今天之后的课程,代码如下:

[html]  view plain  copy
 print ?
  1. <select id="searchCourses" parameterType="hashmap" resultMap="CourseResult">  
  2.     SELECT * FROM COURSES  
  3.     <choose>  
  4.         <when test="searchBy == 'Tutor'">  
  5.             WHERE TUTOR_ID= #{tutorId}  
  6.         </when>  
  7.         <when test="searchBy == 'CourseName'">  
  8.             WHERE name like #{courseName}  
  9.         </when>  
  10.         <otherwise>  
  11.             WHERE TUTOR start_date >= now()  
  12.         </otherwise>  
  13.     </choose>  
  14. </select>  

MyBatis计算<choose>测试条件的值,且使用第一个值为TRUE的子句。如果没有条件为true,则使用<otherwise>内的子句。

3.6.3 where条件

有时候,所有的查询条件(criteria)应该是可选的。在需要使用至少一种查询条件的情况下,我们应该使用WHERE子句。并且, 如果有多个条件,我们需要在条件中添加AND或OR。MyBatis提供了<where>元素支持这种类型的动态SQL语句。

在我们查询课程界面,我们假设所有的查询条件是可选的。进而,当需要提供一个或多个查询条件时,应该改使用WHERE子句。

[html]  view plain  copy
 print ?
  1. <select id="searchCourses" parameterType="hashmap"   
  2. resultMap="CourseResult">  
  3.     SELECT * FROM COURSES  
  4.     <where>   
  5.         <if test=" tutorId != null ">  
  6.             TUTOR_ID= #{tutorId}  
  7.         </if>  
  8.         <if test="courseName != null">  
  9.             AND name like #{courseName}  
  10.         </if>  
  11.         <if test="startDate != null">  
  12.             AND start_date >= #{startDate}  
  13.         </if>  
  14.         <if test="endDate != null">  
  15.             AND end_date <= #{endDate}  
  16.         </if>  
  17.     </where>  
  18. </select>  

<where>元素只有在其内部标签有返回内容时才会在动态语句上插入WHERE条件语句。并且,如果WHERE子句以AND或者OR打头,则打头的AND或OR将会被移除。

如果tutor_id参数值为null,并且courseName参数值不为null,则<where>标签会将AND name like#{courseName} 中的AND移除掉,生成的SQL WHERE子句为:where name like#{courseName}。

3.6.4 <trim>条件

<trim>元素和<where>元素类似,但是<trim>提供了在添加前缀/后缀 或者移除前缀/后缀方面提供更大的灵活性。

[html]  view plain  copy
 print ?
  1. <select id="searchCourses" parameterType="hashmap"   
  2. resultMap="CourseResult">  
  3. SELECT * FROM COURSES  
  4. <trim prefix="WHERE" prefixOverrides="AND | OR">  
  5. <if test=" tutorId != null ">  
  6. TUTOR_ID= #{tutorId}  
  7. </if>  
  8. <if test="courseName != null">  
  9. AND name like #{courseName}  
  10. </if>  
  11. </trim>  
  12. </select>  

这里如果任意一个<if>条件为true,<trim>元素会插入WHERE,并且移除紧跟WHERE后面的AND或OR。

3.6.5 foreach循环

另外一个强大的动态SQL语句构造标签即是<foreach>。它可以迭代遍历一个数组或者列表,构造AND/OR条件或一个IN子句。

假设我们想找到tutor_id为1,3,6的讲师所教授的课程,我们可以传递一个tutor_id组成的列表给映射语句,然后通过<foreach>遍历此列表构造动态SQL。

[html]  view plain  copy
 print ?
  1. <select id="searchCoursesByTutors" parameterType="map"   
  2. resultMap="CourseResult">  
  3. SELECT * FROM COURSES  
  4. <if test="tutorIds != null">  
  5. <where>  
  6. <foreach item="tutorId" collection="tutorIds">  
  7. OR tutor_id=#{tutorId}  
  8. </foreach>  
  9. </where>   
  10. </if>   
  11. </select>  

[java]  view plain  copy
 print ?
  1. public interface CourseMapper  
  2. {  
  3.     List<Course> searchCoursesByTutors(Map<String, Object> map);  
  4. }  
  5. public void searchCoursesByTutors()  
  6. {  
  7.     Map<String, Object> map = new HashMap<String, Object>();  
  8.     List<Integer> tutorIds = new ArrayList<Integer>();  
  9.     tutorIds.add(1);  
  10.     tutorIds.add(3);  
  11.     tutorIds.add(6);  
  12.     map.put("tutorIds", tutorIds);  
  13.     CourseMapper mapper =  
  14.         sqlSession.getMapper(CourseMapper.class);  
  15.     List<Course> courses = mapper.searchCoursesByTutors(map);  
  16.     for (Course course : courses)  
  17.     {  
  18.         System.out.println(course);  
  19.     }  
  20. }  

现在让我们来看一下怎样使用<foreach>生成 IN子句:

[html]  view plain  copy
 print ?
  1. <select id="searchCoursesByTutors" parameterType="map"   
  2. resultMap="CourseResult">  
  3.     SELECT * FROM COURSES  
  4.     <if test="tutorIds != null">  
  5.         <where>  
  6.         tutor_id IN  
  7.             <foreach item="tutorId" collection="tutorIds"   
  8.             open="(" separator="," close=")">  
  9.             #{tutorId}  
  10.             </foreach>  
  11.         </where>  
  12.     </if>  
  13. </select>  

3.6.6 set条件

<set>元素和<where>元素类似,如果其内部条件判断有任何内容返回时,他会插入SET SQL片段。

[html]  view plain  copy
 print ?
  1. <update id="updateStudent" parameterType="Student">  
  2.     update students   
  3.     <set>  
  4.     <if test="name != null">name=#{name},</if>  
  5.     <if test="email != null">email=#{email},</if>  
  6.     <if test="phone != null">phone=#{phone},</if>  
  7.     </set>  
  8.     where stud_id=#{id}  
  9. </update>  

这里,如果<if>条件返回了任何文本内容,<set>将会插入set关键字和其文本内容,并且会剔除将末尾的 “,”。

 在上述的例子中,如果 phone!=null,<set>将会让会移除  phone=#{phone}后的逗号“,”,

生成 set phone=#{phone} 


3.7 MyBaits 食谱

除了简化数据库编程外,MyBatis还提供了各种功能,这些对实现一些常用任务非常有用,比如按页加载表数据,存取CLOB/BLOB类型的数据,处理枚举类型值,等等。让我们来看看其中一些特性吧。

3.7.1  处理枚举类型

MyBatis支持开箱方式持久化enum 类型属性。假设STUDENTS表中有一列gender(性别)类型为varchar,存储”MALE”或者“FEMALE”两种值。并且,Student对象有一个enum 类型的gender属性,如下所示:

[java]  view plain  copy
 print ?
  1. public enum Gender  
  2. {  
  3.     FEMALE,  
  4.     MALE  
  5. }  

    默认情况下,MyBatis使用EnumTypeHandler来处理enum类型的Java属性,并且将其存储为enum值的名称。你不需要为此做任何额外的配置。你可以可以向使用基本数据类型属性一样使用enum类型属性,代码如下:
[java]  view plain  copy
 print ?
  1. public class Student  
  2. {  
  3.     private Integer id;  
  4.     private String name;  
  5.     private String email;  
  6.     private PhoneNumber phone;  
  7.     private Address address;  
  8.     private Gender gender;  
  9.     //setters and getters  
  10. }  

[html]  view plain  copy
 print ?
  1. <insert id="insertStudent" parameterType="Student"   
  2. useGeneratedKeys="true" keyProperty="id">  
  3. insert into students(name,email,addr_id, phone,gender)  
  4. values(#{name},#{email},#{address.addrId},#{phone},#{gender})  
  5. </insert>  

当你执行insertStudent语句的时候,MyBatis会取Gender枚举(FEMALE/MALE)的名称,然后将其存储到GENDER列中。

如果你希望存储原enum的顺序位置,而不是enum名,,你需要明确地配置它。

如果你想存储FEMALE为0,MALE为1到gender列中,你需要在mybatis-config.xml文件中配置EnumOrdinalTypeHandler:

[html]  view plain  copy
 print ?
  1. <typeHandler   
  2. handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"  
  3. javaType="com.mybatis3.domain.Gender"/>  

3.7.2 处理CLOB/BLOB类型数据

MyBatis提供了内建的对CLOB/BLOB类型列的映射处理支持。

假设我们有如下的表结构来存储学生和讲师的照片和简介信息:

[sql]  view plain  copy
 print ?
  1. CREATE TABLE USER_PICS   
  2. (  
  3. ID INT(11) NOT NULL AUTO_INCREMENT,  
  4. NAME VARCHAR(50) DEFAULT NULL,  
  5. PIC BLOB,  
  6. BIO LONGTEXT,  
  7. PRIMARY KEY (ID)  
  8. ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;  

    这里,照片可以是PNG,JPG或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,MyBatis将CLOB类型的列映射到java.lang.String类型上、而把BLOB列映射到byte[] 类型上。

[java]  view plain  copy
 print ?
  1. public class UserPic  
  2. {  
  3.     private int id;  
  4.     private String name;  
  5.     private byte[] pic;  
  6.     private String bio;  
  7.     //setters & getters  
  8. }  

创建UserPicMapper.xml文件,配置映射语句,代码如下:

[html]  view plain  copy
 print ?
  1. <insert id="insertUserPic" parameterType="UserPic">  
  2.     INSERT INTO USER_PICS(NAME, PIC,BIO)  
  3.     VALUES(#{name},#{pic},#{bio})  
  4. </insert>  
  5. <select id="getUserPic" parameterType="int" resultType="UserPic">  
  6.     SELECT * FROM USER_PICS WHERE ID=#{id}  
  7. </select>  

下列的insertUserPic()展示了如何将数据插入到CLOB/BLOB类型的列上:

[java]  view plain  copy
 print ?
  1. public void insertUserPic()  
  2. {  
  3.     byte[] pic = null;  
  4.     try  
  5.     {  
  6.         File file = new File("C:\\Images\\UserImg.jpg");  
  7.         InputStream is = new FileInputStream(file);  
  8.         pic = new byte[is.available()];  
  9.         is.read(pic);  
  10.         is.close();  
  11.     }  
  12.     catch (FileNotFoundException e)  
  13.     {  
  14.         e.printStackTrace();  
  15.     }  
  16.     catch (IOException e)  
  17.     {  
  18.         e.printStackTrace();  
  19.     }  
  20.     String name = "UserName";  
  21.     String bio = "put some lenghty bio here";  
  22.     UserPic userPic = new UserPic(0, name, pic , bio);  
  23.     SqlSession sqlSession = MyBatisUtil.openSession();  
  24.     try  
  25.     {  
  26.         UserPicMapper mapper =  
  27.             sqlSession.getMapper(UserPicMapper.class);  
  28.         mapper.insertUserPic(userPic);  
  29.         sqlSession.commit();  
  30.     }  
  31.     finally  
  32.     {  
  33.         sqlSession.close();  
  34.     }  
  35. }  

下面的getUserPic()方法展示了怎样将CLOB类型数据读取到String类型,BLOB类型数据读取成byte[]属性:

[java]  view plain  copy
 print ?
  1. public void getUserPic()  
  2. {  
  3.     UserPic userPic = null;  
  4.     SqlSession sqlSession = MyBatisUtil.openSession();  
  5.     try  
  6.     {  
  7.         UserPicMapper mapper =  
  8.             sqlSession.getMapper(UserPicMapper.class);  
  9.         userPic = mapper.getUserPic(1);  
  10.     }  
  11.     finally  
  12.     {  
  13.         sqlSession.close();  
  14.     }  
  15.     byte[] pic = userPic.getPic();  
  16.     try  
  17.     {  
  18.         OutputStream os = new FileOutputStream(new  
  19.                                                File("C:\\Images\\UserImage_FromDB.jpg"));  
  20.         os.write(pic);  
  21.         os.close();  
  22.     }  
  23.     catch (FileNotFoundException e)  
  24.     {  
  25.         e.printStackTrace();  
  26.     }  
  27.     catch (IOException e)  
  28.     {  
  29.         e.printStackTrace();  
  30.     }  
  31. }  

3.7.3 传入多个输入参数

MyBatis中的映射语句有一个parameterType属性来制定输入参数的类型。如果我们想给映射语句传入多个参数的话,我们可以将所有的输入参数放到HashMap中,将HashMap传递给映射语句。

MyBatis 还提供了另外一种传递多个输入参数给映射语句的方法。假设我们想通过给定的name和email信息查找学生信息,定义查询接口如下:

[java]  view plain  copy
 print ?
  1. Public interface StudentMapper  
  2. {  
  3.     List<Student> findAllStudentsByNameEmail(String name, String email);  
  4. }  

MyBatis 支持将多个输入参数传递给映射语句,并以#{param}的语法形式引用它们:

[html]  view plain  copy
 print ?
  1. <select id="findAllStudentsByNameEmail" resultMap="StudentResult">  
  2.     select stud_id, name,email, phone from Students  
  3.         where name=#{param1} and email=#{param2}  
  4. </select>  

这里#{param1}引用第一个参数name,而#{param2}引用了第二个参数email。

[java]  view plain  copy
 print ?
  1. StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);  
  2. studentMapper.findAllStudentsByNameEmail(name, email);  

3.7.4 多行结果集映射成Map

如果你有一个映射语句返回多行记录,并且你想以HashMap的形式存储记录的值,使用记录列名作为key值,而记录对应值或为value值。我们可以使用sqlSession.selectMap(),如下所示:

[html]  view plain  copy
 print ?
  1. <select id=" findAllStudents" resultMap="StudentResult">  
  2.     select * from Students  
  3. </select>  

[java]  view plain  copy
 print ?
  1. Map<Integer, Student> studentMap =   
  2. sqlSession.selectMap("com.mybatis3.mappers.StudentMapper.findAllStudents""studId");  

这里studentMap将会将studId作为key值,而Student对象作为value值。

3.7.5 使用RowBounds对结果集进行分页

有时候,我们会需要跟海量的数据打交道,比如一个有数百万条数据级别的表。由于计算机内存的现实我们不可能一次性加载这么多数据,我们可以获取到数据的一部分。特别是在Web应用程序中,分页机制被用来以一页一页的形式展示海量的数据。

MyBatis可以使用RowBounds逐页加载表数据。RowBounds对象可以使用offset和limit参数来构建。参数offset表示开始位置,而limit表示要取的记录的数目。

假设如果你想每页加载并显示25条学生的记录,你可以使用如下的代码:

[html]  view plain  copy
 print ?
  1. <select id="findAllStudents" resultMap="StudentResult">  
  2.     select * from Students  
  3. </select>  

然后,你可以加载如下加载第一页数据(前25条):

[java]  view plain  copy
 print ?
  1. int offset =0 , limit =25;  
  2. RowBounds rowBounds = new RowBounds(offset, limit);  
  3. List<Student> = studentMapper.getStudents(rowBounds);  

若要展示第二页,使用offset=25,limit=25;第三页,则为offset=50,limit=25。

3.7.6 使用ResultSetHandler自定义结果集ResultSet处理

MyBatis在将查询结果集映射到JavaBean方面提供了很大的选择性。但是,有时候我们会遇到由于特定的目的,需要我们自己处理SQL查询结果的情况。MyBatis提供了ResultHandler插件形式允许我们以任何自己喜欢的方式处理结果集ResultSet。

假设我们想从学生的stud_id被用作key,而name被用作value的HashMap中获取到student信息。

对于sqlSession.select()方法,我们可以传递给它一个ResultHandler的实现,它会被调用来处理ResultSet的每一条记录。

[java]  view plain  copy
 print ?
  1. public interface ResultHandler  
  2. {  
  3.     void handleResult(ResultContext context);  
  4. }  

现在然我们来看一下怎么使用ResultHandler来处理结果集ResultSet,并返回自定义化的结果。

[java]  view plain  copy
 print ?
  1. public Map<Integer, String> getStudentIdNameMap()  
  2. {  
  3.     final Map<Integer, String> map = new HashMap<Integer, String>();  
  4.     SqlSession sqlSession = MyBatisUtil.openSession();  
  5.     try  
  6.     {  
  7.         sqlSession.select("com.mybatis3.mappers.StudentMapper.findAllStude  
  8.                           nts",  
  9.                           new ResultHandler()  
  10.         {  
  11.             @Override  
  12.             public void handleResult(ResultContext context)  
  13.             {  
  14.                 Student student = (Student) context.getResultObject();  
  15.                 map.put(student.getStudId(), student.getName());  
  16.             }  
  17.         }  
  18.                          );  
  19.     }  
  20.     finally  
  21.     {  
  22.         sqlSession.close();  
  23.     }  
  24.     return map;  
  25. }  

     在上述的代码中,我们提供了匿名内部ResultHandler实现类,在handleResult()方法中,我们使用context.getResultObject()获取当前的result对象,即Student对象,因为我们定义了findAllStudent映射语句的resultMap=”studentResult“。对查询返回的每一行都会调用handleResult()方法,并且我们从Student对象中取出studId和name,将其放到map中。

3.7.7 缓存

将从数据库中加载的数据缓存到内存中,是很多应用程序为了提高性能而采取的一贯做法。MyBatis对通过映射的SELECT语句加载的查询结果提供了内建的缓存支持。默认情况下,启用一级缓存;即,如果你使用同一个SqlSession接口对象调用了相同的SELECT语句,则直接会从缓存中返回结果,而不是再查询一次数据库。

我们可以在SQL映射器XML配置文件中使用<cache />元素添加全局二级缓存。

当你加入了<cache/>元素,将会出现以下情况:

  •         ž 所有的在映射语句文件定义的<select>语句的查询结果都会被缓存
  •         ž 所有的在映射语句文件定义的<insert>,<update> 和<delete>语句将会刷新缓存
  •         ž 缓存根据最近最少被使用(Least Recently Used,LRU)算法管理
  •         ž 缓存不会被任何形式的基于时间表的刷新(没有刷新时间间隔),即不支持定时刷新机制
  •         ž 缓存将存储1024个 查询方法返回的列表或者对象的引用
  •         ž 缓存会被当作一个读/写缓存。这是指检索出的对象不会被共享,并且可以被调用者安全地修改,不会其他潜在的调用者或者线程的潜在修改干扰。(即,缓存是线程安全的)

你也可以通过复写默认属性来自定义缓存的行为,如下所示:

[html]  view plain  copy
 print ?
  1. <cache eviction="FIFO" flushInterval="60000" size="512"   
  2. readOnly="true"/>  

以下是对上述属性的描述:

  •         ž eviction:此处定义缓存的移除机制。默认值是LRU,其可能的值有:LRU(least recently used,最近最少使用),FIFO(first infirst out,先进先出),SOFT(soft reference,软引用),WEAK(weak reference,弱引用)。
  •         ž flushInterval:定义缓存刷新间隔,以毫秒计。默认情况下不设置。所以不使用刷新间隔,缓存cache只有调用语句的时候刷新。
  •         ž size:此表示缓存cache中能容纳的最大元素数。默认值是1024,你可以设置成任意的正整数。
  •         ž readOnly:一个只读的缓存cache会对所有的调用者返回被缓存对象的同一个实例(实际返回的是被返回对象的一份引用)。一个读/写缓存cache将会返回被返回对象的一分拷贝(通过序列化)。默认情况下设置为false。可能的值有false和true。

一个缓存的配置和缓存实例被绑定到映射器配置文件所在的名空间(namespace)上,所以在相同名空间内的所有语句被绑定到一个cache中。

默认的映射语句的cache配置如下:

[html]  view plain  copy
 print ?
  1. <select ... flushCache="false" useCache="true"/>  
  2. <insert ... flushCache="true"/>  
  3. <update ... flushCache="true"/>  
  4. <delete ... flushCache="true"/>  

你可以为任意特定的映射语句复写默认的cache行为;例如,对一个select语句不使用缓存,可以设置useCache=“false”。

除了内建的缓存支持,MyBatis也提供了与第三方缓存类库如Ehcache,OSCache,Hazelcast的集成支持。你可以在MyBatis官方网站https://code.google.com/p/mybatis/wiki/Caches 上找到关于继承第三方缓存类库的更多信息。

3.8 总结

     在本章中,我们学习了怎样使用映射器配置文件 书写SQL映射语句。讨论了如何配置简单的语句,一对一以及一对多关系的语句,以及怎样使用ResultMap进行结果集映射。我们还了解了构建动态SQL语句,结果分页,以及自定义结果集(ResultSet)处理。在下一章,我们将会讨论如何使用注解书写映射语句。