MyBatis 真正的力量是在映射语句中。这里是奇迹发生的地方。对于所有的力量,SQL 映射的 XML 文件是相当的简单。当然如果你将它们和对等功能的 JDBC 代码来比较,你会发现映射文件节省了大约 95%的代码量。MyBatis 的构建就是聚焦于 SQL 的,使其远离于普通的方式。
SQL 映射文件有很少的几个顶级元素(按照它们应该被定义的顺序):
cache – 配置给定命名空间的缓存。
cache-ref – 从其他命名空间引用缓存配置。
resultMap – 最复杂,也是最有力量的元素,用来描述如何从数据库结果集中来加载你的对象。
parameterMap – 已经被废弃了!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除。这里不会记录。
sql – 可以重用的 SQL 块,也可以被其他语句引用。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句
1)cache
MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。MyBatis 3 中的缓存实现的很多改进都已经实现了,使得它更加强大而且易于配置。
默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:
"FIFO" //回收策略
flushInterval = "60000" //缓存刷新频率
size="512" //缓存对象个数
readOnly="true" //只读设置
type="org.mybatis.caches.ehcache.EhcacheCache"/>
字面上看就是这样。这个简单语句的效果如下:
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
可用的收回策略有:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此,所有在相同命名空间的语句正如绑定的缓存一样。语句可以修改和缓存交互的方式, 或在语句的语句的基础上使用两种简单的属性来完全排除它们。默认情况下,语句可以这样来配置:
2)sql
用来定义可重用的sql语句块。
<sql id="column">
name,species,sex
sql>
然后在sql映射中用
标签引入:
<select id="selectPetByName" parameterType="string" resultType="hashmap">
select
"column"/>
from pet
where name = #{name}
select>
这样就可以减少大量重复的sql书写工作。
3)resultMap
resultMap是mybatis中最强大最有用的配置,它可以帮助我们告别繁琐的while + resultSet.get(..) 而实现自动封装。
对于简单的sql查询,不需要配置resultMap,用hashmap来封装结果集会更好,这样会以字段名称为key,字段值为value来封装结果集:
<select id="selectAllColumn" parameterType="string" resultType="hashmap">
select "column"/> from pet where name = #{name}
select>
但有时我们需要得到一个JavaBean对象,有两种方式可以实现,一种用reusltType来制定结果类型,另一种用resultMap来映射结果集和JavaBean对象:
第一种方式:
<select id="selectOneByName" parameterType="string" resultType="com.mybatis.test.entity.Pet">
select "column"/>
from pet where name = #{name}
select>
这样mybatis就会把查询出的结果封装到Pet对象中。
第二种方式:
配置resultMapp
"com.mybatis.test.entity.Pet" id="petMap">
<result column="name" property="name" javaType="string" jdbcType="VARCHAR"/>
<result column="species" property="species" javaType="string" jdbcType="VARCHAR" typeHandler="com.mybatis.test.handler.MyStringTypeHandler"/>
<result column="sex" property="sex" typeHandler="com.mybatis.test.handler.MyStringTypeHandler"/>
在sql映射中配置resultMap:
<select id="cthSelect" resultMap="petMap">
select "column"/>
from pet
select>
实际上应用第一种配置方式时,mybatis会在幕后根据字段名称自动创建一个resultMap。若表字段名称与JavaBean中的字段名称不匹配,还可以利用sql语句的as来修改sql结果集的字段名,使之与JavaBean中的字段名相匹配:
但有的时候事情并没有这么简单,我们在很多情况下需要做一对多或多对一的联合查询,但把这些重组的字段再组合到一个新的bean对象中是不现实的,这样就需要对多个表和bean利用resultMap做联合配置,以满足实际的需求。
下面以经典的“部门”对“员工”为例,做一对多和多对一的映射配置:
Employee Bean:
private Integer id;
private String employeeName;
private Integer age;
private Department department = new Department();
Department Bean:
private Integer id;
private String departmentName;
private List employees;
一个部门对应多个员工,而一个员工属于一个部门,我们分别从部门和员工的角度出发,来做一对多和多对一的映射。
员工-部门的多对一映射:
<resultMap type="com.mybatis.test.entity.Employee" id="employeeMap">
<id property="id" column="id"/>
<result property="employeeName" column="name"/>
<result property="age" column="age"/>
<association property="department" javaType="Department">
<id property="id" column="id"/>
<result property="departmentName" column="department_name"/>
association>
resultMap>
用
来指定每一个员工多对应的部门。
部门-员工的多对一映射:
"Department" id="departmentMap">
<id property="id" column="id"/>
<result property="departmentName" column="department_name"/>
property="employees" ofType="Employee" column="dept_id">
<id property="id" column="id"/>
<result property="employeeName" column="name"/>
<result property="age" column="age"/>
利用
来制定多是一方。在配置多对一关系时,需要在
中制定关联字段,column="dept_id"
,这样mybatis就会根据这个字段来找出所有属于该部门的员工,并将其封装到集合中。
在定义resultMap时,还可以利用javaType、jdbcType和typeHandler来实现java数据类型和数据库字段类型的对应关系及其特定的操作动作,eg:
在mybatis核心配置文件中生命自定义类型处理器:
<typeHandlers>
<typeHandler javaType="string" jdbcType="VARCHAR" handler="com.mybatis.test.handler.MyStringTypeHandler"/>
typeHandlers>
自定义类型处理器:
package com.mybatis.test.handler;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
public class MyStringTypeHandler implements TypeHandler<String>{
@Override
public void setParameter(PreparedStatement ps, int i, String parameter,
JdbcType jdbcType) throws SQLException {
if("".equals(parameter) || "null".equals(parameter)
|| parameter == null || "NULL".equals(parameter)){
parameter = "blank";
}
ps.setString(i, parameter);
}
@Override
public String getResult(ResultSet rs, String columnName)
throws SQLException {
String result = rs.getString(columnName);
if("null".equals(result) || "".equals(result)
|| result == null || "NULL".equals(result)){
result = "blank";
}
return result;
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
String result = rs.getString(columnIndex);
if("null".equals(result) || "".equals(result)
|| result == null || "NULL".equals(result)){
result = "blank";
}
return result;
}
@Override
public String getResult(CallableStatement cs, int columnIndex)
throws SQLException {
String result = cs.getString(columnIndex);
if("null".equals(result) || "".equals(result)
|| result == null || "NULL".equals(result)){
result = "blank";
}
return null;
}
}
该类型处理器既可以用于设置参数时、也可以用于获取结果时对空值的处理。
在resultMap中进行配置:
"com.mybatis.test.entity.Pet" id="petMap">
<result column="name" property="name" javaType="string" jdbcType="VARCHAR"/>
<result column="species" property="species" javaType="string" jdbcType="VARCHAR" typeHandler="com.mybatis.test.handler.MyStringTypeHandler"/>
<result column="sex" property="sex" typeHandler="com.mybatis.test.handler.MyStringTypeHandler"/>
这样,如果从数据库表中查询出的结果为null时,就会用自定义类型处理器中的方式进行处理。
在设置参数的时候可以像下面这样应用自定义类型处理器对参数进行处理:
"insertNullByMyTypeHandler" parameterType="Pet">
insert into pet
(<include refid="column"/>)
values
(
#{name,jdbcType=UNDEFINED,javaType=string,handler=com.mybatis.test.handler.MyTypeHandler},
#{species},
#{sex}
)
3)insert、update、delete
这三个标签分别配置对应的sql语句,由于这三中sql都是DML,所以在用法上基本相同:
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
keyProperty=""
keyColumn=""
useGeneratedKeys=""
timeout="20">
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
id="insertAuthor"
parameterType="domain.blog.Author"
flushCache="true"
statementType="PREPARED"
timeout="20">
id:该sql语句在当前名称空间下的唯一标识符,用户调用该sql语句。
parameterType:sql语句的参数类型,可以是mybatis提供的别名(int、string、hashmap、list等),也可以是自定义类似。需要注意的是,若参数类型为hashmap或自定义bean等,在利用#{fieldName}取得参数的值时用到的名称需要与map中的key或bean中的字段名称保证一致,否则会取不到数据,如果是string或int等,则名称没有现实,只起到标识的作用。
flushCache:刷新缓存,若将该属性设置为true,则调用该sql时,会到时缓存被清空。默认为false。
statementType:设置预编译sql语句的方式,mybatis提供了三种:
STATEMENT,PREPARED,CALLABLE
默认是PREPARED类型。
timeout:设置获取数据库操作结果的最大等事件。默认不对其进行设置,有数据库取得来决定。
4)select
select语句是最常用的数据库操作,相对于DML操作,mybatis对select语句映射的支持明显更强。查询语句的特殊之处在于既有输入也有输出,下面对select语句映射做简要概述。
一个最简单的查询映射:
<select id="selectPerson" parameterType="int" resultType="hashmap">
SELECT * FROM PERSON WHERE ID = #{id}
select>
该语句按照id查询一条person记录,其中输入参数是int类型,虽然#{id}中的id与数据表中的id列名保存一致,但前面已经说过,对于基本数据类型的参数,名称没有现实,任何名字mybatis都可以取到参数的值。返回结果是hashmap,也就是Java中对应的HashMap,定义该结果类型后,mybatis会将查询结果封装到一个map对象中,key值为字段名称,value为该字段的值。
Mybatis对select标签提供了丰富的属性,以支持对select语句的更细粒度配置:
其中的本分属性前面已经介绍过,这里就不在冗述了。
useCache:配置二级缓存,当mybatis开启二级缓存时,查询有默认是应用二级缓存的,若不想将某一个DQL与二级缓存联系起来,可将该属性设置为false。
fetchSize:设置每次查询的最大返回结果,通常不设置改属性。