Dao接口:
public employee getEmpById(Integer id) ;
mapper映射SQL:
Dao接口:
public List
getEmpsByName(String name) ;
mapper映射SQL:
Dao接口:(返回一条记录的map,key就是列明,值就是对应的值)
public Map
getEmpsByIdReturnMap(Integer id) ;
mapper映射SQL:
多条记录封装成一个Map:要求:键是记录的主键id,值是employee对象。
Dao接口:(返回一条记录的map,key就是列明,值就是对应的值)
@MapKey("id")
//告诉Mybatis封装这个map的时候使用id作为map主键
public MapgetEmpsByNameReturnMap(String name) ;
mapper映射SQL:
是对数据库返回的结果集进行自定义映射,结果集过滤、控制等操作的动作标签。
在MyBatis中,当SQL中的字段名与java中定义的bean对象字段名对不上的时候,可以直接在MyBatis全局映射文件中设置setting标签进行修改,从而实现字段映射匹配。
今天我们在学习一种解决方式,通过自动义resultMap,实现高级结果集的映射。
resultMap
自定义结果集映射规则,是对数据库返回的结果集进行自定义映射,数据过滤的操作。用于自定义javaBean的封装规则,方于mapper映射文件的下
属性:
type:自定义规则的java类型
id:唯一id,方便引用
Dao接口:
public employee getEmpById(Integer id) ;
mapper映射文件和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="Dao.IEmpDao" >
<!--自定义resultMap:id为resultMap标识, type为bean类名或别名 -->
<resultMap type="com.xiaolang.employee" id="MyEmp">
<!--指定主键列的封装规则
id:使用<id>标签来定义主键列,MyBatis底层有优化。
column:指定哪一列(oracle或mySSQL列)
property:指定对应的javaBean属性
-->
<id column="id" property="id" javaType="int" jdbcType="INTEGER"/>
<!--指定封装last_name非主键列 -->
<result column="last_name" property="lastName" javaType="string" jdbcType="CHAR"/>
<!--当然,其他未指定的列MyBatis会自动封装,但是更推荐全部映射规则都写上 -->
<result column="email" property="email" javaType="string" jdbcType="CHAR"/>
<result column="gender" property="gender" javaType="string" jdbcType="CHAR"/>
</resultMap>
<!--配置查询所有用户的SQL -->
<select id="getEmpById" resultMap="MyEmp">
select * from employee where id=#{id}
</select>
</mapper>
注:
(1)resultMap和resultType只能存在一个
(2)很多高级查询中会用到resultMap
最后测试,发现数据库字段与javaBean字段可以正常映射。
需求:如果一个员工对应一个部门,那么在查询员工的同时如何关联查询出部门信息?
方式一:使用级联属性的方式
方式二:新建一个实体关联类(项目开发中常用)
假如两个entity没有在不修改原DO属性的时候可以这样做:
3、userMapper 文件写映射
4、写一个测试方法:
测试结果:
association定义关联对象封装
association可以指定联合的javaBean对象
--property = “dept”,指定User中的属性dept是联合对象
--javaType = “entity.Dept”,指定属性对象的类型,不能省略不写
--select:表明当前属性是调用select指定的方法查出结果
--column:指定将那一列的值传给这个方法
使用association定义关联的单个对象的封装规则:
修改Mapper映射文件:
直接测试即可。
现在有一个需求,如果也有一个接口是getDeptById(Integer id)根据部门id来查部门信息,此时在不重写SQL的情况下想要查询员工信息和部门信>息该怎么做。
association可以指定联合的javaBean对象
--property = “dept”,指定属性dept是联合对象
--javaType = “entity.Dept”,指定属性对象的类型,不能省略不写
--select:表明当前属性是调用select指定的方法查出结果
--column:指定将那一列的值传给这个方法
分析:先查根据emp的id查询该员工所在部门,然后根据dept_id再去查询dept表。也可以使用association进行分布查询,此时在只需要修改Mapper映射文件:
这种方法的实质是执行了两句查询SQL来完成的。
需求:在association分段查询中,我们每次查询employee对象的时候,都会将部门信息一起查出来。现在有个需求要实现需要部门信息的时候显示部门信息,不需要部门信息的时候只显示员工信息,该怎么做?
答案:可以使用延迟加载
懒加载的配置在MyBatis全局配置文件中设置。在以上使用association分别查询的基础上配置懒加载:
配置之后就会发现不会再执行查询部门信息的SQL。
场景:根据部门id查询部门信息,并将该部门下的所有员工信息显示出来
知识:
resultMap中的collection标签用来定义关联集合类型的属性封装规则
--ofType:指定集合里面的元素类型
(1)deptBean增加属性:
(2)dept接口增加方法:
(3)MyBatis全局变量配置文件:
(4)deptMapper映射文件:
(5)测试:
结果:
知识:
resultMap中的collection标签用来定义关联集合类型的属性封装规则
--ofType:指定集合里面的元素类型
--select:指定调用的接口SQL
--column:执行SQL后显示的字段名
(1)EmpDao新增:
(2)增加EmpMapper文件SQL:
(3)deptMapper文件中修改:
(4)测试:
结果:
collection的延迟加载需要设置:
除此之外,只需要打印出想要的详细就可以使用延迟加载,如:
只打印部门名称:
只执行了一个SQL:
无论是association还是collection以上演示的都是只传递一个参数的情况,如果是多个参数,需要怎么传递呢?
知识:
涉及多值传递的时候,MyBatis会将多列值封装成map很传递:
column=“{key1=column1,key2=column2}”
其中key1为SQL中的条件参数,column1为SQL中的变量
discriminator鉴别器也是用于执行完SQL之后,对数据库返回的结果集MyBatis进行结果集处理或控制的一个语法
鉴别器:myBatis可以使用discriminator判断某列的值,然后根据某列的值改变封装行为
场景:查询员工信息,如果查询的是女生,就把部门信息查询出来,如果是男生,把last_name这一列的值赋值给phone
Mapper文件中的resultMap和SQL这样写即可:
<resultMap id="empMapDis" type="com.xiaolang.bean.Emp">
<id column="id" property="id" />
<result column="last_name" property="last_name" />
<result column="gender" property="gender" />
<result column="phone" property="phone" />
<!--
discriminator
column:指定需要判断的列名
javaType:列名对应的java类型
-->
<discriminator javaType="string" column="gender">
<!--如果是女生,查询出employee和部门信息-->
<case value="0" resultType="com.xiaolang.bean.Emp">
<association property="dept"
select="com.xiaolang.dao.IDeptDao.getDeptById"
column="dept_id"
>
</association>
</case>
<!--如果是男生,部门信息不查询,且把last_name的值赋值给phone-->
<case value="1" resultType="com.xiaolang.bean.Emp">
<id column="id" property="id" />
<result column="last_name" property="last_name" />
<result column="gender" property="gender" />
<result column="last_name" property="phone" />
</case>
</discriminator>
</resultMap>
<select id="getEmpByIdStep" resultMap="empMapDis">
select * from emp where id=#{id}
</select>
主要解决的是对数据库返回的结果集进行字段自定义映射,结果集过滤、结果集控制等操作。
优点:1、减少了SQL代码的编写和修改频率
2、降低了数据库字段与Bean字段映射要求
3、一定程度上可以减少与数据库的交互频率
缺点:resultMap配置复杂,知识冗余,容错率低,对于不熟悉resultMap配置的童鞋来说增加了开发或维护难度。
那么接下来 一起来学习下动态SQL,对于不熟悉resultMap配置但是擅长编写SQL的人来说就说一大福利。
动态SQL:灵活的根据想要查询的结果集去执行不同的SQL,本质上和resultMap解决的是同一个问题。区别在于,resultMap注重对返回的结果集进行处理,而动态SQL注重对想要查询的目标结果灵活的分析,进行SQL的灵活拼装,再去执行SQL。
动态SQL使用的是OGNL语法,有点像JSP的EL表达式
常使用的动态SQL标签:
if
判断
choose(when,otherwise)
分支选择
trim(where,set)
字符串截取
foreach
循环
在动态SQL中遇到特殊字段,如:< 、> 、&等
需要写成转移字符,如:<
、>
标签常常作为拼装SQL的判断条件,配合OGNL语法使用。
场景:根据员工多个不同的查询条件查询用员工信息
(1)IEmpDao中新增查询接口:
//根据组合条件查询员工信息
public List<Emp> queryEmps(Emp employee);
(2)EmpMapper的映射文件可以这样写:
<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
select * from emp A
where 1=1
<if test="id!=null">
and id=#{id}
</if>
<if test="last_name !=null and last_name != '' ">
<!--因为特殊字符在xml中需要写成转义字符,所以不能这样写
<if test="last_name !=null && last_name != "" ">
可以这样写:
<if test="last_name !=null && last_name != "" ">
-->
and last_name = #{last_name}
</if>
<if test="gender != 0 or gender != 1 ">
and gender = #{gender}
</if>
<if test="phone != null and phone != '' ">
and phone = #{phone}
</if>
</select>
测试即可。
在以上例子中,我们使用了where 1=1
和后面的查询条件来拼装,避免了没有id的情况下SQL拼接出错。
下面我们使用第二种方案,使用where标签来将所有的包括在里面。myBatis就会将where标签中拼装的SQL,条件前面多出来的and或者or去掉。
知识:
where
用于将where标签中的内容封装成一个条件
如下:
<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
select * from emp A
<where>
<if test="id!=null">
and id=#{id}
</if>
<if test="last_name !=null and last_name != '' ">
and last_name = #{last_name}
</if>
<if test="gender != null and gender != '' ">
and gender = #{gender}
</if>
<if test="phone != null and phone != '' ">
and phone = #{phone}
</if>
</where>
</select>
测试即可。
如果2.2的案例动态SQL这样写
<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
select * from emp A
<where>
<if test="id!=null">
id=#{id} and
</if>
<if test="last_name !=null and last_name != '' ">
last_name = #{last_name} and
</if>
<if test="gender != null and gender != '' ">
gender = #{gender} and
</if>
<if test="phone != null and phone != '' ">
phone = #{phone}
</if>
</where>
</select>
如果phone不给值,或为null,那么此时执行的SQL是这样的:
此时我们发现,如果and或or在条件后面,此时我们是无法解决问题的。那么我们如何解决这个问题呢?
知识:
prefix
:前缀。trim标签体中整个字符串拼串后的结果加上前缀。
prefixOverrides
:前缀覆盖。去掉整个字符串前面多余的字符
suffix
:后缀。给拼接后的整个字符串加一个后缀
suffixOverrides
:后缀覆盖。去掉整个字符串后面多余的字
需要使用trim解决上述问题,需要给整个字符串加上where作为前缀,然后使用去除整个字符串后悔and即可,所以可以这样写:
<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
select * from emp A
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
and id=#{id}
</if>
<if test="last_name !=null and last_name != '' ">
and last_name = #{last_name}
</if>
<if test="gender != null and gender != '' ">
and gender = #{gender}
</if>
<if test="phone != null and phone != '' ">
and phone = #{phone}
</if>
</trim>
</select>
动态SQL中的choose分支选择(when…otherwise)相当于java中switch…case语法。只会进入一个分支的语法。
场景:如果查询条件里面有id就按照id去查询,没有id就按照姓名去查,如果都没有就按照其他条件去查询。
代码:
<select id="queryEmps" resultType="com.xiaolang.bean.Emp">
select * from emp A
<where>
<choose>
<when test="id != null">
id = #{id}
</when>
<when test="last_name != null">
last_name = #{last_name}
</when>
<otherwise>
gender = #{gender} and phone = #{phone}
</otherwise>
</choose>
</where>
</select>
知识:
标签是将标签中的语句封装成一个句子。
场景:根据员工id更新员工信息。
动态SQL为:
<update id="updateEmp">
update emp
set
<if test="last_name !=null">
last_name = #{last_name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="phone != null">
phone = #{phone}
</if>
where id = #{id}
</update>
当传值,phone为null时候,此时发现执行SQL之后的结果为:
为了解决以上条件后面多出来的“,”号,我们可以使用set标签。
如下:
<update id="updateEmp">
update emp
<set>
<if test="last_name !=null and last_name != '' ">
last_name = #{last_name},
</if>
<if test="gender != null and gender != '' ">
gender = #{gender},
</if>
<if test="phone != null and phone != '' ">
phone = #{phone}
</if>
</set>
where id = #{id}
</update>
这样即可解决问题,当然,也可以使用trim来进行操作:
<update id="updateEmp">
update emp
<trim prefix="set" suffixOverrides=",">
<if test="last_name !=null and last_name != '' ">
last_name = #{last_name},
</if>
<if test="gender != null and gender != '' ">
gender = #{gender},
</if>
<if test="phone != null and phone != '' ">
phone = #{phone}
</if>
</trim>
where id = #{id}
</update>
知识:
--------------collection
:指定要遍历的集合。list类型参数会特殊处理封装在map中,map的key就叫list
--------------item
:将当前遍历的元素赋值给当前变量
--------------separator
:每个元素之间的分隔符
--------------open
:遍历出所有结果,在结果头部拼接一个开始的字符
--------------close
:遍历出所有结果,在结果尾部拼接一个开始的字符
--------------index
:索引。遍历list的时候是索引,item就是当前值。遍历map的时候表示的是map的key,item就是map的值。
例:
IEmpDao接口:
//查询指定id的员工信息
public List<Emp> queryEmpInfo(@Param("list_ids") List<Integer> ids);
EmpMapper:
<select id="queryEmpInfo" resultType="com.xiaolang.bean.Emp">
select * from emp where id in
<foreach collection="list_ids" item="item_id"
separator="," open="(" close=")">
#{item_id}
</foreach>
</select>
测试:
结果:
注意:foreanch 标签中的 collection的值要对应mapper.java中的@param(“”) 里面的值。
MySQL数据库支持插入两条或两条以上的SQL为:
insert into emp(id, last_name, gender, phone, dept_id) values(104,"皮城女警",'0',12346784908,2) , (105,"德邦总管",'1',123462108,4)
那么如何使用foreach批量插入数据呢?
方法一:
(1)IEmpDao:
//批量新增员工
public void insertEmps(@Param("emp_list")List<Emp> emps);
(2)Mapper:
<insert id="insertEmps">
insert into emp(id, last_name, gender, phone, dept_id)
values
<foreach collection="emp_list" item="emp_item" separator=",">
(
#{emp_item.id}, #{emp_item.last_name}, #{emp_item.gender},
#{emp_item.phone}, #{emp_item.dept.dept_id}
)
</foreach>
</insert>
方式二:
Mapper的Sql这样写:
<insert id="insertEmps">
<foreach collection="emp_list" item="emp_item" separator=";">
insert into emp(id, last_name, gender, phone, dept_id)
values
(
#{emp_item.id}, #{emp_item.last_name}, #{emp_item.gender},
#{emp_item.phone}, #{emp_item.dept.dept_id}
)
</foreach>
</insert>
更推荐使用第一种写法
同样,foreach可以用于批量修改,批量删除。
Oracle数据库不支持下面语法:
insert into emp(id, last_name, gender, phone, dept_id) values(104,"皮城女警",'0',12346784908,2) , (105,"德邦总管",'1',123462108,4)
但是Oracle支持这样的语法:
begin
insert into emp(id, last_name, gender, phone) values(104,"皮城女警",'0',12346784908,2);
insert into emp(id, last_name, gender, phone) values(105,"德邦总管",'1',123462108);
end;
所以,方式一:
<insert id="insertEmpsBeach">
begin
<foreach collection="emp_list" item="emp_item">
insert into emp(id, last_name, gender, phone)
values(#{emp_seq.nextval}, #{emp_item.last_name}, #{emp_item.gender}, #{emp_item.phone});
</foreach>
end
</insert>
当然也可以这样:
<insert id="insertEmpsBeach">
<foreach collection="emp_list" item="emp_item" open="begin" close="end">
insert into emp(id, last_name, gender, phone)
values(#{emp_seq.nextval}, #{emp_item.last_name}, #{emp_item.gender}, #{emp_item.phone});
</foreach>
</insert>
Oracle批量插入数据方式二:利用中间表(这种方式比较麻烦)
Oracle还支持这样写:
insert into emp(id, last_name, phone)
select emp_seq.nextval, last_name, phone from (
select '张三' last_name, '13678705044' phone from dual
union
select '李四' last_name, '18678705044' phone from dual
union
select '李四' last_name, '18678705044' phone from dual
union
)
所以,Mapper中的SQL这样写:
<insert id="insertEmpsBeach">
insert into emp(id, last_name, phone)
select emp_seq.nextval, last_name, phone from (
<foreach collection="emp_list" item="emp_item" separator="union">
select #{emp_item.last_name} last_name, #{emp_item.phone} phone from dual
</foreach>
)
</insert>
在动态SQL中,不只是方法传递过来的参数可以用来判断和取值。
myBatis默认还有两个内置参数:
1、_parameter:代表整个参数
单个参数:_parameter代表的就是参数本身
多个参数:参数会被封装成一个map,_parameter就代表这个map
2、_datanaseId:如果在MyBatis全局配置里面配置了databaseIdProvider标签
_datanaseId就是代表当前数据库的别名
如图:
那么我们可以根据这个默认参数作为判断条件,只写一个动态SQL:
<select id="queryEmp" resultType="com.xiaolang.bean.Emp">
<if test="_databaseId == mysql">
select * from emp
<if test="_parameter != null">
where id = #{id}
<!--也可以这样写 where id = #{_parameter.id} -->
</if>
</if>
<if test="_databaseId == oracle">
select * from emp where id = #{id}
</if>
</select>
知识:bind绑定,可以将OGNL表达式的值 绑定到一个变量中,方便后来引用这个变量值
场景:现在有一个模糊查询,想要在SQL这样这样输出
select * from emp where last_name like '%e%'
但是last_name是变量,我们如何来写SQL。
方案一:where last_name like '% #{last_name}%'
错误
方案二:where last_name like '% ${last_name}%'
正确,但是不安全
方案三:使用bind标签
正确,如下:
知识:
(1)标签用于定义重复使用的SQL代码段
(2)标签用于引用重复使用的片段
注:sql
标签内也可以写等标签
、
用途:用于经常复用的sql代码片段
例如:
原SQL为:
insert into emp(id, last_name, phone)
values(#{id}, #{last_name}, #{phone})
抽出复用的SQL代码:
<sql id="insertColumn">
<if test="_databaseId == mysql">
id, last_name, phone
</if>
<if test="_databaseId == oracle">
id, last_name, email
</if>
</sql>
复用在其他SQL中:
<insert id="insertEmps">
insert into emp( <include refid="insertColumn"></include> )
values
<foreach collection="emp_list" item="emp_item" separator=",">
(
#{emp_item.id}, #{emp_item.last_name},#{emp_item.phone}
)
</foreach>
</insert>
include还可以自定义属性,例如:
<sql id="insertColumn">
<if test="_databaseId == mysql">
id, last_name, phone, ${birthday}
</if>
<if test="_databaseId == oracle">
id, last_name, email
</if>
</sql>