文章目录
- 一、Hello MyBatis
- 1.1 流程
- 1.2 总结
- 二、Crud 的一些注意点
- 三、参数传递
- 3.1 #{ } VS ${ }
- 3.2 单、复参数传递
- (1)单参数
- (2)多参数 -- @Param
- (3)总结
- 四、查询结果返回--结果封装
- 4.1 @ResultType 一般返回
- 4.2 @ResultMap 使用自定义映射规则
- 4.3 总结,最佳实践
- 五、自定义结果集
- ▽ 关联关系
- 5.1 关联查询
- (1) 一对一 ^【association】^
- (2) 一对多^【collection】^
- 5.2 分布查询
- (1)原生分布查询
- (2)MyBatis自动分布查询
- (3)延迟查询^(延迟加载)^
- 六、动态SQL语句
- 6.1 < if >标签
- 6.2 < where >标签
- 6.3 < set >标签
- 6.4 < trim >标签^(自定义截串规则)^
- (1)内部属性
- (2)对where标签的替换:
- (3)对于set标签的替换
- 6.5 choose--when--otherwise 标签
- 6.6 < foreach > 标签
- (1)内部属性
- (2)基本使用:遍历插入/查询
- (3)基本使用:遍历更新/删除
- ▽ 是否使用多sql一起发送
- ▽ 可重复字段
- 6.7 总结
- ▽ XML文件的转义字符
- 七、缓存机制
- 6.1 什么是缓存机制
- 6.2 MyBatis的缓存机制
- 八、插件机制
- 8.1 插件拦截
- 8.2 应用:PageHelper 分页插件
- (1)基本使用
- (2)进阶使用^(前后端请求响应交互)^
一、Hello MyBatis
1.1 流程
-
导入MyBatis、配置数据库
-
创建Bean组件
@Data
public class Employee {
Integer id;
String name;
Integer age;
Double salary;
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
- 创建Dao层接口(在MyBatis中文件目录写成mapper),并标识@Mapper
@Mapper
public interface EmpMapper {
Employee getEmpById(Integer id);
}
- 通过IDEA的插件创建resources目录中的mapper.xml,xml文件中的select、update…方法会被MyBatis自动通过代理对象生成对应的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="com.syq.mybatis02.dao.EmpMapper">
<select id="getEmpById" resultType="com.syq.mybatis02.bean.Employee">
select id, emp_name as name, age, emp_salary as salary from t_emp where id = #{id}
</select>
</mapper>
注意:
- 数据库查询的字段和bean对象的名字是否一样,如果不一样需要使用as 把查询到的数据库的字段名改掉
- 注意xml文件中的方法和mapper接口的方法名、返回类型、(参数类型)是否一样,
- **在 [application.properties]中配置对应的mapper.xml **
mybatis.mapper-locations=classpath:mapper/**.xml
- 实际使用
@SpringBootTest
class MyBatis02ApplicationTests {
@Autowired
EmpMapper empMapper;// 如果配置错误那么会显示无法注入
@Test
void contextLoads() {
Employee empById = empMapper.getEmpById(1);
System.out.println("emp:"+empById);
}
}
1.2 总结
- MyBatis是个半自动的Dao工具,配置比较复杂但后续使用还算方便,一定要认真检查配置
- MyBatis的底层是接口的代理实现,实际使用的也是代理出来的对象和方法,当配置有误时–>代理没有正常创建,就无法通过接口的多态性注入到容器中
二、Crud 的一些注意点
-
打开sql日志
Mybatis默认在运行时,会根据xml文件中的sql生成sql,如果想要看自动生成的sql是什么样的可以开启sql日志,会在控制台中显示
application.properties中:
mybatis.mapper-locations=classpath:mapper/**.xml
-
获取数据库自增字段信息(自增信息回填)
对于自增的数据库属性,比如传入一个Employee对象后、把数据库中自动生成的值传入原Employee对象的对应属性
- useGeneratedKeys 使用生成的键 ture/false
- keyProperty 键属性 对应属性
mapper.xml中:
<insert id="addEmp" parameterType="com.syq.mybatis02.bean.Employee" useGeneratedKeys="true" keyProperty="id"><!-- 这里 --> insert into t_emp(emp_name, age, emp_salary) values (#{name}, #{age}, #{salary}) </insert>
-
**查询所有或者查询多个 **
在JDBC中需要把Dao方法返回类型设置为List,但是在MyBatis中的,mapper.xml直接使用Bean接收就行了,会自动封装成List
@Mapper public interface EmpMapper { List<Employee> getAllEmp(); }
<select id="getAllEmp" resultType="com.syq.mybatis02.bean.Employee"> select id, emp_name as name, age, emp_salary as salary from t_emp </select>
-
开启驼峰命名与_命名自动转换
sql中的组合词以_分割,java中使用驼峰,开启后自动转换
mybatis.configuration.map-underscore-to-camel-case=true
三、参数传递
sql语句需要一定灵活性,所以sql一些参数使用#{ }或者${ }传递
3.1 #{ } VS ${ }
他们都可以用于参数传递,但是仍有区别
-
#{ }
本质上就是预编译,将空位留给参数输入
- 不会有sql注入问题
- 只能传递属性的具体值–>比如age=18
-
${ }
本质上是字符串拼接,直接把参数放到sql中
-
有sql注入问题
- 可以通过工具类先判断是否有sql注入风险,然后使用if限定
-
除了值传递的功能以外,还可以传递类型–>比如指定具体的表名、字段名
-
3.2 单、复参数传递
(1)单参数
例如:
-
根据List< Integer >数组,通过第2个id查询
Employee getEmpByIds(List<Integer> ids);
<select id="getEmpByIds" resultType="com.syq.mybatis02.bean.Employee"> select id, emp_name as name, age, emp_salary as salary from t_emp where id in=#{ids{1}}<!-- /索引从0开始 /只有一个容器时ids可以随便写比如abcd{1} --> </select>
-
根据对象查询
Employee getEmpByEmp(Employee e);
<select id="getEmpByEmp" resultType="com.syq.mybatis02.bean.Employee"> select id, emp_name as name, age, emp_salary as salary from t_emp where id = #{id} <!-- 不用写e.id,如果写了相当于:有一个e的属性,e里面还有一个id属性 --> </select>
此时,在mapper.xml配置中不一定要使用对应的形参名,因为只有一个参数,MyBatis自动会给匹配上
但是,如果使用**@Param(“e”) 精确指定了参数名为e,则必须使用 e.属性** 来调用
-
根据Map内容查询
Employee geEmpByMap(Map<String,Integer> map);
<select id="geEmpByMap" resultType="com.syq.mybatis02.bean.Employee"> select id, emp_name as name, age, emp_salary as salary from t_emp where id = #{id} </select>
(2)多参数 – @Param
List<Employee> getEmpAaa(
@Param("id")Integer id,
@Param("name") List<String> names,
@Param("e") Employee employee
);
<select id="getEmpAaa" resultType="com.syq.mybatis02.bean.Employee">
select id, emp_name as name, age, emp_salary as salary from t_emp where id = #{id} or emp_name = #{name} or emp_salary = #{e.salary}
</select>
- 多参数时必须在mapper接口使用@Param指定参数名,xml中sql引入参数时也必须使用对应的参数名
(3)总结
- 最佳实践:
无论是一个参数还是多个参数,都使用@Param 标识
- 区别:
传参形式 | 示例 | 取值方式 |
---|---|---|
单个参数 - 普通类型 | getEmploy(Long id) | #{变量名} |
单个参数 - List类型 | getEmploy(List id) | #{变量名[0]} |
单个参数 - 对象类型 | addEmploy(Employ e) | #{对象中属性名} |
单个参数 - Map类型 | addEmploy(Map<String,Object> m) | #{map中属性名} |
多个参数 - 无@Param | getEmploy(Long id,String name) | #{变量名} //新版兼容 |
多个参数 - 有@Param | getEmploy(@Param(“id”)Long id, @Param(“name”)String name) | #{param指定的名} |
扩展: | getEmploy(@Param(“id”)Long id, @Param(“ext”)Map<String,Object> m, @Param(“ids”)List ids, @Param(“emp”)Employ e) | #{id}、 #{ext.name}、#{ext.age}, #{ids[0]}、#{ids[1]}, #{e.email}、#{e.age} |
四、查询结果返回–结果封装
4.1 @ResultType 一般返回
本质上:
使用MyBatis的默认映射规则,把查询的内容封装到指定类型中
注意点:
- 返回对象、基本数据类型时:@ResultType=“全类名”
- 虽然对于java自带的一些类型可以简写,但是不推荐
- 返回Map、List.集合时:@ResultType=“全类名”
- 有时对于返回Map<,>封装结果的方法,插件生成的@ResultType使用了Map的全类名,此时虽然也得到了Map集合,但是其中的内容(Employee等自定义对象)也都变成了Map集合,这样不符合业务逻辑无法使用get方法,不建议使用
4.2 @ResultMap 使用自定义映射规则
有时就算开启了驼峰转换,或者是其他情况,此时我们可以自定义映射的规则,确保代码正常运行
例:
-
mapper.java
Employee getEmpByName(@Param("name") String name);
-
mapper.xml
<!-- 自定义映射规则--> <resultMap id="EmployeeRM" type="com.syq.mybatis02.bean.Employee"> <id property="id" column="id"/> <result property="name" column="emp_name"/> <result property="age" column="age"/> <result property="salary" column="emp_salary"/> </resultMap> <!-- 使用该规则--> <select id="getEmpByName" resultMap="EmployeeToResult"> select id, emp_name , age, emp_salary from t_emp where emp_name = #{name} </select>
- id标签:主键
- result标签:普通字段
4.3 总结,最佳实践
步骤:
- 开启驼峰命名转换
- 如果无法转换,使用@ResultMap
五、自定义结果集
▽ 关联关系
-
一对一:
多表联查产生一对一关系,比如一个订单对应唯一的一个下单客户
- 此时需要保存客户与订单的关系键到其中的一个表中
-
一对多:
多表查询的是一对多的关系,比如一个客户的购物车中有多个订单
- 此时把对应关系存到为多的那一方的表中
-
多对多:
查询的对应关系不存在一方为一时,比如一个客户的对应商家有多个、一个商家又服务多个客户
- 此时要新建一个中间表记录客户和商家的关系
5.1 关联查询
我们要获取具有关联性的数据,可以sql的表关联 (a join b on a.id=b.id),而要
如何接收sql的查询结果
,则是我们要考虑的
(1) 一对一 【association】
因为使用关联查询,我们在sql查询器中查询得到的是一排数据,为了将其中各个表的数据分开存储,我们在自定义映射规则中引入一种把部分属性封装到一个对象中的标签【association】
使用例:
Order 与Customer 两个javaBean有关联
<?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="com.syq.mybatis03.mappers.OrderMapper">
<!-- 映射规则 -->
<resultMap id="OrderRM" type="com.syq.mybatis03.bean.Order">
<id column="id" property="id"></id>
<result column="address" property="address"></result>
<result column="amount" property="amount"></result>
<association property="customer" javaType="com.syq.mybatis03.bean.Customer"><!-- 重点 -->
<id column="customer_id" property="id"></id>
<result column="customer_name" property="customerName"></result>
<result column="phone" property="phone"></result>
</association>
</resultMap>
<!-- sql代码 -->
<select id="getOrderByIdWithCustomer" resultMap="OrderRM">
select o.*,
c.id customer_id,
c.customer_name customer_name,
c.phone
from t_order o
left join t_customer c on o.customer_id =c.id
where c.id=#{id};
</select>
</mapper>
- 我们发现在association 标签中除了property属性 指定封装属性,还
要使用javaType属性 指定对象的类型
(2) 一对多【collection】
当面对一对多关系时,查询结果有时为多行,此时我们不仅要将部分数据封装,而且要对于多行进行处理,处理成一个List集合,我们在自定义映射规则中引入一种把部分属性封装到一个List<对象>中的标签【collection】
使用例:
<?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" >
<!--suppress ALL -->
<mapper namespace="com.syq.mybatis03.mappers.CustomerMapper">
<!-- 映射规则-->
<resultMap id="CustomerRM" type="com.syq.mybatis03.bean.Customer">
<id column="c_id" property="id"></id>
<result column="customer_name" property="customerName"></result>
<result column="phone" property="phone"></result>
<collection property="orders" ofType="com.syq.mybatis03.bean.Order">
<id column="id" property="id"></id>
<result column="address" property="address"></result>
<result column="amount" property="amount"></result>
<result column="c_id" property="customerId"></result>
</collection>
</resultMap>
<!-- sql语句-->
<select id="getCustomerById" resultMap="CustomerRM">
select c.id c_id,
c.phone phone,
c.customer_name customer_name,
o.*
from t_customer c
left join t_order o on c.id =o.customer_id
where c.id=${id};
</select>
</mapper>
- 我们发现在collection 标签中除了property属性 指定封装属性,还
要使用ofType属性 指定集合元素的类型
association则是javaType
5.2 分布查询
关联查询适用于有关联关系的查询(本质上是对通过关联关系查询一次得到的容器进行分层封装),而还有一种“分布查询”的方式,除了关联关系对于没有关联关系的查询也能得到结果(本质上是对于头一次sql得不到的数据,进行再次查询,依次类推直到数据全部得到并将其封装完毕)
(1)原生分布查询
通过方法手动调用多次Dao方法,如何把结果封装并返回
(2)MyBatis自动分布查询
分布查询的本质是对于头一次sql得不到的数据,进行多次查询,也就是多个sql语句,同理我们在xml中配置多个< select > 标签,并整合到resultMap 的 collection或者association的select中就能做到分布查询
基础sql:
<select id="getOrderByIdWithCustomer2" resultMap="OrderRM2">
select *
from t_order
where id = #{id};
</select>
- 使用的是resultMap,不是resultType
resultMap:
<!-- 分布查询,获取Order-->
<resultMap id="OrderRM2" type="com.syq.mybatis03.bean.Order">
<id column="id" property="id"></id>
<result column="address" property="address"></result>
<result column="amount" property="amount"></result>
<collection property="customer"<!-- 重点 -->
select="com.syq.mybatis03.mappers.OrderMapper.getCustomerByOrderId"<!-- 重点 -->
column="{id=customer_id}">
</collection>
</resultMap>
- 这里使用的是collection标签处理一对多关系,同理association也可以
- select=" "指定的最好是全类名
- column=" “指定的是sql方法对应的参数,写成KeyV形式,有多个时:”{K=V,K=V,K=V}"
内层sql:
<select id="getCustomerByOrderId" resultType="com.syq.mybatis03.bean.Customer">
select *
from t_customer
where id=#{id};
</select>
- 这里使用的是resultType 而不是Map,以此类推:
- 如果是Map属性:则表示仍要继续
- 如果是Type属性:表示不用继续了,已经到结尾了
注意点:
要小心查询,避免出现无限重复的查询代码,引发栈溢出等问题
要规避的话可以:注意分布查询存在有resultType的select标签,注意每次使用resultMap所指向的封装规则
(3)延迟查询(延迟加载)
当使用的Dao方法会引发大量的分布查询时,我们可以通过延迟查询机制(类似于懒加载的延迟),只加载到方法所需要的数据的那一步,从而减少损耗
对应的两行配置
•mybatis.configuration.lazy-loading-enabled=true
•mybatis.configuration.aggressive-lazy-loading=false
mybatis.configuration.lazy-loading-enabled=true
mybatis.configuration.aggressive-lazy-loading=false
六、动态SQL语句
先前我们学习MyBatis的查询,但是对于sql只能拼接处理,不能在sql语句的层次进行分支、条件……的判断,而MyBatis可以通过一系列标签来做到这种动态变化
6.1 < if >标签
和JavaSE中的if类似,但是判断条件是其中的 test属性
以根据id 或者age 获取Emp为例子
- .java文件:
@Mapper
public interface EmpMapper {
// 根据id或者age获取Emp
List<Emp> getEmps1(@Param("id") Integer id,
@Param("age") Integer age);
}
- .xml文件:
<?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="com.syq.mybatis03.mappers.EmpMapper">
<select id="getEmps1" resultType="com.syq.mybatis03.bean.Emp">
select * from t_emp
where
<if test="id!=null"><!-- 这里,判断不为空 -->
id=#{id}
</if>
<if test="age!=null"><!-- 这里,判断不为空 -->
and age=#{age};
</if>
</select>
</mapper>
- 但是仅仅如此解决满足需求如果只传入一个age 属性,那么程序会出错(原因是拼接的是 and age=#{age},其中and 违反了sql的语法规范)
6.2 < where >标签
对于6.1中的实现方式,如果只传入一个age 属性,那么程序会出错
于是,通过where 标签,MyBatis 会将where标签中的and、or 等语法错误纠正,这样就能满足需求了
- .java文件(和上面一样):
@Mapper
public interface EmpMapper {
// 根据id或者age获取Emp
List<Emp> getEmps2(@Param("id") Integer id,
@Param("age") Integer age);
}
- .xml文件:
<?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="com.syq.mybatis03.mappers.EmpMapper">
<select id="getEmps1" resultType="com.syq.mybatis03.bean.Emp">
select * from t_emp
<where><!-- 这里,设置了where标签 -->
<if test="id!=null"><!-- 这里,判断不为空 -->
id=#{id}
</if>
<if test="age!=null"><!-- 这里,判断不为空 -->
and age=#{age};
</if>
</where>
</select>
</mapper>
6.3 < set >标签
类似于where标签,但是是针对更新数据时使用
对于sql中的where我们有了处理的方法,但是举个例子:
update t_emp set
<if test="salary!=null">
emp_salary=${salary} ,
</if>
<if test="id!=null">
age=${age} where id=${id};
</if>
对于这种情况,如果我们只用salary查询,那么会报错,因为结尾存在一个","号,此时,我们就要使用< set >标签
- .java文件:
void setEmp1(Emp emp);
- .xml文件:
<update id="setEmp1" parameterType="com.syq.mybatis03.bean.Emp">
update t_emp
<set>
<if test="empSalary != null">
emp_salary = #{empSalary},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="empName != null">
emp_name = #{empName}
</if>
</set>
where id = #{id};
</update>
- **注意:**虽然不知道什么原因,只有一个对象参数使用@Param后居然无法调用其属性,自己写代码时要小心,或者之后搞明白原因
6.4 < trim >标签(自定义截串规则)
trim标签能替代where或者set标签,本质是:where和set都是对于if 中语句的特定前后缀进行判断修改,而trim 标签则是将修改的内容交给程序员(比如and为前缀,或是","为后缀)
(1)内部属性
属性 | 用途 |
---|---|
prefix | 设置前缀 |
suffix | 设置后缀 |
prefixOverrides | 前缀覆盖 |
suffixOverrides | 后缀覆盖 |
- **注意:**这里的设置前后缀,只有当trim 中存在内容时才会推荐;如果没有内容,就没有前后缀
(2)对where标签的替换:
whre标签的本质是对于其内以and、or开头的字符串用 空 覆盖and、or
- .java文件:
// 根据id或者age获取Emp--2
List<Emp> getEmps2(@Param("id") Integer id,
@Param("age") Integer age);
- .xml文件:
<select id="getEmps2" resultType="com.syq.mybatis03.bean.Emp">
select * from t_emp
<trim prefix="where" prefixOverrides="and"><!-- 如果标签中包含的字符串以and开头,则用空的内容覆盖掉and -->
<if test="id!=null">
id=#{id}
</if>
<if test="age!=null">
and age=#{age};
</if>
</trim>
</select>
- **原理:**如果标签中包含的字符串以and开头,则用空的内容覆盖掉and
(3)对于set标签的替换
set标签的本质是对以“,”结尾的字符串用 空 替换
- .java文件:
// 更新Emp--2
void setEmp2(Emp emp);
- .xml文件:
<update id="setEmp2">
update t_emp
<trim prefix="set" suffixOverrides=","><!-- 如果标签中包含的字符串以“,”结尾,则用 空 的内容覆盖掉 -->
<if test="empSalary != null">
emp_salary = #{empSalary},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="empName != null">
emp_name = #{empName}
</if>
</trim>
where id = #{id};
</update>
- **原理:**如果标签中包含的字符串以“,”结尾,则用 空 的内容覆盖掉
6.5 choose–when–otherwise 标签
就是javaSE中的switch–case–default 的翻版
仍然以根据id或者age获取Emp为例
- .java文件
// 根据id或者age获取Emp--3--这个方法只能使用一个参数
List<Emp> getEmps3(@Param("id") Integer id,
@Param("age") Integer age);
- .xml文件
<select id="getEmps3" resultType="com.syq.mybatis03.bean.Emp">
select * from t_emp
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="age!=null">
age=#{age}
</when>
<otherwise>
id=2
</otherwise>
</choose>
</where>
</select>
6.6 < foreach > 标签
前面我们学了这么多但是当对于集合,我们无法确定集合中有多少参数,此时不妨使用foreach 来遍历集合,并拼接到sql语句中
(1)内部属性
属性 | 作用 |
---|---|
collection | 确定遍历的集合的名字 |
item | 指定集合的元素对应的实例名 |
separator | 指定每次遍历的分隔符 |
open | 整个遍历开始前的前缀 |
(2)基本使用:遍历插入/查询
其实也可以在查询时使用,不过这里直接以插入为代表
- java文件:
// 添加一堆Emp
void addEmp1(List<Emp> emps);
- xml文件:
<insert id="addEmp1">
insert into t_emp(emp_name, age, emp_salary)
values
<foreach collection="emps" item="emp" separator=",">
(#{emp.empName},#{emp.age},#{emp.empSalary})
</foreach>
</insert>
(3)基本使用:遍历更新/删除
更新与删除在sql层面每一次都需要一个新的sql语句(以“;”结尾算是一条语句),所以需要用“;”号分隔遍历,而默认情况下MyBatis不支持一次Dao带有多条sql语句需要在配置数据库的代码后添加/mybatis-example?allowMultiQueries=true
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-example?allowMultiQueries=true#在这里
spring.datasource.username=root
spring.datasource.password=syq8257507
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
以遍历更新为例:
- .java文件:
// 更新一堆Emp
void setEmp3(List<Emp> emps);
- .xml文件:
<update id="setEmp3">
update t_emp
<foreach collection="emps" item="emp" separator=";">
<set>
<if test="emp.empSalary != null">
emp_salary = #{emp.empSalary},
</if>
<if test="emp.age != null">
age = #{emp.age},
</if>
<if test="emp.empName != null">
emp_name = #{emp.empName}
</if>
</set>
</foreach>
</update>
注意:
- 如果不使用MyBatis遍历,而是在java代码中遍历,那么后者的效率远不如前者,因为后者的交互次数多,而前者只有一次
▽ 是否使用多sql一起发送
前面我们在遍历更新时使用了这种功能,虽然能大幅度提高效率,但是在一些情况下这种功能并不一定好
- 当我们需要对每个sql做好事务回滚:如果使用这种功能,那么一旦后面有错,那么前面的数据也一块回滚了
- 在分布式框架中不能使用:具体原因未知,等后续学到
▽ 可重复字段
当有些内容过长而且经常出现,此时可以用可重复字段代替
- 创建复用字段:
<sql id="user_name">
id,emp_name empName,age
</sql>
- 使用:
<select id="getEmp" resultType="com.syq.mybatis03.bean.Emp">
select <include refid="user_name"></include><!-- 这里使用 -->
from t_emp
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="age!=null">
age=#{age}
</when>
<otherwise>
id=2
</otherwise>
</choose>
</where>
</select>
6.7 总结
动态SQL语句的实现原理是MyBatis的自动拼串机制,我们所谓的sql动态化其本质就是对该机制的应用。又由于MyBatis在xml文件中,所以我们使用了一系列似曾相识的标签来规范这种操作
▽ XML文件的转义字符
我们的MyBatis使用xml配置,所以一些字符要以xml的规则,写成转义字符
原始字符 | 转义字符 |
---|---|
& | & |
< | < |
> | > |
" | " |
’ | ' |
七、缓存机制
6.1 什么是缓存机制
字面意思,暂缓存储:通过N级的缓存,调节运算速度和存储空间,从而改善处理装置和存储装置之间交互效率
**例:**计算机的cpu和其硬盘
6.2 MyBatis的缓存机制
MyBatis作为服务器与数据源的交互处,为了效率考虑也建立了缓存的机制
- 了解即可,以后有专门的工具
MyBatis 拥有二级缓存机制:
-
**一级缓存:**默认开启;
- 事务级别:当前事务共享
-
**二级缓存:**手动配置开启
- 开启方式:在mapper.xml中标识< cache/ >标签
- 事务级别:所有事务共享
-
缓存中有就不用查数据库;
八、插件机制
为了应对不同的情况,MyBatis 也能组载插件
- 底层原理不用细究,过于复杂,大部分时间会用别人写的插件即可
8.1 插件拦截
MyBatis 底层使用 拦截器机制提供插件功能,方便用户在SQL执行前后进行拦截增强。
-
拦截器:Interceptor
-
拦截器可以拦截 四大对象 的执行
- ParameterHandler:处理SQL的参数对象
- ResultSetHandler:处理SQL的返回结果集
- StatementHandler:数据库的处理对象,用于执行SQL语句
- Executor:MyBatis的执行器,用于执行增删改查操作
8.2 应用:PageHelper 分页插件
虽然我们可以在sql语句中配置分页操作,但是实际业务比较麻烦,可以通过分页插件较为简单并统一地给前端发数据,在减少后端代码量的同时便于前端接收并使用分页数据
(1)基本使用
-
maven配置:
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>最新版本号</version> </dependency>
-
配置类配置:
package com.syq.mybatis03.config; import com.github.pagehelper.PageInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Properties; @Configuration public class MyBatisConfig { @Bean PageInterceptor myBatisConfigBean(){ // 创建分页插件 PageInterceptor pageInterceptor = new PageInterceptor(); // 设置,以分页合理化为例 Properties properties = new Properties();// 创建设置项 properties.setProperty("reasonable","true");// 修改设置项 pageInterceptor.setProperties(properties);// 添加设置项 // 完成插件配置 return pageInterceptor; } }
- **什么是分页合理化:**超过最大按最大页数算,少于1按1算
-
实际使用:
@SpringBootTest public class PageTest { @Autowired EmpMapper empMapper; @Test public void test1() { PageHelper.startPage(1,3);// 在每次需要用到分页时使用:(当前页数,一页记录数) for (Emp emp : empMapper.getEmpAll()) { System.out.println(emp); } } }
-
注意:
- 调用PageHelper.startPage( , ) 方法,只能对其后第一个方法调用有效,原理是后一个方法获取的是插件产生的共享数据,当方法获取完毕,该数据自动销毁
(2)进阶使用(前后端请求响应交互)
前面我们只是在后端进行了分页,但是对于真正开发来说,前端会需要更详细的信息,比如:总页数、当前是第几页、页面内容的大小。
此时我们使用PageInfo< Object > Info 对象,为前端获取这些信息
例:
@Test
public void test2() {
PageHelper.startPage(1,3);
List<Emp> empAll = empMapper.getEmpAll();
System.out.println("--------------------************************");
PageInfo<Emp> empPageInfo = new PageInfo<>(empAll);
// 获取信息
System.out.println("每页显示的条数:"+empPageInfo.getPageSize());// 每页显示的条数
System.out.println("总页数:"+empPageInfo.getPages());// 总页数
System.out.println("总记录数:"+empPageInfo.getTotal());// 总记录数
System.out.println("查询结果:"+empPageInfo.getList());// 查询结果
}
效果:
- **注:**实际使用时会放在Controller方法的返回值里(作为JSON串发给前端),info中的list属性对应的就是分页查询的结果,其他属性对应别的参数