Java Persistence with MyBatis 3(中文版) 第三章 使用XML配置SQL映射器
时间:2022-03-10 17:48
关系型数据库和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 菜谱
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis3.mappers.StudentMapper">
<select id="findStudentById" parameterType="int" resultType="Student">
select stud_id as studId, name, email, dob
from Students where stud_id=#{studId}
</select>
</mapper>
我们可以通过下列代码调用findStudentById映射的SQL语句:
public Student findStudentById(Integer studId) { SqlSession sqlSession = MyBatisUtil.getSqlSession(); try { Student student = sqlSession.selectOne("com.mybatis3.mappers.StudentMapper. findStudentById", studId); return student; } finally { sqlSession.close(); } }
我们可以通过字符串(字符串形式为:映射器配置文件所在的包名namespace + 在文件内定义的语句id,如上,即包名com.mybatis3.mappers.StudentMapper 和语句id findStudentById 组成)调用映射的SQL语句,但是这种方式容易出错。你需要检查映射器配置文件中的定义,以保证你的输入参数类型和结果返回类型是有效的。
MyBatis通过使用映射器Mapper接口提供了更好的调用映射语句的方法。一旦我们通过映射器配置文件配置了映射语句,我们可以创建一个完全对应的一个映射器接口,接口名跟配置文件名相同,接口所在包名也跟配置文件所在包名完全一样(如StudentMapper.xml所在的包名是com.mybatis3.mappers,对应的接口名就是com.mybatis3.mappers.StudentMapper.java)。映射器接口中的方法签名也跟映射器配置文件中完全对应:方法名为配置文件中id值;方法参数类型为parameterType对应值;方法返回值类型为returnType对应值。
对于上述的 StudentMapper.xml 文件,我们可以创建一个映射器接口StudentMapper.java 如下:
package com.mybatis3.mappers; public interface StudentMapper { Student findStudentById(Integer id); }
在StudentMapper.xml 映射器配置文件中,其名空间namespace 应该跟StudentMapper接口的完全限定名保持一致。另外,StudentMapper.xml中语句id,parameterType,returnType应该分别和StudentMapper接口中的方法名,参数类型,返回值相对应。
使用映射器接口我们可以以类型安全的形式调用调用映射语句。如下所示:
public Student findStudentById(Integer studId) { SqlSession sqlSession = MyBatisUtil.getSqlSession(); try { StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); return studentMapper.findStudentById(studId); } finally { sqlSession.close(); } }
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
这里我们使用一个ID insertStudent,可以在名空间com.mybatis3.mappers.StudentMapper.insertStudent中唯一标识。parameterType属性应该是一个完全限定类名或者是一个类型别名(alias)。
我们可以如下调用这个语句:
int count =sqlSession.insert("com.mybatis3.mappers.StudentMapper.insertStudent", student);
sqlSession.insert()方法返回执行INSERT语句后所影响的行数。
如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:
package com.mybatis3.mappers; public interface StudentMapper { int insertStudent(Student student); }
你可以如下调用 insertStudent 映射语句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); int count = mapper.insertStudent(student);
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true"
keyProperty="studId">
INSERT INTO STUDENTS(NAME, EMAIL, PHONE)
VALUES(#{name},#{email},#{phone})
</insert>
这里 STUD_ID列值将会被MySQL 数据库自动生成,并且生成的值会被设置到student对象的studId属性上。
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); mapper.insertStudent(student);
现在可以如下获取插入的STUDENT 记录的STUD_ID的值:
int studentId = student.getStudId();
有些数据库如Oracle并不支持 AUTO_INCREMENT 列,其使用序列(SEQUENCE)来生成主键值。
假设我们有一个名为 STUD_ID_SEQ 的序列来生成SUTD_ID主键值。使用如下代码来生成主键:<insert id="insertStudent" parameterType="Student"> <selectKey keyProperty="studId" resultType="int" order="BEFORE"> SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL </selectKey> INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE) VALUES(#{studId},#{name},#{email},#{phone}) </insert>
这里我们使用了<selectKey>子元素来生成主键值,并将值保存到Student对象的studId属性上。 属性order=“before”表示MyBatis将取得序列的下一个值作为主键值,并且在执行INSERTSQL语句之前将值设置到studId属性上。
我们也可以在获取序列的下一个值时,使用触发器(trigger)来设置主键值,并且在执行INSERT SQL语句之前将值设置到主键列上。如果你采取这样的方式,则对应的INSERT映射语句如下所示:
<insert id="insertStudent" parameterType="Student"> INSERT INTO STUDENTS(NAME,EMAIL, PHONE) VALUES(#{name},#{email},#{phone}) <selectKey keyProperty="studId" resultType="int" order="AFTER"> SELECT ELEARNING.STUD_ID_SEQ.CURRVAL FROM DUAL </selectKey> </insert>
<update id="updateStudent" parameterType="Student">
UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
WHERE STUD_ID=#{studId}
</update>
我们可以如下调用此语句:
int noOfRowsUpdated = sqlSession.update("com.mybatis3.mappers.StudentMapper.updateStudent", student);
sqlSession.update() 方法返回执行UPDATE语句之后影响的行数。
如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:
package com.mybatis3.mappers; public interface StudentMapper { int updateStudent(Student student); }
你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); int noOfRowsUpdated = mapper.updateStudent(student);
<delete id="deleteStudent" parameterType="int"> DELETE FROM STUDENTS WHERE STUD_ID=#{studId} </delete>
我们可以如下调用此语句:
int studId = 1; int noOfRowsDeleted = sqlSession.delete("com.mybatis3.mappers.StudentMapper.deleteStudent", studId);
sqlSession.delete()方法返回delete语句执行后影响的行数。
如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:
package com.mybatis3.mappers; public interface StudentMapper { int deleteStudent(int studId); }
你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); int noOfRowsDeleted = mapper.deleteStudent(studId);
<select id="findStudentById" parameterType="int"
resultType="Student">
SELECT STUD_ID, NAME, EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
我们可以如下调用此语句:
int studId =1; Student student = sqlSession.selectOne("com.mybatis3.mappers. StudentMapper.findStudentById", studId);
如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器Mapper接口,并以类型安全的方式调用方法,如下所示:
package com.mybatis3.mappers; public interface StudentMapper { Student findStudentById(Integer studId); }
你可以使用映射器Mapper接口来调用 updateStudent语句,如下所示:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); Student student = mapper.findStudentById(studId);
如果你检查Student对象的属性值,你会发现studId属性值并没有被stud_id列值填充。这是因为MyBatis自动对JavaBean中和列名匹配的属性进行填充。这就是为什么name ,email,和phone属性被填充,而studId属性没有被填充。
为了解决这一问题,我们可以为列名起一个可以与JavaBean中属性名匹配的别名,如下所示:
<select id="findStudentById" parameterType="int" resultType="Student"> SELECT STUD_ID AS studId, NAME,EMAIL, PHONE FROM STUDENTS WHERE STUD_ID=#{studId} </select>
现在,Student 这个Bean对象中的值将会恰当地被stud_id,name,email,phone列填充了。
现在,让我们看一下如何执行返回多条结果的SELECT语句查询,如下所示:
<select id="findAllStudents" resultType="Student"> SELECT STUD_ID AS studId, NAME,EMAIL, PHONE FROM STUDENTS </select>
List<Student> students = sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllStudents");
映射器Mapper接口StudentMapper可以如下定义:
package com.mybatis3.mappers; public interface StudentMapper { List<Student> findAllStudents(); }
使用上述代码,我们可以如下调用 findAllStudents语句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); 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
<resultMap id="StudentResult" type="com.mybatis3.domain.Student"> <id property="studId" column="stud_id" /> <result property="name" column="name" /> <result property="email" column="email" /> <result property="phone" column="phone" /> </resultMap> <select id="findAllStudents" resultMap="StudentResult"> SELECT * FROM STUDENTS </select> <select id="findStudentById" parameterType="int" resultMap="StudentResult"> SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} </select>
表示resultMap的StudentResult id值应该在此名空间内是唯一的。并且type属性应该是完全限定类名或者是返回类型的别名。
<result>子元素被用来将一个resultset列映射到JavaBean的一个属性中。
<id>元素和<result>元素功能相同,不过它被用来映射到唯一标识属性,用来区分和比较对象(一般和主键列相对应)。
在<select>语句中,我们使用了resultMap属性,而不是resultType来引用StudentResult映射。当<select>语句中配置了resutlMap属性,MyBatis会使用此数据库列名与对象属性映射关系来填充JavaBean中的属性。
让我们来看另外一个<select>映射语句定义的例子,怎样将查询结果填充到HashMap中。如下所示:
<select id="findStudentById" parameterType="int" resultType="map"> SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} </select>
在上述的<select>语句中,我们将resultType配置成map,即java.util.HashMap的别名。在这种情况下,结果集的列名将会作为Map中的key值,而列值将作为Map的value值。
HashMap<String,Object> studentMap = sqlSession.selectOne("com. mybatis3.mappers.StudentMapper.findStudentById", studId); System.out.println("stud_id :"+studentMap.get("stud_id")); System.out.println("name :"+studentMap.get("name")); System.out.println("email :"+studentMap.get("email")); System.out.println("phone :"+studentMap.get("phone"));
让我们再看一个 使用resultType=”map”,返回多行结果的例子:
<select id="findAllStudents" resultType="map"> SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS </select>
由于resultType=”map”和语句返回多行,则最终返回的数据类型应该是List<HashMap<String,Object>>,如下所示:
List<HashMap<String, Object>> studentMapList = sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllS tudents"); for(HashMap<String, Object> studentMap : studentMapList) { System.out.println("studId :" + studentMap.get("stud_id")); System.out.println("name :" + studentMap.get("name")); System.out.println("email :" + studentMap.get("email")); System.out.println("phone :" + studentMap.get("phone")); }
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>
id为StudentWithAddressResult的resultMap拓展了id为StudentResult的resultMap。
如果你只想映射Student数据,你可以使用id为StudentResult的resultMap,如下所示:
<select id="findStudentById" parameterType="int" resultMap="StudentResult"> SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} </select>
如果你想将映射Student数据和Address数据,你可以使用id为StudentWithAddressResult的resultMap:
<select id="selectStudentWithAddress" parameterType="int" resultMap="StudentWithAddressResult"> SELECT STUD_ID, NAME, EMAIL, PHONE, A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON S.ADDR_ID=A.ADDR_ID WHERE STUD_ID=#{studId} </select>
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
STUD_ID
NAME
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文件定义如下所示:
public class Address { private Integer addrId; private String street; private String city; private String state; private String zip; private String country; // setters & getters } public class Student { private Integer studId; private String name; private String email; private PhoneNumber phone; private Address address; //setters & getters }
<resultMap type="Student" id="StudentWithAddressResult"> <id property="studId" column="stud_id" /> <result property="name" column="name" /> <result property="email" column="email" /> <result property="phone" column="phone" /> <result property="address.addrId" column="addr_id" /> <result property="address.street" column="street" /> <result property="address.city" column="city" /> <result property="address.state" column="state" /> <result property="address.zip" column="zip" /> <result property="address.country" column="country" /> </resultMap> <select id="selectStudentWithAddress" parameterType="int" resultMap="StudentWithAddressResult"> SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON S.ADDR_ID=A.ADDR_ID WHERE STUD_ID=#{studId} </select>
我们可以使用圆点记法为内嵌的对象的属性赋值。在上述的resultMap中,Student的address属性使用了圆点记法被赋上了address对应列的值。同样地,我们可以访问任意深度的内嵌对象的属性。我们可以如下访问内嵌对象属性:
//接口定义 public interface StudentMapper { Student selectStudentWithAddress(int studId); } //使用 int studId = 1; StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); Student student = studentMapper.selectStudentWithAddress(studId); System.out.println("Student :" + student); System.out.println("Address :" + student.getAddress());
上述样例展示了一对一关联映射的一种方法。然而,使用这种方式映射,如果address结果需要在其他的SELECT映射语句中映射成Address对象,我们需要为每一个语句重复这种映射关系。MyBatis提供了更好地实现一对一关联映射的方法:嵌套结果ResultMap和嵌套select查询语句。接下来,我们将讨论这两种方式。
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>
<select id="findStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,
ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
元素<association>被用来导入“有一个”(has-one)类型的关联。在上述的例子中,我们使用了<association>元素引用了另外的在同一个XML文件中定义的<resultMap>。
我们也可以使用<association定义内联的resultMap,代码如下所示:
<resultMap type="Student" id="StudentWithAddressResult"> <id property="studId" column="stud_id" /> <result property="name" column="name" /> <result property="email" column="email" /> <association property="address" javaType="Address"> <id property="addrId" column="addr_id" /> <result property="street" column="street" /> <result property="city" column="city" /> <result property="state" column="state" /> <result property="zip" column="zip" /> <result property="country" column="country" /> </association> </resultMap>
使用嵌套结果ResultMap方式,关联的数据可以通过简单的查询语句(如果需要的话,需要与joins 连接操作配合)进行加载。
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<select id="findAddressById" parameterType="int"
resultMap="AddressResult">
SELECT * FROM ADDRESSES WHERE ADDR_ID=#{id}
</select>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" column="addr_id" select="findAddressById" />
</resultMap>
<select id="findStudentWithAddress" parameterType="int"
resultMap="StudentWithAddressResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
在此方式中,<association>元素的 select属性被设置成了id为 findAddressById的语句。这里,两个分开的SQL语句将会在数据库中执行,第一个调用findStudentById加载student信息,而第二个调用findAddressById来加载address信息。
Addr_id列的值将会被作为输入参数传递给selectAddressById语句。
我们可以如下调用findStudentWithAddress映射语句:
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class); Student student = mapper.selectStudentWithAddress(studId); System.out.println(student); System.out.println(student.getAddress());
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
TUTOR_ID
NAME
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定义如下:
public class Course { private Integer courseId; private String name; private String description; private Date startDate; private Date endDate; private Integer tutorId; //setters & getters } public class Tutor { private Integer tutorId; private String name; private String email; private Address address; private List<Course> courses; / setters & getters }
现在让我们看看如何获取讲师信息以及其所教授的课程列表信息。
<collection>元素被用来将多行课程结果映射成一个课程Course对象的一个集合。和一对一映射一样,我们可以使用嵌套结果ResultMap和嵌套Select语句两种方式映射实现一对多映射。
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<collection property="courses" resultMap="CourseResult" />
</resultMap>
<select id="findTutorById" parameterType="int"
resultMap="TutorResult">
SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL, C.COURSE_ID,
C.NAME, DESCRIPTION, START_DATE, END_DATE
FROM TUTORS T LEFT OUTER JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID
LEFT OUTER JOIN COURSES C ON T.TUTOR_ID=C.TUTOR_ID
WHERE T.TUTOR_ID=#{tutorId}
</select>
这里我们使用了一个简单的使用了JOINS连接的Select语句获取讲师及其所教课程信息。<collection>元素的resultMap属性设置成了CourseResult,CourseResult包含了Course对象属性与表列名之间的映射。
<resultMap type="Course" id="CourseResult"> <id column="course_id" property="courseId" /> <result column="name" property="name" /> <result column="description" property="description" /> <result column="start_date" property="startDate" /> <result column="end_date" property="endDate" /> </resultMap> <resultMap type="Tutor" id="TutorResult"> <id column="tutor_id" property="tutorId" /> <result column="tutor_name" property="name" /> <result column="email" property="email" /> <association property="address" resultMap="AddressResult" /> <collection property="courses" column="tutor_id" select="findCoursesByTutor" /> </resultMap> <select id="findTutorById" parameterType="int" resultMap="TutorResult"> SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL FROM TUTORS T WHERE T.TUTOR_ID=#{tutorId} </select> <select id="findCoursesByTutor" parameterType="int" resultMap="CourseResult"> SELECT * FROM COURSES WHERE TUTOR_ID=#{tutorId} </select>
在这种方式中,<aossication>元素的select属性被设置为id 为findCourseByTutor的语句,用来触发单独的SQL查询加载课程信息。tutor_id这一列值将会作为输入参数传递给findCouresByTutor语句。
public interface TutorMapper { Tutor findTutorById(int tutorId); } TutorMapper mapper = sqlSession.getMapper(TutorMapper.class); Tutor tutor = mapper.findTutorById(tutorId); System.out.println(tutor); List<Course> courses = tutor.getCourses(); for (Course course : courses) { System.out.println(course); }
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>
<select id="searchCourses" parameterType="hashmap" resultMap="CourseResult"></select>
SELECT * FROM COURSES
WHERE TUTOR_ID= #{tutorId}
<if test="courseName != null">
AND NAME LIKE #{courseName}
</if>
<if test="startDate != null">
AND START_DATE >= #{startDate}
</if>
<if test="endDate != null">
AND END_DATE <= #{endDate}
</if>
</select>
public interface CourseMapper
{
List<Course> searchCourses(Map<String, Object> map);
}
public void searchCourses()
{
Map<String, Object> map = new HashMap<String, Object>();
map.put("tutorId", 1);
map.put("courseName", "%java%");
map.put("startDate", new Date());
CourseMapper mapper = sqlSession.getMapper(CourseMapper.class);
List<Course> courses = mapper.searchCourses(map);
for (Course course : courses)
{
System.out.println(course);
}
}
此处将生成查询语句 SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like? AND START_DATE >= ?。准备根据给定条件的动态SQL查询将会派上用场。
<select id="searchCourses" parameterType="hashmap" resultMap="CourseResult">
SELECT * FROM COURSES
<choose>
<when test="searchBy == 'Tutor'">
WHERE TUTOR_ID= #{tutorId}
</when>
<when test="searchBy == 'CourseName'">
WHERE name like #{courseName}
</when>
<otherwise>
WHERE TUTOR start_date >= now()
</otherwise>
</choose>
</select>
MyBatis计算<choose>测试条件的值,且使用第一个值为TRUE的子句。如果没有条件为true,则使用<otherwise>内的子句。
<select id="searchCourses" parameterType="hashmap"
resultMap="CourseResult">
SELECT * FROM COURSES
<where>
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
<if test="startDate != null">
AND start_date >= #{startDate}
</if>
<if test="endDate != null">
AND end_date <= #{endDate}
</if>
</where>
</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}。
<select id="searchCourses" parameterType="hashmap"
resultMap="CourseResult">
SELECT * FROM COURSES
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
</trim>
</select>
这里如果任意一个<if>条件为true,<trim>元素会插入WHERE,并且移除紧跟WHERE后面的AND或OR。
<select id="searchCoursesByTutors" parameterType="map"
resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
<foreach item="tutorId" collection="tutorIds">
OR tutor_id=#{tutorId}
</foreach>
</where>
</if>
</select>
public interface CourseMapper
{
List<Course> searchCoursesByTutors(Map<String, Object> map);
}
public void searchCoursesByTutors()
{
Map<String, Object> map = new HashMap<String, Object>();
List<Integer> tutorIds = new ArrayList<Integer>();
tutorIds.add(1);
tutorIds.add(3);
tutorIds.add(6);
map.put("tutorIds", tutorIds);
CourseMapper mapper =
sqlSession.getMapper(CourseMapper.class);
List<Course> courses = mapper.searchCoursesByTutors(map);
for (Course course : courses)
{
System.out.println(course);
}
}
现在让我们来看一下怎样使用<foreach>生成 IN子句:
<select id="searchCoursesByTutors" parameterType="map" resultMap="CourseResult"> SELECT * FROM COURSES <if test="tutorIds != null"> <where> tutor_id IN <foreach item="tutorId" collection="tutorIds" open="(" separator="," close=")"> #{tutorId} </foreach> </where> </if> </select>
<update id="updateStudent" parameterType="Student">
update students
<set>
<if test="name != null">name=#{name},</if>
<if test="email != null">email=#{email},</if>
<if test="phone != null">phone=#{phone},</if>
</set>
where stud_id=#{id}
</update>
这里,如果<if>条件返回了任何文本内容,<set>将会插入set关键字和其文本内容,并且会剔除将末尾的 “,”。
在上述的例子中,如果 phone!=null,<set>将会让会移除 phone=#{phone}后的逗号“,”,
生成 set phone=#{phone} 。
public enum Gender
{
FEMALE,
MALE
}
默认情况下,MyBatis使用EnumTypeHandler来处理enum类型的Java属性,并且将其存储为enum值的名称。你不需要为此做任何额外的配置。你可以可以向使用基本数据类型属性一样使用enum类型属性,代码如下:public class Student
{
private Integer id;
private String name;
private String email;
private PhoneNumber phone;
private Address address;
private Gender gender;
//setters and getters
}
<insert id="insertStudent" parameterType="Student"
useGeneratedKeys="true" keyProperty="id">
insert into students(name,email,addr_id, phone,gender)
values(#{name},#{email},#{address.addrId},#{phone},#{gender})
</insert>
当你执行insertStudent语句的时候,MyBatis会取Gender枚举(FEMALE/MALE)的名称,然后将其存储到GENDER列中。
如果你希望存储原enum的顺序位置,而不是enum名,,你需要明确地配置它。
如果你想存储FEMALE为0,MALE为1到gender列中,你需要在mybatis-config.xml文件中配置EnumOrdinalTypeHandler:
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.mybatis3.domain.Gender"/>
CREATE TABLE USER_PICS
(
ID INT(11) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(50) DEFAULT NULL,
PIC BLOB,
BIO LONGTEXT,
PRIMARY KEY (ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
这里,照片可以是PNG,JPG或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,MyBatis将CLOB类型的列映射到java.lang.String类型上、而把BLOB列映射到byte[] 类型上。
public class UserPic { private int id; private String name; private byte[] pic; private String bio; //setters & getters }
创建UserPicMapper.xml文件,配置映射语句,代码如下:
<insert id="insertUserPic" parameterType="UserPic"> INSERT INTO USER_PICS(NAME, PIC,BIO) VALUES(#{name},#{pic},#{bio}) </insert> <select id="getUserPic" parameterType="int" resultType="UserPic"> SELECT * FROM USER_PICS WHERE ID=#{id} </select>
下列的insertUserPic()展示了如何将数据插入到CLOB/BLOB类型的列上:
public void insertUserPic() { byte[] pic = null; try { File file = new File("C:\\Images\\UserImg.jpg"); InputStream is = new FileInputStream(file); pic = new byte[is.available()]; is.read(pic); is.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } String name = "UserName"; String bio = "put some lenghty bio here"; UserPic userPic = new UserPic(0, name, pic , bio); SqlSession sqlSession = MyBatisUtil.openSession(); try { UserPicMapper mapper = sqlSession.getMapper(UserPicMapper.class); mapper.insertUserPic(userPic); sqlSession.commit(); } finally { sqlSession.close(); } }
下面的getUserPic()方法展示了怎样将CLOB类型数据读取到String类型,BLOB类型数据读取成byte[]属性:
public void getUserPic() { UserPic userPic = null; SqlSession sqlSession = MyBatisUtil.openSession(); try { UserPicMapper mapper = sqlSession.getMapper(UserPicMapper.class); userPic = mapper.getUserPic(1); } finally { sqlSession.close(); } byte[] pic = userPic.getPic(); try { OutputStream os = new FileOutputStream(new File("C:\\Images\\UserImage_FromDB.jpg")); os.write(pic); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Public interface StudentMapper
{
List<Student> findAllStudentsByNameEmail(String name, String email);
}
MyBatis 支持将多个输入参数传递给映射语句,并以#{param}的语法形式引用它们:
<select id="findAllStudentsByNameEmail" resultMap="StudentResult"> select stud_id, name,email, phone from Students where name=#{param1} and email=#{param2} </select>
这里#{param1}引用第一个参数name,而#{param2}引用了第二个参数email。
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class); studentMapper.findAllStudentsByNameEmail(name, email);
<select id=" findAllStudents" resultMap="StudentResult">
select * from Students
</select>
Map<Integer, Student> studentMap =
sqlSession.selectMap("com.mybatis3.mappers.StudentMapper.findAllStudents", "studId");
这里studentMap将会将studId作为key值,而Student对象作为value值。
<select id="findAllStudents" resultMap="StudentResult"> select * from Students </select>
然后,你可以加载如下加载第一页数据(前25条):
int offset =0 , limit =25; RowBounds rowBounds = new RowBounds(offset, limit); List<Student> = studentMapper.getStudents(rowBounds);
若要展示第二页,使用offset=25,limit=25;第三页,则为offset=50,limit=25。
对于sqlSession.select()方法,我们可以传递给它一个ResultHandler的实现,它会被调用来处理ResultSet的每一条记录。
public interface ResultHandler { void handleResult(ResultContext context); }
现在然我们来看一下怎么使用ResultHandler来处理结果集ResultSet,并返回自定义化的结果。
public Map<Integer, String> getStudentIdNameMap() { final Map<Integer, String> map = new HashMap<Integer, String>(); SqlSession sqlSession = MyBatisUtil.openSession(); try { sqlSession.select("com.mybatis3.mappers.StudentMapper.findAllStude nts", new ResultHandler() { @Override public void handleResult(ResultContext context) { Student student = (Student) context.getResultObject(); map.put(student.getStudId(), student.getName()); } } ); } finally { sqlSession.close(); } return map; }
在上述的代码中,我们提供了匿名内部ResultHandler实现类,在handleResult()方法中,我们使用context.getResultObject()获取当前的result对象,即Student对象,因为我们定义了findAllStudent映射语句的resultMap=”studentResult“。对查询返回的每一行都会调用handleResult()方法,并且我们从Student对象中取出studId和name,将其放到map中。
<cache eviction="FIFO" flushInterval="60000" size="512" 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配置如下:
<select ... flushCache="false" useCache="true"/> <insert ... flushCache="true"/> <update ... flushCache="true"/> <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)处理。在下一章,我们将会讨论如何使用注解书写映射语句。
前一章:
下一章:Java Persistence with MyBatis 3(中文版) 第四章 使用注解配置SQL映射器
-------------------------------------------------------------------------------------------------------------------------------
作者声明:本文出处是http://blog.csdn.net/luanlouis,如需转载,请注明出处!
Java Persistence with MyBatis 3(中文版) 第三章 使用XML配置SQL映射器,布布扣,bubuko.com