Mybatis学习笔记
一、简介
-
Mybatis历史
- MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁 移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于 2013年11月迁移到Github。 iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架 包括SQL Maps和Data Access Objects(DAO)。
-
Mybatis特性
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录
- MyBatis 是一个 半自动的ORM(Object Relation Mapping)框架
-
Mybatis和其他持久化层技术对比
- JDBC
- SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难复杂 SQL 需要绕过框架
- 内部自动生产的 SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
- 反射操作太多,导致数据库性能下降
- MyBatis
- 轻量级,性能出色
- SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
- 开发效率稍逊于HIbernate,但是完全能够接受
- JDBC
二、Mybatis基础功能
一、配置数据库相关信息
spring:
# 配置项目名称
application:
name: mybatis-demo
# 配置数据库连接
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: 1234
# 配置mapper接口目录下的所有日志级别都是debug(可以看到sql语句)
logging:
level:
pers:
jl:
mapper: debug
# 给包下所有实体设置别名,后续映射文件就可以写别名
mybatis:
type-aliases-package: pers.jl.entity
二、Mybatis的映射文件的讲解和编写
-
创建User表、实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; private String password; private String sex; private String email; private String age; }
-
创建UserMapper接口
public interface UserMapper { int userInsert(); }
-
映射文件
注意映射文件放在资源目录下时,创建目标要使用pers/jl/mapper,用斜线,不能用点
<?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="pers.jl.mapper.UserMapper"> <insert id="userInsert"> insert into t_user values (null,'小明','1234','男','1235@qq.com','23') </insert> </mapper>
-
启动类添加注解
@SpringBootApplication @MapperScan("pers.jl.mapper")//扫描所有mapper接口,生成代理对象,也会扫描映射文件,因为编译后都在一个包下 public class MybatisDemoApplication { public static void main(String[] args) { SpringApplication.run(MybatisDemoApplication.class, args); } }
三、Mybatis实现增删改查
mapper接口
public interface UserMapper {
int userInsert();
int userUpdate();
int userDelete();
User getUserById();
List<User> getAllUser();
}
映射文件
<?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="pers.jl.mapper.UserMapper">
<!--添加数据-->
<insert id="userInsert">
insert into t_user values (null,'小明','1234','男','1235@qq.com','23')
</insert>
<!--更新数据-->
<update id="userUpdate">
update t_user set name = '小红',password = '5678',sex = '女' where id = 2
</update>
<!--删除数据-->
<delete id="userDelete">
delete from t_user where id = 3
</delete>
<!--根据id查询用户-->
<select id="getUserById" resultType="pers.jl.entity.User">
select * from t_user where id = 2
</select>
<!--查询所有用户-->
<select id="getAllUser" resultType="pers.jl.entity.User">
select * from t_user
</select>
</mapper>
四、Mybatis获取参数数值的两种方式
-
Mybatis获取参数的两种方式:${} 和 #{}
${}本质是字符串拼接,#{}本质是占位符赋值
-
${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引 号;
-
但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号
1. 单个字面量类型的参数
-
就是一个形参的情况。
-
直接使用#{}或者${}获取变量值,但是要注意后者是字符串拼接,所以需要手动添加单引号,然后就是变量名不重要,它会自动把参数值传给{}里面。
mapper接口
User getUserByName(String name);
映射文件
<!--根据用户名查询用户,单个字面量传参-->
<select id="getUserByName" resultType="User">
select * from t_user where name = #{name}
</select>
2.多个字面量类型的参数
- 获取多个参数时,mybatis会把所有参数封装成一个Map集合,可以使用参数名来获取参数,也可以使用param1、param2来获取。
mapper接口
User getUserByNameAndPassword(String name,String password);
映射文件
<!--根据用户名、密码查询用户-->
<select id="getUserByNameAndPassword" resultType="User">
<!--select * from t_user where name = #{param1} and password = #{param2}-->
select * from t_user where name = #{name} and password = #{password}
</select>
3.map集合类型的参数
- 当用户有多个参数需要传递时,我们可以选择将所有参数都封装到一个Map集合中,每个参数值有对应的键,然后直接使用#{}取值即可。
mapper接口
<T> User getUserByMap(Map<String,T> map);
映射文件
<!--根据map集合查询用户对象-->
<select id="getUserByMap" resultType="User">
select * from t_user where name = #{n} and password = #{p}
</select>
4.实体类型的参数
- 当参数是实体对象时,拼接SQL时,直接使用#{}获取传参实体属性就可以获取参数值。
mapper接口
int insertUser(User user);
映射文件
<!--新增用户-->
<insert id="insertUser">
insert into t_user values (null,#{name},#{password},#{sex},#{email},#{age})
</insert>
5.使用@Param标识参数
- 在mapper接口上使用@Param注解标识参数,就是将所有参数放到Map集合中,然后使用@Param的值作为键,参数值作为值,然后在映射文件可以使用#{}直接引用
mapper接口
User getUserByParam(@Param("na") String name, @Param("pa") String password);
映射文件
<!--获取用户信息,使用注解起别名-->
<select id="getUserByParam" resultType="User">
select * from t_user where name = #{na} and password = #{pa}
</select>
五、Mybatis的各种查询功能
1. 查询一个实体类对象
2. 查询一个list集合
- 根据查询结果,我们需要选择合适的类型去接收。
- 比如:查询结果是一个对象,我们可以选择用一个对象或者对象集合接收;如果查询结果是一个对象集合,那么我们只能用对象集合来接收
3.查询单个数据
-
Mybatis中设置了默认的类型别名(常用)
-
java.lang.Integer–>int、integer(大小写均可)
-
int–>int、integer(大小写均可)
-
Map–>map
-
String–>string
-
mapper接口
Integer getCount();
映射文件
<!--查询数据数量-->
<select id="getCount" resultType="java.lang.Integer">
select count(*) from t_user
</select>
4.查询一条数据为map集合
- 当我们查询的数据没有实体对象时,我们可以选择使用map来接收,或者我们是联表查询,也可以使用map来封装结果。
mapper接口
<M> Map<String,M> getUserByIdToMap(Integer id);
映射文件
<!--查询用户信息,封装成一个map集合-->
<select id="getUserByIdToMap" resultType="java.util.Map">
select * from t_user where id = #{id}
</select>
5.查询多条数据为map集合
- 当我们查询多条数据,使用map来封装时,我们可以使用List集合来封装每一个Map数据;也可以使用一个map来封装所有map数据,键是数据的id,值就是map数据,相当于就是map里面装的map。
mapper接口
// List<Map<String,O>> getUserListByMap();
@MapKey("id")//使用id作为键,单挑用户数据作为值
Map<String,O> getUserListByMap();
映射文件
<!--查询多条用户数据封装成Map集合-->
<select id="getUserListByMap" resultType="map">
select *
from t_user;
</select>
六、特殊SQL的执行
1. 模糊查询
- 模糊查询有三种方式,第一种使用${}获取参数,第二种使用字符串函数拼接,第三种使用双引号把%包裹然后使用#{}获取参数,最常用的是第三种方式。
mapper接口
List<User> getLikeUser(String name);
映射文件
<!--模糊查询-->
<select id="getLikeUser" resultType="User">
<!-- select * from t_user where name like '%${name}%'-->
<!--select * from t_user where name like concat('%',#{name},'%')-->
select * from t_user where name like "%"#{name}"%"
</select>
2. 批量删除
- 注意批量删除数据时,只能使用${}来获取参数值
- 我们传给sql的是一个用逗号隔开的id字符串,在sql中我们使用${}就可以获取字符串中的每个id
mapper接口
int removeInId(String ids);
映射文件
<!--批量删除-->
<delete id="removeInId">
delete from t_user where id in (${ids})
</delete>
3. 动态设置表名
- 有些时候我们需要把一张表水平切割成多个表,所以我们有时候需要指定表名来查询数据,我们只能用${}来获取表名。
mapper接口
List<User> getAllUserWithTableName(@Param("tableName") String tableName);
映射文件
<!--动态设置表名-->
<select id="getAllUserWithTableName" resultType="User">
select * from ${tableName}
</select>
4. 添加功能获取自增的主键
- 在映射文件上设置两个参数,就可以获取到我们的自增主键id,有些时候插入数据,我们后续需要使用这个自增的id来进行其他业务,就需要使用这个功能。
- useGeneratedKeys="true"表示SQL使用的是自增主键id
- keyProperty="id"表示将生成的主键id赋值给实体对象的属性id中。
mapper接口
int insertUserReturnId(User user);
映射文件
<!--新增用户,可以获取自增id-->
<insert id="insertUserReturnId" useGeneratedKeys="true" keyProperty="id">
insert into t_user values (null,#{name},#{password},#{sex},#{email},#{age})
</insert>
七、Mybatis的自定义映射resultMap
1.数据库字段名与实体类属性名不一致的解决方案(三种)
-
数据库字段符合数据库设计规则,实体类名符合实体类命名规则,所以不能随便更改。
-
解决数据库字段名与实体类属性不一致,导致数据无法封装的情况
1.设置字段别名对应实体类属性
mapper接口
List<Emp> getAllEmp();
映射文件
<!--设置别名查询数据-->
<select id="getAllEmp" resultType="Emp">
select eid,emp_name empName,age,sex,email from t_emp
</select>
2.配置全局配置,下划线自动映射成驼峰命名
mybatis:
# 给包下所有实体设置别名,后续映射文件就可以写别名
type-aliases-package: pers.jl.entity
configuration:
# 下划线自动映射成驼峰命名
map-underscore-to-camel-case: true
3.自定义映射关系
- resultType只能解决默认的映射关系,也就是字段名匹配属性名的情况,如果不匹配,就不能用。
自定义映射标签
- resultMap是自定义映射关系
- id是映射关系的唯一标识
- type是设置映射关系中的实体类类型,也就是需要映射的实体类
子标签
- id是设置主键映射关系
- result是设置普通字段的映射关系
属性
- property是设置映射关系中的属性名,必须是type属性所设置的实体类中的属性
- colume是设置映射关系中的字段名,必须是sql语句查询出的字段名
<resultMap id="mapEmp" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="did" column="did"></result>
</resultMap>
<!--自定义映射查询数据-->
<select id="getAllEmp" resultMap="mapEmp">
select eid,emp_name,age,sex,email from t_emp
</select>
2.处理多对一情况
- 实体类多对一时,我们在多的那一个实体类中存放一的实体类属性。
1.级联查询
映射文件
<resultMap id="cascading" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="did" column="did"></result>
<result property="dept.did" column="did"></result>
<result property="dept.deptName" column="dept_name"></result>
</resultMap>
<!--左外连接查询员工以及员工对应的部门信息-->
<select id="getEmpWithDept" resultMap="cascading">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{id}
</select>
2.使用多对一标签
- association是处理多对一的映射关系的标签
- property是待赋值实体类的属性名,也就是Emp的dept属性
- javaType是该属性的类型,指定为部门Dept
<resultMap id="cascading" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="did" column="did"></result>
<association property="dept" javaType="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</association>
</resultMap>
<!--左外连接查询员工以及员工对应的部门信息-->
<select id="getEmpWithDept" resultMap="cascading">
select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{id}
</select>
3. 分步查询
-
直接查询员工信息,然后根据员工的部门id查询部门信息,最后封装到结果中
-
property就是Emp的属性
-
select是查询当前属性所调用的sql语句,需要使用唯一标识全类名+方法名
-
column是传递给子查询的查询条件,这里因为需要根据员工部门did查询部门信息,所有传递did
-
分步查询的优点是可以开启延迟加载,减少不必要的sql语句执行。
全局的分步查询开启延迟加载
mybatis: # 给包下所有实体设置别名,后续映射文件就可以写别名 type-aliases-package: pers.jl.entity configuration: #下划线自动映射成驼峰命名 map-underscore-to-camel-case: true # 开启延迟加载 lazy-loading-enabled: true
如果在全局延迟加载的条件下,某个分步查询sql不需要延迟加载,就在映射文件那里使用fetchType指定eager立即加载
<resultMap id="step" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <result property="did" column="did"></result> <association property="dept" select="pers.jl.mapper.DeptMapper.getDeptByDid" column="did" fetchType="eager"></association> </resultMap>
先查询员工信息
<resultMap id="step" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<result property="did" column="did"></result>
<association property="dept"
select="pers.jl.mapper.DeptMapper.getDeptByDid"
column="did"></association>
</resultMap>
<!--分步查询员工信息-->
<select id="getEmpAndDeptByStepOne" resultMap="step">
select * from t_emp where eid = #{eid}
</select>
第二部查询部门调用的SQL
<!--根据did查询部门信息-->
<select id="getDeptByDid" resultType="Dept">
select * from t_dept where did = #{did}
</select>
3.处理一对多情况
- 实体类一对多时,我们在一的那一个实体类中存放一个集合存放多的实体类即可。
1.使用一对多标签
- oftype就是指定集合中存放的实体类型。
- 注意这时员工信息不用封装部门id了,不然会循环套娃。
- 也是查出所有数据,然后返回自定义映射封装的结果
mapper接口
Dept getDeptById(Integer did);
映射文件
<resultMap id="getDeptAndEmp" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<!--根据did左外连接查询部门及其部门的员工-->
<select id="getDeptById" resultMap="getDeptAndEmp">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>
2.分步查询
mapper接口
Dept getDeptStepOne(Integer did);
映射文件
<resultMap id="getDeptOne" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="pers.jl.mapper.EmpMapper.getEmpByDid"
column="did"></collection>
</resultMap>
<!--分步查询部门信息-->
<select id="getDeptStepOne" resultMap="getDeptOne">
select * from t_dept where did = #{did}
</select>
调用的sql
<!--根据部门did查询员工集合-->
<select id="getEmpByDid" resultType="Emp">
select * from t_emp where did = #{did}
</select>
八、Mybatis的动态SQL
- Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。
if标签
- test里面就是拼接条件,可以直接写参数的属性,不用使用#{}
- where 1=1,无论后面是否有条件成立,sql都可以拼接成功,成功执行
<!--多条件查询员工信息-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp where 1=1
<if test="empName !=null and empName!= ''">
and emp_name = #{empName}
</if>
<if test="age !=null and age!= ''">
and age = #{age}
</if>
<if test="sex !=null and sex!= ''">
and sex = #{sex}
</if>
<if test="email !=null and email!= ''">
and email = #{email}
</if>;
</select>
where标签
- 当where标签中有内容时,会自动生成where关键字,并且将内容前多余的and或or去掉。(注意不能去掉内容后面的,所以and或or都写前面)。
- 当where标签中没有内容时,此时的where标签没有任何效果
<!--多条件查询员工信息-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<where>
<if test="empName !=null and empName!= ''">
and emp_name = #{empName}
</if>
<if test="age !=null and age!= ''">
and age = #{age}
</if>
<if test="sex !=null and sex!= ''">
and sex = #{sex}
</if>
<if test="email !=null and email!= ''">
and email = #{email}
</if>
</where>
</select>
trim标签
- prefix | suffix 在内容前面或后面添加指定内容
- suffixOverrides | prefixOverrides 去掉内容前面或后面的指定内容
- 如果trim标签中没有内容,trim标签没有任何效果
<!--多条件查询员工信息-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and|or">
<if test="empName !=null and empName!= ''">
emp_name = #{empName} and
</if>
<if test="age !=null and age!= ''">
age = #{age} and
</if>
<if test="sex !=null and sex!= ''">
sex = #{sex} or
</if>
<if test="email !=null and email!= ''">
email = #{email}
</if>
</trim>
</select>
choose、when、otherwise标签
- 相当于if…else if …else,也类似switch…case的作用,只会寻找一个满足条件的when,如果没有就拼接otherwise中的条件。
- when可以有多个,otherwise最多一个
<!--多条件查询员工信息-->
<select id="getEmpByCondition" resultType="Emp">
select * from t_emp
<trim prefix="where">
<choose>
<when test="empName !=null and empName!= ''">
emp_name = #{empName}
</when>
<when test="age !=null and age!= ''">
age = #{age}
</when>
<when test="sex !=null and sex!= ''">
sex = #{sex}
</when>
<when test="email !=null and email!= ''">
email = #{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</trim>
</select>
foreach标签
- collection指定访问数组或集合
- item是数组或集合中的每一个数据
- separator可以给循环体对象之间加分隔符
通过数组实现批量删除功能
方式一
<!--根据数组id批量删除员工-->
<delete id="removeEmpByArrays">
delete from t_emp where eid in
<foreach collection="eids" item="eid" open="(" close=")" separator=",">
#{eid}
</foreach>
</delete>
方式二
<!--根据数组id批量删除员工-->
<delete id="removeEmpByArrays">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
</delete>
通过集合实现批量添加功能
<!--根据集合批量添加员工-->
<insert id="addEmpByList">
insert into t_emp values
<foreach collection="list" item="emp" separator=",">
(null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>
SQL标签
- sql标签截取重复片段,以供其他sql使用
- include标签在需要的地方引入sql片段
<sql id="empCloumns">eid, emp_name, age, sex, email</sql>
<!--自定义映射查询数据-->
<select id="getAllEmp" resultMap="mapEmp">
select <include refid="empCloumns"></include>
from t_emp
</select>
三、Mybatis的缓存
1.一级缓存
-
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
-
使一级缓存失效的四种情况:
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作(任何增删改都会使一级缓存失效)
- 同一个SqlSession两次查询期间手动清空了缓存
-
虽然一级缓存默认开启,但是如果我们在springboot中需要使用一级缓存,需要添加注解@Transactional才能获取到同一个SqlSession,然后才能生效
//查询员工以及员工对应的部门信息 @Test @Transactional void getEmpWithDept() { Emp emp = empMapper.getEmpWithDept(1); System.out.println(emp); Emp emp2 = empMapper.getEmpWithDept(1); System.out.println(emp2); }
2.二级缓存
-
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被 缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
-
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签<cache/>
- 二级缓存必须在SqlSession关闭或提交之后有效 (springboot中,mapper接口的代理对象执行完操作自动提交)
- 查询的数据所转换的实体类类型必须实现序列化的接口
-
使二级缓存失效的情况:
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
-
二级缓存标签的一些属性配置
-
eviction属性,缓存回收策略:
- 缓存回收策略 LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
- 默认的是 LRU。
-
flushInterval属性:刷新间隔,单位毫秒 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size属性:引用数目,正整数 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly属性:只读,true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。
- false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
-
-
二级缓存,我们就不用使用注解@Transactional,这样获取的SqlSession对象就不是同一个
//查询员工以及员工对应的部门信息 @Test void getEmpWithDept() { Emp emp = empMapper.getEmpWithDept(1); System.out.println(emp); Emp emp2 = empMapper.getEmpWithDept(1); System.out.println(emp2); }
注意:二级缓存在springboot中对应namespace命名空间的范围,一旦一个命名空间开启二级缓存,该命名空间下的所有查询操作都会走二级缓存,如果不需要走二级缓存,可以在sql上使用useCache=“false” 配置即可。
3.缓存查询顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序(SqlSession)已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存(springboot中一级缓存自动提交)
- springboot中,mapper执行完操作,自动提交SqlSession,就会写入一级缓存和二级缓存。
//二级缓存
@Test
void getEmpWithDept() {
//1.先查询二级缓存,二级缓存未命中,
getEmpWithDept2();
//4.二级缓存,命中
Emp emp2 = empMapper.getEmpWithDept(1);
System.out.println("2"+emp2);
}
//一级缓存
@Test
@Transactional
void getEmpWithDept2() {
//2.查询一级缓存,一级缓存未命中,查询数据库,然后写入一级缓存,自动提交SqlSession,然后写入二级缓存
Emp emp0 = empMapper.getEmpWithDept(1);
System.out.println("0"+emp0);
//3.二级缓存命中
Emp emp1 = empMapper.getEmpWithDept(1);
System.out.println("1"+emp1);
}
4.二级缓存整合第三方缓存EHCache
-
添加依赖
slf4j就是一个日志门面,logback是它的一个具体实现
<!-- Mybatis EHCache整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <!-- slf4j日志门面的一个具体实现 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
-
添加ehcache配置文件
<?xml version="1.0" encoding="utf-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="C:\Users\ASUS\Desktop\ehcache"/> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
-
指定配置文件路径
spring: ...... cache: ehcache: # 整合ehcache config: ehcache.xml
-
添加logback日志配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 --> <root level="DEBUG"> <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="pers.jl.mapper" level="DEBUG"/> </configuration>
四、Mybatis的逆向工程
- 正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程 的。
- 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
-
导入插件
<!-- 控制Maven在构建过程中相关配置 --> <build> <!-- 构建过程中用到的插件 --> <plugins> <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 --> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.0</version> <!-- 插件的依赖 --> <dependencies> <!-- 逆向工程的核心依赖 --> <dependency> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-core</artifactId> <version>1.3.2</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> </dependencies> </plugin> </plugins> </build>
-
添加配置文件generatorConfig.xml
MyBatis3Simple和MyBatis3自行选择
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!-- targetRuntime: 执行生成的逆向工程的版本 MyBatis3Simple: 生成基本的CRUD(清新简洁版) MyBatis3: 生成带条件的CRUD(奢华尊享版) --> <context id="DB2Tables" targetRuntime="MyBatis3"> <!-- 数据库的连接信息 --> <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="1234"> </jdbcConnection> <!-- javaBean的生成策略--> <javaModelGenerator targetPackage="pers.jl.pojo" targetProject=".\src\main\java"> <!--能够使用子包--> <property name="enableSubPackages" value="true" /> <!--去掉数据库字段名前后的空格--> <property name="trimStrings" value="true" /> </javaModelGenerator> <!-- SQL映射文件的生成策略 --> <sqlMapGenerator targetPackage="pers.jl.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!-- Mapper接口的生成策略 --> <javaClientGenerator type="XMLMAPPER" targetPackage="pers.jl.mapper" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!-- 逆向分析的表,需要对哪些表进行逆向工程,就写这 --> <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName --> <!-- domainObjectName属性指定生成出来的实体类的类名 --> <table tableName="t_emp" domainObjectName="Emp"/> <table tableName="t_dept" domainObjectName="Dept"/> <table tableName="t_user" domainObjectName="User"/> </context> </generatorConfiguration>
-
插件执行mybatis-generator
直接点击就行。
注意:
- 生成的实体类或者接口带有Example都是条件的意思,例如deleteByExample就是根据条件删除。
- 生成的实体类或者接口带有Selective都是选择性的意思,例如insertSelective就是该方法可能只插入指定的非空字段,而忽略其他为空的字段。(如果不懂看映射文件sql)