mybatis二级缓存在的问题?
mybatis的二级缓存是基于namespace级别的。
由于关联查询,导致不同命名空间下的缓存不能及时更新的情况。
mybatis给出的建议是:如果涉及到关联查询,尽量不要使用它自带的二级缓存。
1、Mybatis 映射文件之增删改:
(1)、Mybatis 允许增删改直接定义以下类型的返回值:
Integer、Long、Boolean、void (或其基本数据类型)
(2)、Mybatis 默认为手动提交(sqlSession.commit();)
* sqlSessionFactory.openSession();选择无参的,则需要手动提交
* sqlSessionFactory.openSession(boolean autoCommit); 选择有参的,
如果填入true,则会自动提交,默认为false,即手动提交
(3)、Mybatis 映射文件中的 parameterType 可以省略
(4)、Mybatis模糊查询的方法:
* 不要在Java代码中添加"%",影响效率
①、方式一:使用CONCAT()函数进行连接
<!--PC端分页查询活动报名人员列表-->
<select id="listApplicationPage" resultType="com.zhengtoon.ghtoon.activity.entity.Application" >
select
<include refid="applicationColumns" />
from t_application
<where>
<if test="actId != null and actId != ''">
AND act_id = #{actId}
</if>
<if test="userName != null and userName != ''">
AND user_name LIKE CONCAT('%', #{userName}, '%')
或者
AND user_name LIKE "%"#{userName}"%"
</if>
<if test="telephone != null and telephone != ''">
AND telephone LIKE CONCAT('%', #{telephone}, '%')
或者
AND telephone LIKE "%"#{telephone}"%"
</if>
</where>
ORDER BY create_time DESC
</select>
2、Mybatis 映射文件之 Insert 获取自增主键的值:
(1)、原生的JDBC 采用 statement对象的getGeneratedKeys()方法可以获取到
(2)、mysql 支持自增主键,自增主键值的获取,Mybatis 也是利用 statement的 getGeneratedKeys()方法来获取
* 需要在 insert 标签中配置 useGeneratedKeys="true",表示使用自增主键获取主键值策略,默认值为FALSE
* 还需要在 insert 标签中配置 keyProperty属性,指定对应的主键属性,也就是 Mybatis 获取到主键值以后,
将这个值封装给 javaBean的哪个属性
例:
keyProperty="id" , 即将这个值封装给javaBean的 id 属性
<insert id="addEmp" parameterType="com.zlj.mybatis.bean.Employee" useGeneratedKeys="true"
keyProperty="id">
insert into tbl_employee(last_name,email, gender) values(#{lastName}, #{email}, #{gender} )
</insert>
3、Mybatis 映射文件之 Insert 获取非自增主键的值:
(1)、Oracle 不支持自增,Oracle 使用序列来模拟自增,
每次插入的数据的主键是从序列中拿到的值,如何获取到这个值?
------ 先从selectKey 标签中查询出序列的下一个值封装到JavaBean的id属性中,
然后再把查询出来的id 值插入到数据表中
例:
<insert id="addEmp" parameterType="com.zlj.mybatis.bean.Employee" databaseId="oracle">
/*
①、keyProperty:查出的主键值封装给 JavaBean 的哪个属性
②、order属性,取值有两种 :BEFORE 和 AFTER
BEFORE :当前 sql 在插入 sql 之前执行
AFTER :当前 sql 在插入 sql 之后执行
③、resultType : 查出的数据的返回值类型
④、BEFORE 运行顺序:
* 先运行 selectKey 查询 id 的sql, 查出 id值封装给 JavaBean的id 属性
* 再运行插入的sql,就可以取出id 属性对应的值
⑤、AFTER 运行顺序:
* 先运行插入的 sql (从序列中取出新值作为 id);
* 运行 selectKey 查询 id 的 sql ,然后封装到JavaBean的id属性中
⑥、因为 AFTER 有些问题,如果一次插入好几条数据,那么使用selectKey 查询出的 id,为最后插入sql 的最后一个序列值,
所以推荐使用 BEFORE
*/
/*
BEFORE :
*/
<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
/* 编写查询主键的 sql 语句 */
select EMPLOYEE_SEQ.nextval from dual
</selectKey>
/* 插入时的主键是从序列中拿到的,即id为上面查出来后进行封装后的值 */
insert into tbl_employee(id, last_name,email, gender)
values(#{id}, #{lastName}, #{email}, #{gender} )
/*
AFTER:
*/
<!--
<selectKey keyProperty="id" order="AFTER" resultType="Integer">
/* 再从系列中查找出当前系列值,封装到JavaBean对象的id属性中 */
select EMPLOYEE_SEQ.currval from dual
</selectKey>
<!-- 先执行插入,插入时插入的 id 值直接是从系列中取出下一个序列的值插入 -->
insert into tbl_employee(id, last_name,email, gender)
values(EMPLOYEE_SEQ.nextval, #{lastName}, #{email}, #{gender} )
-->
</insert>
4、Mybatis映射文件参数处理(单个参数/多个参数/命名参数):
(1)、单个参数:Mybatis 不会做任何特殊处理
#{参数名}:取出参数值, #{}括号里面的参数名可以随意取值
(2)、多个参数:Mybatis 会做特殊处理
①、多个参数会被封装成一个 map,其中:
key : param1 ... paramN 或者参数的索引也可以,从0开始
value : 传入的参数值
②、#{}就是从 map 中获取指定的key 的值
即 #{param1} and #{param2} 或者 #{0} and #{1}
操作:
* 方法:Employee getEmpByIdAndLastName(Integer id, String lastName);
* 取值:#{id} and #{lastName}
* 抛异常:
org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are
[0, 1, param1, param2]
* 正确取值方式:
#{param1} and #{param2} 或者 #{0} and #{1}
(3)、命名参数:多个参数的情况下,可以明确指定封装参数时map 的key,多个参数会被封装成一个map
例如:Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
key:@Param 注解指定的值
value : 参数值
#{指定的key} 取出对应的参数值,即可以使用id = #{id} and last_name=#{lastName}
<!--
<select id="getEmpByIdAndLastName" resultType="emp">
select * from tbl_employee where id = #{0} and last_name=#{1}
</select>
<select id="getEmpByIdAndLastName" resultType="emp">
select * from tbl_employee where id = #{param1} and last_name=#{param2}
</select>
-->
//使用 @Param() 注解后
<select id="getEmpByIdAndLastName" resultType="emp">
select * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>
5、Mybatis 参数处理(POJO、Map、TO)
(1)、POJO:如果多个参数正好是业务逻辑的数据模型,可以直接传入 POJO
#{属性名} : 取出传入的 POJO 的属性值
(2)、Map: 如果多个参数不是业务模型中的数据,没有对应的POJO,不经常使用,为了方便,也可以传入Map
#{key}: 取出 map 中对应的值
(3)、TO:如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个To(Transfer Object)数据传输对象
6、Mybatis 参数处理 -> 参数封装扩展思考:
(1)、public Employee getEmp(@Param("id") Integer id, String lastName);
取值: id ==> #{id / param1 / 0} lastName ==> #{param2 / 1}
(2)、public Employee getEmp(Integer id, @Param("e")Employee emp);
取值:id ==> #{param1} lastName ==> #{param2.lastName / e.lastName}
(3)、特别注意,如果是 Collection (List、Set) 类型或者是数组,Mybatis也会进行特殊处理。
也是把传入的list或者数组封装在map中
key : Collection (collection) , 如果是 List 还可以使用List封装的key(list)
数组(array)
public Employee getEmpById(List<Integer> ids);
取值:取出第一个id的值:#{list[0]} 或者 #{collection[0]}
7、Mybatis 参数处理 -> # 与 $ 取值的区别:
(1)、#{} :可以获取 map 中的值或者 POJO 对象属性的值
${} : 可以获取 map 中的值或者 POJO 对象属性的值
(2)、区别:
#{} : 是以预编译的形式,将参数设置到sql语句中,占位符,就像原生JDBC中的PreparedStatement,
可以防止sql注入问题
${} : 取出的值直接拼装在sql语句中,会有安全问题;
例如: select * from tbl_employee where id=${id} and last_name=#{lastName}
Preparing : select * from tbl_employee where id=1 and last_name = ?
(3)、大多情况下,我们取参数的值都应该去使用 #{}
(4)、原生JDBC 不支持占位符的地方就可以使用${}进行取值
例如 : 分表、排序.....
按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${last_name} ${order} 等
8、Mybatis 参数处理 -> # 取值时指定参数相关规则:
(1)、规定参数的一些规则:
javaType、jdbcType、mode(存储过程)、numericScale、 resultMap、typeHandler、jdbcTypeName、
expression(未来准备支持的功能)
(2)、jdbcType 通常需要在某种特定的条件下被设置:
在执行插入操作,当数据为 null 的时候,有些数据库可能不能识别 Mybatis 对null的默认处理,比如 Oracle(报错)
jdbcType OTHER : 无效的类型,因为 Mybatis 对所有的null都映射的是原生的 jdbc 的 OTHER 类型,
Oracle不能正确处理!
(3)、由于全局配置中,jdbcTypeForNull 默认值为 OTHER, Oracle 不支持,有两种解决办法:
(1)、#{email,jdbcType=NULL}
(2)、在全局配置文件中配置jdbcTypeForNull = NULL
<setting name="jdbcTypeForNull" value="NULL"></setting>
9、Mybatis 映射文件 - select 返回 List:
resultType: 如果返回的是一个集合,要写集合中元素的类型
10、Mybatis映射文件 - select 返回 Map:
(1)、返回一条记录的 map,key 就是列名,值就是对应的值,resultType="map"
(2)、返回多条记录封装一个map:Map<Integer, Employee> 键是这条记录的主键,值时记录封装后的JavaBean对象
* @MapKey("") 注解,标注在方法上,告诉Mybatis 封装这个map 的时候使用哪个属性作为map的key
比如: @MapKey("id"),就是使用id 作为map 的key
* resultType 为 map中元素的类型
11、Mybatis映射文件 - select - resultMap 自定义结果映射规则:
(1)、resultMap: 自定义结果集映射规则,resultMap和resultType 只能选择一个
(2)、自定义结果集映射规则
* 自定义某个 JavaBean 的封装规则
type : 自定义规则的java类型, 可以使用设置的别名,也可以使用全类名
id : 唯一id , 方便后面引用
<resultMap id="myEmp" type="com.zlj.mybatis.bean.Employee">
<!--
指定主键列的封装规则
id 标签专门用来定义主键列,底层会有一定的优化
column :指定哪一列
property: 指定对应的 JavaBean 属性
-->
<id column="id" property="id"></id>
<!-- result 标签定义普通列封装规则 -->
<result column="last_name" property="lastName"></result>
<!-- 不指定的列会自动封装,我们只要写 resultMap 就把全部的映射规则都写上 -->
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
</resultMap>
<!-- resultMap: 自定义结果集映射规则 -->
<select id="getEmpById" resultMap="myEmp">
select * from tbl_employee where id = #{id}
</select>
12、Mybatis 映射文件 - select - resultMap 关联查询_级联属性封装结果:
例:
<!-- 联合查询:级联属性封装结果集 -->
<resultMap id="myDifEmp" type="com.zlj.mybatis.bean.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<result column="did" property="dept.id"></result>
<result column="dept_name" property="dept.departmentName"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="myDifEmp">
SELECT e.id id, e.last_name last_name, e.gender gender, e.email email, e.d_id d_id,d.id did,
d.dept_name dept_name FROM tbl_employee e,tbl_dept d WHERE e.d_id = d.id AND e.id = #{id}
</select>
13、Mybatis 映射文件 - select - resultMap 关联查询_association定义关联对象封装规则:
例:
<!-- 使用 association 定义关联的单个对象的封装规则 -->
<resultMap id="MyDifEmp2" type="com.zlj.mybatis.bean.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<!--
association 可以指定联合的 JavaBean 对象
property=“dept” : 指定哪个属性是联合的对象
javaType : 指定这个属性对象的类型[不能省略]
-->
<association property="dept" javaType="com.zlj.mybatis.bean.Department">
//注意:column 中不能有值重复的,否则会出错,但是property 中可以,因为属于不用的java类
<id column="did" property="id"></id>
<result column="dept_name" property="departmentName"></result>
</association>
</resultMap>
14、Mybatis 映射文件 - select - resultMap 关联查询_association分步查询:
例:
<!--
使用 association 进行分步查询:
1、先按照员工的 id 查询员工的信息;
2、根据查询员工的信息中的 d_id 值去部门表查出部门信息
3、部门信息设置到员工信息中;
-->
<resultMap id="myEmpStep" type="com.zlj.mybatis.bean.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<!--
association: 定义关联对象的封装规则
select :表明当前属性是调用 select 指定的方法查出的结果
column : 指定将哪一列的值传给这个方法
流程:使用select 指定的方法(传入column指定的这列参数的值)查出对象,并封装给property 指定的属性
-->
<association property="dept" javaType="com.zlj.mybatis.bean.Department"
select="com.zlj.mybatis.bean.dao.DepartmentMapper.getDeptById" column="d_id"></association>
</resultMap>
<select id="getEmpByIdStep" resultMap="myEmpStep">
select * from tbl_employee where id = #{id}
</select>
15、Mybatis 映射文件 - select - resultMap 关联查询_association分步查询 & 延迟加载:
(1)、在分步查询的基础上,可以使用延迟加载(懒加载 或 按需加载):
Employee ==> Dept
每次查询Employee 对象的时候,都将Dept一起查询出来,想让部门信息在使用的时候再去查询。
解决办法:在分段查询的基础之上在全局配置文件中加两个配置即可。
<!-- 开启延迟加载 -->
<!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认false -->
<setting name="lazyLoadingEnabled" value="true"></setting>
<!-- 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,默认false -->
<setting name="aggressiveLazyLoading" value="false"></setting>
16、Mybatis 映射文件 - select - resultMap 关联查询_collection定义关联集合封装规则:
例:
<!-- collection 嵌套结果集的方式,定义关联的集合类型元素的封装规则 -->
<resultMap id="myDept" type="com.zlj.mybatis.bean.Department">
<id column="did" property="id"></id>
<result column="dept_name" property="departmentName"></result>
<!--
collection :定义关联集合类型的属性的封装规则
ofType :指定集合里面元素的类型
-->
<collection property="emps" ofType="com.zlj.mybatis.bean.Employee">
<!-- 定义这个集合中元素的封装规则 -->
<id column="eid" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
</collection>
</resultMap>
<select id="getDeptAndEmpsById" resultMap="myDept">
SELECT d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email,
e.gender gender,e.d_id d_id
FROM tbl_dept d LEFT JOIN tbl_employee e ON e.d_id = d.id WHERE d.id = 1
</select>
16、Mybatis 映射文件 - select - resultMap 关联查询_collection分步查询 & 延迟加载:
(与 association 的分步查询和延迟加载一样 )
<!--
resultMap - 关联查询 - collection 分步查询 & 延迟加载
-->
<resultMap id="myDeptStep" type="com.zlj.mybatis.bean.Department">
<id column="id" property="id"></id>
<result column="dept_name" property="departmentName"></result>
<collection property="emps" select="com.zlj.mybatis.bean.dao.EmployeeMapper2.getEmpsByIdStep"
column="id">
</collection>
</resultMap>
<select id="getDeptByStep" resultMap="myDeptStep">
select id, dept_name from tbl_dept where id = #{id}
</select>
<select id="getEmpsByIdStep" resultType="com.zlj.mybatis.bean.Employee">
select * from tbl_employee where d_id = #{deptId}
</select>
17、Mybatis 映射文件 - select - resultMap 分步查询传递多列值 & fetchType:
(1)、resultMap - 分步查询传递多列值 & fetchType
扩展:将多列的值封装 map 传递 column = "{key1 = column1, key2 = column2}"
(2)、fetchType = "lazy" : 表示使用延迟加载
取值有:lazy - 延迟加载
eager - 立即加载
<resultMap id="myDeptStep" type="com.zlj.mybatis.bean.Department">
<id column="id" property="id"></id>
<result column="dept_name" property="departmentName"></result>
<collection property="emps" select="com.zlj.mybatis.bean.dao.EmployeeMapper2.getEmpsByIdStep"
column="{deptId = id}" fetchType="eager"></collection>
</resultMap>
18、Mybatis 映射文件 - select - resultMap - discriminator 鉴别器:
(1)、<discriminator javaType=""></discriminator>
鉴别器:Mybatis 可以使用 discriminator 判断某列的值,然后根据某列的值改变封装行为
例:
<!--
例:封装 Employee
如果查出的是女生,就把部门信息查询出来,否则不查询
如果查出的是男生,把last_name 这一列的值赋值给email
-->
<resultMap id="MyEmpDis" type="com.zlj.mybatis.bean.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<!--
column : 指定判断的列名
javaType : 列值对应的java 类型,可以使用别名,比如:String 的别名 Mybatis自动封装为 string
-->
<discriminator javaType="string" column="gender">
<!-- value="0" 表示女生,resultType / resultMap : 指定封装的结果类型,不能缺少 -->
<case value="0" resultType="com.zlj.mybatis.bean.Employee">
<association property="dept" javaType="com.zlj.mybatis.bean.Department"
select="com.zlj.mybatis.bean.dao.DepartmentMapper.getDeptById" column="d_id">
</association>
</case>
<!-- value="1" 表示男生 -->
<case value="1" resultType="com.zlj.mybatis.bean.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="last_name" property="email"></result>
</case>
</discriminator>
</resultMap>
五、Mybatis 动态 sql
1、Mybatis 动态SQL - if 判断 & OGNL:
(1)、动态SQL是Mybatis 强大特性之一,极大的简化了我们拼装SQL的操作。
(2) 、Mybatis 采用功能强大的基于 OGNL 的表达式来简化操作。
* if
* choose(when, otherwise)
* trim(where, set)
* foreach
(3)、OGNL(Object Graph Navigation Language) 对象图导航语言,这是一种强大的表达式语言,通过它可以
非常方便的来操作对象属性。类似于EL、SpEL等.
* 访问对象属性:person.name
* 调用方法:person.getName()
* 调用静态属性/方法: @java.lang.Math@PI
@java.util.UUID@random.UUID()
* 调用构造方法: new com.zlj.bean.Person("admin").name
* 运算符: +、-、*、/、%
* 逻辑运算符: in、not、in、>、>=、<、<=、==、!=
注意:xml 中特殊符号如 "、> 、 < 等这些都需要使用转义字符
* 访问集合伪属性:
类型 伪属性 伪属性对应的 java 方法
List、Set、Map size、isEmpty List/Set/Map.size(),List/Set/Map.isEmpty()
List、Set iterator List/Set.iterator
Map keys、values Map.keySet(),Map.values()
Iterator next、hasNext Iterator.next(),Iterator.hasNext()
(4)、查询的时候如果某些条件没带可能sql拼装会有问题
* 给 where 后面加上 1 = 1, 以后的条件都 and xxx
* Mybatis 使用 where 标签来将所有的查询条件包括在内。Mybatis就会将where 标签中拼装的sql,多出来的 and或者or 去掉。
注意:where 标签只会去掉第一个多出来的 and 或者 or
例:
<!-- 查询员工,要求携带了哪个字段查询条件就带上这个字段的值 -->
<select id="getEmpsByConditionIf" resultType="com.zlj.mybatis.bean.Employee">
select * from tbl_employee
<!--
1、 test : 判断表达式,使用的是 OGNL
2、OGNL 查看官方文档
3、从参数中取值进行判断
4、遇见特殊符号应该去写转义字符,例如:双引号"" --> "" 、 and --> &&
-->
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="lastName != null && lastName != """>
and last_name like #{lastName}
</if>
<if test="email != null && email.trim() != """>
and email = #{email}
</if>
<!-- OGNL会进行字符串与数字的转换判断 "0" == 0 -->
<if test="gender == 0 or gender == 1">
and gender = #{gender}
</if>
</where>
</select>
2、Mybatis 动态SQL - trim 自定义字符串截取:
(1)、trim标签:后面多出的and或者or,where标签不能解决,所以可以使用trim标签来解决
* prefix="" 前缀,trim标签体中是整个字符串拼串后的结果
prefix 给拼串后的整个字符串加一个前缀
* prefixOverrides="" 前缀覆盖,去掉整个字符串前面多余的字符
* suffixOverrides="" 后缀覆盖,去掉整个字符串后面多余的字符
* suffix="" 后缀,suffix给拼串后的整个字符串加一个后缀
例:
<select id="getEmpsByConditionTrim" resultType="com.zlj.mybatis.bean.Employee">
select * from tbl_employee
<!-- 自定义字符串的截取规则 -->
<trim prefix="where" suffixOverrides="and">
<if test="id != null">
id = #{id} and
</if>
<if test="lastName != null && lastName != """>
last_name like #{lastName} and
</if>
<if test="email != null && email.trim() != """>
email = #{email} and
</if>
<!-- OGNL会进行字符串与数字的转换判断 "0" == 0 -->
<if test="gender == 0 or gender == 1">
gender = #{gender}
</if>
</trim>
</select>
3、Mybatis 动态SQL - choose 分支选择:
例:
<select id="getEmpsByConditionChoose" resultType="com.zlj.mybatis.bean.Employee">
select * from tbl_employee
<where>
<choose>
<when test="id != null" >
id = #{id}
</when>
<when test="lastName != null">
last_name like #{lastName}
</when>
<when test="email != null">
email = #{email}
</when>
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>
4、Mybatis 动态SQL - set 与 if 结合的动态更新 :
例:
<update id="updateEmployee">
<!-- set 标签的使用 -->
update tbl_employee
<set>
<if test="lastName != null">
last_name = #{lastName},
</if>
<if test="email != null">
email != #{email},
</if>
<if test="gender != null">
gender = #{gender}
</if>
</set>
where id = #{id}
<!-- Trim 更新拼串
update tbl_employee
<trim prefix="set" suffixOverrides=",">
<if test="lastName != null">
last_name = #{lastName},
</if>
<if test="email != null">
email != #{email},
</if>
<if test="gender != null">
gender = #{gender}
</if>
</trim>
where id = #{id}
-->
</update>
5、Mybatis 动态SQL - foreach 遍历集合 :
* collection :指定要遍历的集合。 list类型的参数会特殊处理封装在map中,map的key 就叫list
也可以是使用@Param("")在方法的入参上指定别名,则collection="指定的别名"
例:List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);则collection="ids"
* item :将当前遍历出的元素赋值给指定的变量
* separator: 每个元素之间的分隔符
* open :遍历出所有结果拼接一个开始的字符
* close :遍历出所有的结果拼接一个结束的字符
* index :索引。遍历list的时候index 就是索引,item 就是当前值
遍历map的时候index 表示的就是map的key,item就是map的值
* #{变量名} 就能取出变量的值,也就是当前遍历出的元素
例:
<select id="getEmpsByConditionForeach" resultType="com.zlj.mybatis.bean.Employee">
select * from tbl_employee
<foreach collection="list" item="id" separator=", " open="where id in (" close=")">
#{id}
</foreach>
</select>
6、Mybatis 动态SQL - foreach - mysql 下 foreach批量插入的两种方式 :
(1)、方式一:
MYSQL下的批量保存,可以 foreach遍历,mysql 支持 insert into 表名 values(), (), () 等语法
<insert id="addEmps">
insert into tbl_employee(last_name, email, gender, d_id) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.dept.id})
</foreach>
</insert>
(2)、方式二: 循环遍历整条insert语句,每条insert语句之间用;隔开
这种分号分隔多个sql可以用于其他批量操作(删除,修改等)
注意:mysql 默认不支持用;分隔多条语句,所以需要设置数据库连接属性allowMultiQueries=true
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into tbl_employee(last_name, email, gender, d_id)
values (#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.dept.id})
</foreach>
</insert>
7、Mybatis 动态SQL - foreach - Oracle下 foreach批量保存的两种方式 :
(1)、Oracle 不支持 values(), (), () 这种语法
(2)、Oracle 支持的批量方式:
方式一:多个insert放在 begin - end 里面
begin
insert into employees(employee_id,last_name,email,gender)
values(employees_seq.nextval,"aa","aa@163.com","1");
insert into tbl_employee(employee_id,last_name,email,gender)
values(employees_seq.nextval,"aa","aa@163.com","1");
end;
方式二:利用中间表
insert into employees(employee_id,last_name,email,gender)
select employees_seq.nextval,lastName,email,gender from (
select "aa" lastName,"aa@163.com" email,"1" gender) from dual
union
select "bb" lastName,"bb@163.com" email,"1" gender) from dual
)
例:
<insert id="addEmps">
<!-- Oracle 第一种批量方式
<foreach collection="emps" item="emp" open="begin" close="end;">
insert into employees(employee_id,last_name,email,gender)
values(employees_seq.nextval,#{emp.lastName},#{emp.email},#{emp.gender});
</foreach>
-->
<!-- Oracle 第二种批量方式 -->
insert into employees(employee_id,last_name,email,gender)
<foreach collection="emps" item="emp" open=" select employees_seq.nextval,lastName,email,gender from ("
close=")" separator="union">
select #{emp.lastName} lastName,#{emp.email} email,#{emp.gender} gender) from dual
</foreach>
</insert>
8、Mybatis 动态SQL - 内置参数 _parameter & _databaseId :
不只是方法传递过来的参数可以被用来判断,取值。Mybatis默认还有两个内置参数:
(1)、_parameter:代表整个参数
单个参数:_parameter 就是这个参数
多个参数:参数会被封装为一个map, _parameter就是代表这个map
(2)、_databaseId : 如果配置了 databaseIdProvider 标签,_databaseId就是代表当前数据库的别名 oracle/ mysql
例:
<select id="getEmpsTestInnerParameter" resultType="com.zlj.mybatis.bean.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
<!-- _parameter 代表的是 getEmpsTestInnerParameter 方法参数中的 Employee 对象 -->
<if test="_parameter!=null">
where last_name = #{_parameter.lastName}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from employees
<if test="_parameter != null">
where last_name = #{_parameter.lastName}
</if>
</if>
</select>
9、Mybatis 动态SQL - bind 绑定 :
(1)、bind : 可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值
例:
<select id="getEmpsTestInnerParameter" resultType="com.zlj.mybatis.bean.Employee">
<!-- 给 lastName 左右添加 %,然后把值赋给 _lastName , 即把Value 的值 赋给 name -->
<bind name="_lastName" value="'%' + lastName + '%'"></bind>
<if test="_databaseId=='mysql'">
select * from tbl_employee
<!-- _parameter 代表的是 getEmpsTestInnerParameter 方法参数中的 Employee 对象 -->
<if test="_parameter!=null">
where last_name like #{_lastName}
</if>
</if>
</select>
10、Mybatis 动态SQL - sql标签 抽取可重用的sql片段 :
抽取可重用的sql片段,方便后面引用。
(1)、sql标签抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
(2)、include 标签来引用已经抽取的sql
(3)、include还可以自定义一些 property,sql标签内部就能使用自定义的属性
取值的正确方式:${prop}
#{} 不能使用这种方式取值
例 :
<sql id="insertColumn">
<if test="_databaseId=='mysql'">
last_name, email, gender, d_id
<!-- last_name, email, gender, d_id, ${testColumn} -->
</if>
<if test="_databaseId=='oracle'">
last_name, email, gender
</if>
</sql>
<!-- 使用sql标签抽取 -->
<insert id="addEmps">
insert into tbl_employee(
<include refid="insertColumn">
<!-- <property name="testCplumn" value="abc"></property> -->
</include>
) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.lastName}, #{emp.email}, #{emp.gender}, #{emp.dept.id})
</foreach>
</insert>
六、Mybatis两级缓存
(1)、一级缓存(本地缓存):sqlSession 级别的缓存。一级缓存是一直开启的;
Mybatis 一级缓存其实是sqlSession级别的一个Map.
* 与数据库同一次会话期间查询到的数据会放在本地缓存中。
* 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
(2)、Mybatis 一级缓存失效的四种情况(没有使用到当前一级缓存的情况,效果就是还需要再次向数据库发送sql查询)
1、sqlSession 不同,导致一级缓存失效;
2、sqlSession相同,但是查询条件不同,导致一级缓存失效。(当前一级缓存中还没有这个数据)
3、sqlSession相同,但是两次查询之间执行了增删改操作,导致一级缓存失效。(这次增删改可能对当前数据有影响)
4、sqlSession相同,但是手动清除了一级缓存(缓存清空),也会导致一级缓存失效。
注意:一级缓存(本地缓存)不能被关闭,但可以调用clearCache()来清空本地缓存,或者改变缓存的作用域。
(3)、二级缓存(全局缓存):基于namespace级别的缓存,一个namespace对应一个二级缓存。
工作机制:
1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
2、如果会话关闭,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息,就可以参照二级缓存中的内容
3、sqlSession ===> EmployeeMapper ===> Employee
DepartmentMapper ===> Department
不同的namespace 查出的数据会放在自己对应的缓存中(即map中,因为Mybatis的缓存就是一个map)
(4)、二级缓存的使用步骤:
1、开启全局二级缓存,在全局配置文件中配置:
<!-- 开启二级缓存, 默认为true -->
<setting name="cacheEnabled" value="true"></setting>
2、去mapper.xml 中配置使用二级缓存:
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
3、为了安全,可能会用到序列化和反序列化,所以POJO需要实现序列化接口
4、二级缓存,全局作用域缓存,默认不开启,需要手动配置。
注意:查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中 即 :二级缓存在SqlSession 关闭或者提交之后才会生效。
(5)、缓存有关的设置以及属性:
1、 <setting name="cacheEnabled" value="true"></setting>中的cacheEnabled,默认为true,开启缓存。
如果设置为false,则关闭缓存,这里关闭的是二级缓存,一级缓存还可以一直使用。
2、每个select 标签都有 useCache 属性,默认为true,使用缓存。
如果设置为false,则表示不使用缓存(一级缓存还可以使用,二级缓存不能使用)
3、每个增删改标签都有一个 flushCache 属性,默认为true,清除缓存,这里一级缓存,二级缓存都被清除。
增删改执行完成后都会清除缓存,一二级缓存都被清除。
* 查询标签也有 flushCache 属性,默认为false,不清除缓存。
如果设置为true,则每次查询之后都会清空缓存,一二级缓存都不能使用
4、sqlSession.clearCache(); 只是清除当前session的一级缓存。对二级缓存没有影响
5、localCacheScope : 本地缓存作用域(Mybatis3.3版本之后出现)。可以影响一级缓存,不影响二级缓存。
默认值为 SESSION,一级缓存session,这种情况下会缓存一个会话中执行的所有查询。
若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
(可以禁用一级缓存)
(6)、缓存原理图:
[图片上传失败...(image-68ad87-1531473301917)]
* 缓存的使用顺序:
先去查找二级缓存,如果二级缓存没有数据,再去一级缓存中查询,如果还没有,才去数据库中查
(7)、Mybatis 整个第三方缓存(Ehcache):
1、导入第三方缓存整合的jar包;
2、导入与第三方缓存整合的适配包,可以去官当查找,官当有好多与第三方缓存整合的适配包;
3、Ehcache 要想正常运行,需要在配置文件中加入cache.xml配置文件;
4、mapper.xml 中使用自定义的缓存,type中引入自定义缓存的全类名
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
5、如果其他mapper.xml也使用和该mapper.xml 中一样的第三方缓存,则可以如下配置:
<!-- 引用EmployeeMapper.xml中使用的自定义第三方缓存 Ehcache
namespace : 指定和哪个名称空间下的缓存一样
-->
<cache-ref namespace="com.zlj.mybatis.dao.EmployeeMapper"></cache-ref>
七、Mybatis 批量 BatchExecutor & 与Spring整合时配置批量SqlSession :
注意:批量操作只有执行 sqlSession.commit();才会发送sql语句到数据库执行
(1)、Mybatis 批量 BatchExecutor :
/*
测试 Mybatis 的批量操作
*/
public class BatchTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String source = "mybatis-config.xml";
InputStream stream = Resources.getResourceAsStream(source);
return new SqlSessionFactoryBuilder().build(stream);
}
@Test
public void test() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
/*
使用 openSession(ExecutorType execType) 这个带ExecutorType参数的方法,
传入ExecutorType.BATCH参数,获取到的sqlSession就可以执行批量操作
*/
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
long start = System.currentTimeMillis();
for(int i = 0; i < 1000; i++) {
mapper.addEmp(new Employee("java", "java@qq.com", "1"));
}
sqlSession.commit();
long end = System.currentTimeMillis();
/*
批量操作:(预编译sql 执行一次) ==> (设置参数执行 1000次) ==> 执行sql(一次) 用时 1190 毫秒
Preparing: insert into tbl_employee(last_name,email, gender) values( ?, ?, ? ) java:145
Parameters: java(String), java@qq.com(String), 1(String)
Parameters: java(String), java@qq.com(String), 1(String)
....
非批量操作:每次执行都是 预编译sql ==> 设置参数执行 ==> 执行sql,循环执行1000次 用时 2865 毫秒
Preparing: insert into tbl_employee(last_name,email, gender) values( ?, ?, ? )
Parameters: java(String), java@qq.com(String), 1(String) java:145
Updates: 1 java:145
*/
System.out.println("批量操作一共用时 : " + (end - start));
} finally {
sqlSession.close();
}
}
}
(2)、Mybatis与Spring整合时配置批量SqlSession :
* 在ApplicationContext.xml配置文件中配置:
<!--配置一个可以进行批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
* 然后在service 中通过@Autowired注入可以执行批量操作的SqlSession ,使用该SqlSession 就可以执行批量操作
七、Mybatis自定义类型转换器
1、自定义类型转换器,需要实现 TypeHandler 接口,或者继承 BaseTypeHandler
2、配置自定义的类型转换器(两种方式):
* 在全局配置文件中进行配置;
* 在指定的字段上进行配置
<typeHandlers>
<!-- 1、配置自定义的 TypeHandler -->
<typeHandler handler="com.zlj.mybatis.typeHandler_enum.MyEnumEmpStatusTypeHandler" javaType="com.zlj.mybatis.bean.EmpStatus"></typeHandler>
<!--
2、也可以在处理某个字段的时候告诉Mybatis用什么类型的处理器
保存时:在传入参数时,做如下操作: #{empStatus, typeHandler = “自定义类型处理器的全类名”}
查询时:定义一个 resultMap ,在resultMap 的指定字段中指定即可
<resultMap type="com.zlj.mybatis.bean.Employee" id="PageEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="empStatus" property="empStatus" typeHandler="自定义类型处理器的全类名"/>
</resultMap>
-->
<!-- 使用Mybatis自带的类型转换器 -->
<!-- <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.zlj.mybatis.bean.EmpStatus"></typeHandler>-->
</typeHandlers>
3、测试
/*
* Mybatis 默认在处理枚举对象的时候,保存的是枚举的名字,使用的是 EnumTypeHandler处理
* 如果想改变这个设置,可以使用 EnumOrdinalTypeHandler 来处理,这样Mybatis在处理枚举对象的时候,保存的是枚举的索引
* 需要在全局配置文件中配置:
<typeHandlers>
<!-- 自定义枚举处理器 -->
<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="com.zlj.mybatis.bean.EmpStatus"></typeHandler>
</typeHandlers>
*/
@Test
public void test() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = new Employee("lastName_enum", "enum@qq.com","0");
mapper.addEmp(employee);
sqlSession.commit();
System.out.println("保存成功 : " + employee.getId());
} finally {
sqlSession.close();
}
}
网友评论