语句映射
MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
在每个顶级元素标签中可以添加很多个属性,下面我们开始详细了解下具体的配置。
1、参数
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
<mapper 根节点
namespace 命名空间,一般情况下, 一个mapper映射文件对应一个不同的命名空间,利于管理和维护
书写:默认情况下可以随意输入,但是如果使用接口绑定的方式就必须要输入对应的接口的完整限定名
-->
<mapper namespace="cn.test.mapper.EmpMapper"></mapper>
1.1 参数介绍
id
同一个命名空间只能有一个唯一的id,除非有不同的databaseId,可以被用来引用这条语句。如果是接口绑定的一定要保证接口和方法名相同
statementType
- STATEMENT 代表jdbc的statement 不支持参数解析,不会设置
- PREPARED 代表jdbc的PreparedStatement 支持参数解析, 默认的。
- CALLABLE 代表jdbc的CallableStatement 执行存储过程
获取插入后的自动增长的主键(mysql 和 SQL Server )
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user(user_name) values(#{userName})
</insert>
数据库不支持自增的话,那么可以使用如下的方式进行赋值查询,在新增之前查询最大的id
<insert id="insertUser2" >
<!--
order BEFORE|AFTER 设置在增删改之前或之后运行
keyProperty 将当前查询结果放到哪个pojo属性中
resultType 返回的数据类型
-->
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select max(id)+1 from user
</selectKey>
insert into user(id,user_name) values(#{id},#{userName})
</insert>
获取插入后的自动增长的主键(mysql 和 SQL Server )
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into user(user_name) values(#{userName})
</insert>
1.2 参数处理
(1) 取值方式
在xml文件中编写sql语句的时候有两种取值的方式,分别是#{}和${}
1) #{} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id=?"
- 会经过JDBC当中PreparedStatement的预编译,会根据不同的数据类型来编译成对应数据库所对应的数据。
- 能够有效的防止SQL注入。 推荐使用!!
特殊用法:
- javaType、jdbcType、mode、numericScale、resultMap、typeHandler.
- 需要改变默认的NULL:OTHER:#{id,javaType=NULL}
- 想保留小数点后两位:#{id,numericScale=2}
2) ${} ==> jdbc String sql=" SELECT id,user_name FROM EMP WHERE id="+id
- 不会进行预编译,会直接将输入进来的数据拼接在SQL中。
- 存在SQL注入的风险。不推荐使用。
特殊用法:
- 调试情况下可以临时使用。
- 实现一些特殊功能:前提一定要保证数据的安全性。
- 比如:动态表、动态列. 动态SQL.
<select id="SelectEmp" resultMap="cn.test.pojo.Emp" >
SELECT id,user_name,create_date FROM EMP where id=#{id}
</select>
(2) 传递方式
1)单个参数:SelectEmp(Integer id);
mybatis 不会做任何特殊要求
获取方式:#:{输入任何字符获取参数}
2)多个参数:Emp SelectEmp(Integer id,String username);
- mybatis 会将传进来的参数封装成map,1个值就会对应2个map项:
- id===> {key:arg0 ,value:id的值},{key:param1 ,value:id的值}
- username===> {key:arg1 ,value:id的值},{key:param2 ,value:id的值}
- 获取方式:
- 没使用@Param:这种方式参数名没有意义
- id=====> #{arg0} 或者 #{param1}
- username=====> #{arg1} 或者 #{param2}
- 使用@Param: 设置参数的别名:SelectEmp(@Param("id") Integer id,@Param("username") String username);
- id=====> #{id} 或者 #{param1}
- username=====> #{username} 或者 #{param2}
3)javaBean的参数
- 单个参数:Emp SelectEmp(Emp emp);
- 获取方式:可以直接使用属性名
- emp.id=====>#{id}
- emp.username=====>#{username}
- 多个参数:Emp SelectEmp(Integer num,Emp emp);
- num===> #{param1} 或者 @Param
- emp===> emp.id===> #{param2.id} 或者 @Param("emp")Emp emp ====>#{emp.id}
- emp.username===> #{param2.username} 或者 @Param("emp")Emp emp ====>#{emp.username}
4)集合或者数组参数:
- 如果是list,MyBatis会自动封装为map:{key:"list":value:usernames}
- 没用@Param
- usernames.get(0) =====> #{list[0]}
- usernames.get(0) =====> #{agr0[0]}
- 有@Param("usernames")
- usernames.get(0) =====> #{usernames[0]}
- usernames.get(0) =====> #{param1[0]}
- 如果是数组,MyBatis会自动封装为map:{key:"array":value:usernames}
- 没用@Param:
- usernames.get(0) =====> #{array[0]}
- usernames.get(0) =====> #{agr0[0]}
- 有@Param("usernames")
- usernames.get(0) =====> #{usernames[0]}
- usernames.get(0) =====> #{param1[0]}
5)map参数:和javaBean的参数传递是一样。
请求进来的参数 和pojo对应,就用pojo
请求进来的参数 没有和pojo对应,就用map
请求进来的参数 没有和pojo对应上,但是使用频率很高,就用TO、DTO(就是单独为这些参数创建一个对应的javaBean出来,使参数传递更规范、更重用)
2、返回结果处理
2.1 参数介绍
id
在命名空间中唯一的标识符,可以被用来引用这条语句。
resultType
期望从这条语句中返回结果的全限定类名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap
对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
2.2 处理集合返回结果
(1)当返回值的结果是集合的时候,返回值的类型依然写的是集合中具体的类型
<select id="selectAllEmp" resultType="cn.test.bean.Emp">
select * from emp
</select>
(2)在查询的时候可以设置返回值的类型为map,当mybatis查询完成之后会把列的名称作为key、列的值作为value,转换到map中
<select id="selectEmpByEmpReturnMap" resultType="map">
select * from emp where empno = #{empno}
</select>
(3)当返回的结果是一个集合对象的是,返回值的类型一定要写集合具体value的类型,同时在dao的方法上要添加@MapKey的注解,来设置key是什么结果
@MapKey("empno")
Map<Integer,Emp> getAllEmpReturnMap();<select id="getAllEmpReturnMap" resultType="cn.test.bean.Emp">
select * from emp
</select>
2.3 自定义结果集
(1)声明resultMap自定义结果集
<!--
resultType 和 resultMap 只能使用一个
type 需要映射的pojo对象, 可以设置别名
autoMapping 自动映射,(默认=true) 只要字段名和属性名遵循映射规则就可以自动映射,但是不建议,哪怕属性名和字段名一一对应上了也要显示的配置映射
-->
<resultMap id="common_map" type="emp" autoMapping="false" >
<!--
<id>主键必须使用 对底层存储有性能作用
column 需要映射的数据库字段名
property 需要映射的pojo属性名
-->
<id column="id" property="id"></id>
<result column="user_name" property="username"></result>
</resultMap>
(2)使用resultMap 关联 自定义结果集的id
<select id="SelectEmp" resultType="Emp" resultMap="emp_map" >
SELECT id,user_name,create_date FROM EMP where id=#{id}
</select>
(3)extends 如果多个resultMap有重复映射,可以声明父resultMap,将公共的映射提取出来, 可以减少子resultMap的映射冗余
<resultMap id="emp_map" type="emp" autoMapping="false" extends="common_map">
<result column="create_date" property="cjsj"></result>
</resultMap>
2.4 高级结果映射
(1)联合查询
1.直接返回map。通过map接受参数
<select id="QueryEmp" resultMap="map"></select>
2.通过实体类映射
1)定义类QueryEmpDTO,在自定义结果集中添加新映射关系
public class QueryEmpDTO {
private String deptName;
private Integer deptId;
private Integer id;
private String username;
}
2)定义resultMap
<resultMap id="QueryEmp_Map" type="QueryEmpDTO">
<id column="e_id" property="id"></id>
<result column="user_name" property="username"></result>
<result column="d_id" property="deptId"></result>
<result column="dept_name" property="deptName"></result>
</resultMap>
3.查询
<select id="QueryEmp" resultMap="QueryEmp_Map">
select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
INNER JOIN dept t2 on t1.dept_id=t2.id
where t1.id=#{id}
</select>
(2)嵌套结果
1.多对一
1)实体类
public class Emp {
private Integer id;
private String username;
private LocalDate createDate;
private Dept dept;
}
2)返回映射
<resultMap id="QueryEmp_Map2" type="Emp">
<id column="e_id" property="id"></id>
<result column="user_name" property="username"></result>
<!--
association 实现多对一中的 “一”
property 指定对象中的嵌套对象属性
-->
<association property="dept">
<id column="d_id" property="id"></id>
<id column="dept_name" property="deptName"></id>
</association>
</resultMap>
3)查询语句
<select id="QueryEmp2" resultMap="QueryEmp_Map2">
select t1.id as e_id,t1.user_name,t2.id as d_id,t2.dept_name from emp t1
INNER JOIN dept t2 on t1.dept_id=t2.id
where t1.id=#{id}
</select>
2.一对多
1)实体类
public class Dept {
private Integer id;
private String deptName;
private List<Emp> emps;
}
2)返回映射
<resultMap id="SelectDeptAndEmpsMap" type="Dept">
<id column="d_id" property="id"></id>
<id column="dept_name" property="deptName"></id>
<!--
<collection 映射一对多中的 “多”
property 指定需要映射的“多”的属性,一般声明为List
ofType 需要指定list的类型
-->
<collection property="emps" ofType="Emp" >
<id column="e_id" property="id"></id>
<result column="user_name" property="username"></result>
<result column="create_date" property="createDate"></result>
</collection>
</resultMap>
3)查询语句
<select id="SelectDeptAndEmps" resultMap="SelectDeptAndEmpsMap">
select t1.id as d_id,t1.dept_name,t2.id e_id,t2.user_name,t2.create_date from dept t1
LEFT JOIN emp t2 on t1.id=t2.dept_id
where t1.id=#{id}
</select>
(3)嵌套查询
1.多对一
1)返回映射
<resultMap id="QueryEmp_Map3" type="Emp">
<id column="id" property="id"></id>
<result column="user_name" property="username"></result>
<!-- association 实现多对一中的 “一”
property 指定对象中的嵌套对象属性
column 指定将哪个字段传到分步查询中
select 指定分步查询的 命名空间+ID
以上3个属性是实现分步查询必须的属性
fetchType 可选, eager|lazy eager立即加载 lazy跟随全局配置文件中的lazyLoadingEnabled
-->
<association property="dept" fetchType="eager" column="dept_id" select="cn.test.mapper.DeptMapper.SelectDept">
</association>
</resultMap>
2)查询语句
EmpMapper.xml
<select id="QueryEmp3" resultMap="QueryEmp_Map3">
select * from emp where id=#{id}
</select>
DeptMapper.xml
<select id="SelectDept" resultType="dept">
SELECT * FROM dept where id=#{id}
</select>
2.一对多
1)返回映射
<resultMap id="SelectDeptAndEmpsMap2" type="Dept">
<id column="d_id" property="id"></id>
<id column="dept_name" property="deptName"></id>
<!--
<collection 映射一对多中的 “多”
property 指定需要映射的“多”的属性,一般声明为List
ofType 需要指定list的类型
column 需要将当前查询的字段传递到异步查询的参数
select 指定异步查询
-->
<collection property="emps" ofType="Emp" column="id" select="cn.test.mapper.EmpMapper.SelectEmpByDeptId" >
</collection>
</resultMap>
2)查询语句
DeptMapper.xml
<select id="SelectDeptAndEmps2" resultMap="SelectDeptAndEmpsMap2">
SELECT * FROM dept where id=#{id}
</select>
EmpMapper.xml
<select id="SelectEmpByDeptId" resultType="emp">
select * from emp where dept_id=#{id}
</select>
(4)延迟查询
当我们在进行表关联的时候,有可能在查询结果的时候不需要关联对象的属性值,那么此时可以通过延迟加载来实现功能。延迟加载是与嵌套查询配合使用的
mybatis-config.xml
<!-- 开启延迟加载,所有分步查询都是懒加载 (默认是立即加载)-->
<setting name="lazyLoadingEnabled" value="true"/>
!--当开启时, 使用pojo中任意属性都会加载延迟查询 ,默认是false-->
<setting name="aggressiveLazyLoading" value="false"/>
<!--设置对象的哪些方法调用会加载延迟查询 默认:equals,clone,hashCode,toString-->
<setting name="lazyLoadTriggerMethods" value=""/>
如果设置了全局加载,但是希望在某一个sql语句查询的时候不使用延时策略,可以添加fetchType属性 eager 立即加载/lazy 懒加载
<association property="dept" fetchType="eager" column="dept_id" select="cn.test.mapper.DeptMapper.SelectDept"></association>
2.5 动态sql
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。
在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
(1)引用
<!-- sql 定义sql片段,id 唯一标识 -->
<sql id="SelectEmp">
SELECT ${columns} FROM emp
</sql>
<!--
<include 在SQL中引用SQL片段片段
refid 需要引用的SQL片段的id
<property 声明变量, 就可以在SQL片段中动态调用,让不同的SQL调用同一个SQL片段达到不同的功能
name 变量名
value 变量值
一般情况使用${}在sql片段中引用.一旦引用了,一定保证每个include都声明了该变量
-->
<select id="QueryEmp" resultType="Emp">
<include refid="SelectEmp">
<property name="columns" value="id,dept_id"/>
</include>
<!--另一种用法-->
<include refid="SelectEmp">
<property name="tableName" value="emp"/>
</include>
</select>
(2)if 判断
<select id="getEmpByCondition" resultType="cn.test.bean.Emp">
select * from emp where
<if test="empno!=null">
empno > #{empno}
</if>
<if test="ename!=null">
and ename like #{ename}
</if>
<if test="sal!=null">
and sal > #{sal}
</if>
</select>
看起来测试是比较正常的,但是大家需要注意的是如果我们传入的参数值有缺失会怎么呢?这个时候拼接的sql语句就会变得有问题,例如不传参数或者丢失第一个参数,那么语句中就会多一个where或者and的关键字,因此在mybatis中也给出了具体的解决方案
(3)where
一般会加载动态条件中配合使用, 在有条件的情况下它会自动在所有条件的前面加上WHERE关键字, 还会去掉所有条件前面的AND/OR
<select id="getEmpByCondition" resultType="cn.test.bean.Emp">
select * from emp
<where>
<if test="empno!=null">
empno > #{empno}
</if>
<if test="ename!=null">
and ename like #{ename}
</if>
<if test="sal!=null">
and sal > #{sal}
</if>
</where>
</select>
现在看起来没有什么问题了,但是我们的条件添加到了拼接sql语句的前后,那么我们该如何处理呢
(4)trim
<!--
trim截取字符串:
prefix:在所有包含的SQL前面加上指定的字符串
suffix 在所有包含的SQL后面加上指定的字符串
prefixOverrides 在所有包含的SQL前面加上去除指定的字符串
prefixOverrides 在所有包含的SQL后面加上去除指定的字符串
-->
<select id="getEmpByCondition" resultType="cn.test.bean.Emp">
select * from emp
<trim prefix="where" prefixOverrides="and" suffixOverrides="and">
<if test="empno!=null">
empno > #{empno} and
</if>
<if test="ename!=null">
ename like #{ename} and
</if>
<if test="sal!=null">
sal > #{sal} and
</if>
</trim>
</select>
通过以上方法有时候也解决不了我们的问题,那么有一个简单的办法,那就是加一个永远都成立的条件(比如:1=1) , 后面条件都加上and/or就行
<select id="getEmpByCondition" resultType="cn.test.bean.Emp">
select * from emp where 1=1
<if test="empno!=null">
and empno > #{empno}
</if>
<if test="ename!=null">
and ename like #{ename}
</if>
<if test="sal!=null">
and sal > #{sal}
</if>
</select>
(5)foreach
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)
<!--
实现in 范围查询 使用$可以实现但是有sql注入风险
collection 需要循环的集合的参数名字
item 每次循环使用的接收变量
separator 分割符设置(每次循环在结尾添加什么分隔符,会自动去除最后一个结尾的分隔符)
open 循环开始添加的字符串
close 循环结束添加的字符串
index 循环的下标的变量
-->
<select id="getEmpByDeptnos" resultType="Emp">
select * from emp where deptno in
<foreach collection="deptnos" open="(" close=")" index="idx" item="deptno" separator=",">
#{deptno}
</foreach>
</select>
另外一种用法
<select id="QueryEmp3" resultType="Emp">
select * from emp
<where>
<foreach collection="usernames" item="username" separator="," open=" user_name in (" close=")" index="i">
#{username}
</foreach>
</where>
</select>
(6)choose、when、otherwise
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="getEmpByConditionChoose" resultType="cn.test.bean.Emp">
select * from emp
<where>
<choose>
<when test="empno!=null">
empno > #{empno}
</when>
<when test="ename!=null">
ename like #{ename}
</when>
<when test="sal!=null">
sal > #{sal}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
(7)set
用在update语句上面的,会自动加上set关键字,会自动去除最后一个更新字段的
<update id="update">
update emp
第一种
<trim prefix="set" suffixOverrides=",">
<if test="username!=null and username!='' ">
user_name=#{username},
</if>
<if test="createDate!=null and createDate!='' ">
create_date=#{createDate},
</if>
<if test="deptId!=null and deptId!='' ">
dept_id=#{deptId}
</if>
</trim>
第二种
<set>
<if test="username!=null and username!='' ">
user_name=#{username},
</if>
<if test="createDate!=null">
create_date=#{createDate},
</if>
<if test="deptId!=null and deptId!='' ">
dept_id=#{deptId},
</if>
</set>
where id = #{id}
</update>
(8)bind
<!--
<bind> 允许在 OGNL 表达式以外创建一个变量,在Mapper映射文件上下文声明一个变量
name 变量名称
value 值(支持OGNL表达式)
-->
<select id="QueryEmp4" resultType="Emp">
<bind name="_username" value="'%'+username+'%' "/>
<include refid="SelectEmp">
<property name="columns" value="id"/>
</include>
where user_name like #{_username}
</select>
MyBatis常用OGNL表达式
e1 or e2
e1 and e2
e1 == e2,e1 eq e2
e1 != e2,e1 neq e2
e1 lt e2:小于
e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
e1 in e2
e1 not in e2
e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
!e,not e:非,求反
e.method(args)调用对象方法
e.property对象属性值
e1[ e2 ]按索引取值,List,数组和Map
@class@method(args)调用类的静态方法
@class@field调用类的静态字段值