基于 XML 配置映射器
关系型数据库和 SQL 是经受时间考研和验证的数据存储机制。和其他的 ORM 框架如 Hibernate 不同,MyBatis 鼓励开发者考研直接使用数据库,而不是将其对开发者隐蔽,因为这样考研充分发挥数据库服务器所提供的 SQL 语句的巨大威力。与此同时,MyBatis 消除了书写大量冗余代码的痛苦,它让使用 SQL 更容易。
在代码里直接嵌套 SQL 语句是很差的编码实践,并且维护起来困难。MyBatis 使用了映射器配置文件或注解来配置 SQL 语句。在以下的学习中,我们会看到具体怎样使用映射器配置文件来配置映射器 SQL 语句。
映射器配置文件和映射器接口
在 xx.mybatis3.mappers 包中的 StudentMapper.xml 配置文件内,配置 id 为 “findStudentById” 的 SQL 语句:
<?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="xx/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("xx.mybatis3.mappers.StudentMapper.findStudentById",studId);
return student;
} finally {
sqlSession.close();
}
}
INSERT 语句
MyBatis 提供了多种元素来配置不同类型的语句,如 SELECT,INSERT,UPDATE,DELETE。接下来让我们看看如何具体配置映射语句:
INSERT 语句:
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,PHONE) VALUES(#{studId},#{name},#{email},#{phone})
</insert>
在上述的 INSERT 语句中,我们为可以自动生成(auto-generated)主键的列 STUD_ID 插入值。我们可以使用 useGeneratedKeys 和 keyProperty 属性让数据库生成 auto_increment 列的值,并将生成的值设置到其中一个输入对象属性内,如下所示:
<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 属性上。
有些数据库如 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
</seleectKey>
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,PHONE) VALUES (#{studId},#{name},#{email},#{phone})
</insert>
这里我们使用了 <selectKey>子元素来生成主键值,并将值保存到 Student 对象的 studId 属性上。属性 order=“before” 表示 Mybatis 将取得序列的下一个值作为主键值,并且在执行 INSERT SQL 语句之前将值设置到 studId 属性上。
UPDATE 语句
<update id="updateStudent" parameterType="Student">
UPDATE STUDENTS SET NAME=#{name},EMAIL=#{email},PHONE=#{phone} WHERE STUD_ID = #{studId}
</update>
DELETE 语句
<delete id="deleteStudent" parameterType="int">
DELETE FROM STUDENTS WHERE STUD_ID = #{studId}
</delete>
SELECT 语句
MyBatis 真正强大的功能,在于映射 SELECT 查询结果 JavaBeans 方面的极大灵活性。
<select id="findStudentById" parameterType="int" resultType="Student">
SELECT STUD_ID,NAME,EMAIL,PHONE FROM STUDENTS WHERE STUD_ID = #{studId}
</select>
如果检查 Student 对象的属性值,你会发现 studId 属性值并没有被 stud_id 列值填充。这是因为 MyBatis 自动对 JavaBean 中属性名匹配的别名:
SELECT STUD_ID AS STUDID
SQL 语句
通过 SQL 标签定义的语句可以被其他语句通过 include 进行引用。在不同引用情况下可以传递不同的参数,例如:
<sql id="userColumns">${alias}.id,${alias}.username,${alias}.password</sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1 left join some_table t2 on t1.id=t2.id
</select>
Sql 标签也可以被嵌套使用,如:
SQL语句
<sql id="sometable">
${prefix} Table
</sql>
<sql id="someinclude">
from <include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select field1,field2,field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
结果集映射 ResultMap
ResultMap 被用来将 SQL SELECT 语句的结果集映射到 JavaBeans 的属性中。我们可以定义结果集映射 ResultMap 并且在一些 SELECT 语句上引用 resultMap。MyBatis 的结果集映射 ResultMap 特性非常强大,可以使用它降简单的 SELECT 语句映射到复杂的一对一和一对多关系的 SELECT 语句上。
一个简单的 ResultMap:
<resultMap id="StudentResult" type="xx.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>
resultType 和 resultMap 二者只能用其一,不能同时使用。
将结果集映射到 Map
1、sqlMapper.xml 文件的配置
<select id="findStduentById" parameterType="int" resultType="map">
2、Mapper 接口方法定义:
Map<String,Object> findStudentById(Integer stuId);
如果 resultType="map",返回多行结果,则方法中的返回值类型为 List<Map<String,Obejct>>
拓展 ResultMap
我们可以从另外一个<resultMap>,拓展出一个新的<resultMap>,这样,原先的属性映射可以继承过来
<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>
<resultNap 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" />
</resultMap>
一对一映射
每一个学生都有一个与之关联的地址信息。表 STUDENTS 有一个 ADDR_ID 列,是 ADDRESSES 表的外键。
public class Address{
private Integer addrId;
private String street;
private String city;
private String state;
private String zip;
private String country;
}
public class Student{
private Integer studId;
private String name;
private String email;
private String phone;
private Address address;
}
<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.cuontry" 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 对应列的值。同样地,我们可以访问任意深度的内嵌对象的属性。
使用一个嵌套结果 ResultMap 方式来获取 Student 及其 Address 信息:
<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="rmail" />
<association property="address" resultMap="AddressResult" />
</resultMap>
元素<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>
可以通过使用嵌套 select 查询来获取 Student 及其 Address 信息:
<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 信息。
一对多映射
使用<collection>元素将一对多类型的结果映射到一个对象集合上。
和一对一映射一样,我们可以使用嵌套结果 ResultMap 和嵌套 Select 语句两种方式映射实现一对多映射。
嵌套结果 ResultMap:
<collection property="course" resultMap="CourseResult" />
嵌套 Select 语句:
<collection property="courses" column="tutor_id" select="findCoursesByTutor" />