MyBatis学习总结 + 【手写MyBatis底层机制核心】
MyBatis笔记
MyBatis介绍
MyBatis 是一个持久层框架
- 前身是ibatis, 在ibatis3.x 时,更名为MyBatis
- MyBatis 在java 和sql 之间提供更灵活的映射方案
- mybatis 可以将对数据表的操作(sql,方法)等等直接剥离,写到xml 配置文件,实现和java
代码的解耦 - mybatis 通过SQL 操作DB, 建库建表的工作需要程序员完成
相关文档
MyBatis 中文手册、
https://mybatis.org/mybatis-3/zh/index.html
https://mybatis.net.cn/
为什么需要MyBatis?
- 传统jdbc连接数据库需要自己编写,不统一标准
- 程序不是OOP的方式编写的
- SQL语句是写在程序中,属于硬编码,没有解耦
- mybatis 可以将对数据表的操作(sql,方法)等等直接剥离,写到xml 配置文件,实现和java
代码的解耦 - MyBatis 是一个持久层框架所以它统一
- MyBatis 是OOP方式操作数据库
MyBatis案例
实体类
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Monk {
private Integer id;
private String nickname;
private String skill;
private String grade;
private Double salary;
private String birthTime;
private Date entry;
}
mapper接口
- 只是一个接口
- 该接口用于声明操作monster表的方法
- 这些方法可以通过注解或者xml文件实现
public interface MonkMapper {
void addMonk(Monk monk);
}
mapper.xml
- 这是一个mapper xml 文件
- 该文件可以去实现对应的接口的方法
- namespace 指定该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.code_study.mapper.MonkMapper">
<!-- 配置addMonk
1. id="addMonk" 就是接口的方法名
2. parameterType="com.code_study.entity.Monk" 放入的形参
3. parameterType="com.code_study.entity.Monk" 可以简写
4. (#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})是从传入的monk对象属性来的
5. #{nickname}对应 monk对象的 private String nickname;
-->
<insert id="addMonk" parameterType="Monk">
INSERT INTO `monk`
(`nickname`,`skill`,`grade`,`salary`,`birthTime`,`entry`)
VALUES(#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})
</insert>
</mapper>
原生的API和注解的方式
Mybatis原生的API调用
添加
@Test
public void myBatisNativeCrud(){
Monk monk = new Monk();
monk.setBirthTime("1999-9-9 10:11:02");
Calendar instance = Calendar.getInstance();
instance.set(Calendar.YEAR,2020);
instance.set(Calendar.MONTH,Calendar.OCTOBER);
instance.set(Calendar.DAY_OF_MONTH,15);
Date time = instance.getTime();
monk.setEntry(time);
monk.setGrade("大宗师");
monk.setNickname("法海");
monk.setSalary(200.5);
monk.setSkill("大威天龙");
int insert = sqlSession.insert("com.code_study.mapper.MonkMapper.addMonk", monk);
System.out.println("insert~~" + insert);
System.out.println("操作成功");
}
删除
@Test
public void myBatisNativeCrud(){
int delete = sqlSession.delete("com.code_study.mapper.MonkMapper.deleteMonk", 6);
System.out.println("delete~~" + delete);
System.out.println("操作成功");
}
修改
@Test
public void myBatisNativeCrud(){
Monk monk = new Monk();
monk.setBirthTime("1999-9-9 10:11:02");
Calendar instance = Calendar.getInstance();
instance.set(Calendar.YEAR,2020);
instance.set(Calendar.MONTH,Calendar.OCTOBER);
instance.set(Calendar.DAY_OF_MONTH,15);
Date time = instance.getTime();
monk.setEntry(time);
monk.setGrade("大宗师");
monk.setNickname("法海");
monk.setSalary(200.5);
monk.setSkill("乾坤大挪移");
monk.setId(8);
int update = sqlSession.update("com.code_study.mapper.MonkMapper.updateMonk", monk);
System.out.println("update~~" + update);
System.out.println("操作成功");
}
查询
@Test
public void myBatisNativeCrud(){
List<Monk> monks =
sqlSession.selectList("com.code_study.mapper.MonkMapper.findAllMonk");
for (Monk monk : monks) {
System.out.println("monk= "+ monk);
}
if (sqlSession != null){
sqlSession.commit();
sqlSession.close();
}
}
Mybatis注解的方式操作
添加
/*
useGeneratedKeys = true : 表示可以返回自增长的值
keyProperty = "id" : 表示 自增值对应对象的哪个属性
keyColumn = "id" : 表示 自增值对应表的哪个字段
如果 keyProperty = "id" 和 keyColumn = "id" 一致,可以只保留 keyProperty = "id"
*/
@Insert(value = "INSERT INTO `monk` " +
" (`nickname`,`skill`,`grade`,`salary`,`birthTime`,`entry`) " +
" VALUES(#{nickname},#{skill},#{grade},#{salary},#{birthTime},#{entry})")
@Options(useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
void addMonk(Monk monk);
删除
@Delete(value = " DELETE FROM `monk` WHERE id = #{id}")
void deleteMonk(Integer id);
修改
@Update(value = " UPDATE `monk` " +
" SET `nickname`=#{nickname}, " +
" `skill`=#{skill}, " +
" `grade`=#{grade}, " +
" `salary`=#{salary}, " +
" `birthTime`=#{birthTime}, " +
" `entry`=#{entry} " +
" WHERE id = #{id}")
void updateMonk(Monk monk);
查询单个
//查询-根据id
@Select(value = " select * from `monk` where id = #{id}")
Monk getMonkById(Integer id);
查询多个
//查询所有的Monster
@Select(value = " select * from `monk`")
List<Monk> findAllMonk();
mybatis-config.xml配置文件详解
基本介绍
- mybatis 的核心配置文件(mybatis-config.xml),比如配置jdbc 连接信息,注册mapper
等等
properties 属性
- 通过该属性,可以指定一个外部的jdbc.properties 文件,引入我们的jdbc 连接信息
<!--引入 外部 jdbc.properties-->
<properties resource="jdbc.properties"/>
settings 全局参数定义
- 是MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。
typeAliases 别名处理器
-
别名是为Java 类型命名一个短名字。它只和XML 配置有关,用来减少类名重复的部分
-
如果指定了别名,我们的MappperXxxx.xml 文件就可以做相应的简化处理
-
注意指定别名后,还是可以使用全名的
-
可以直接指向包,这样包下的所有类都是取类名作为别名
<typeAliases>
<!--<typeAlias type="com.code_study.entity.Monk" alias="Monk"/>-->
<package name="com.code_study.entity"/>
</typeAliases>
typeHandlers 类型处理器
-
MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
-
用于java 类型和jdbc 类型映射
-
Mybatis 的映射基本已经满足,不太需要重新定义
-
这个我们使用默认即可,也就是mybatis 会自动的将java 和jdbc 类型进行转换.
environments 环境
resource 注册Mapper 文件:
<!--配置需要管理的 Mapper.xml文件注意路径resource="" 是斜杠-->
<mappers>
<mapper resource="com/code_study/mapper/MonsterMapper.xml"/>
</mappers>
class:接口注解实现
<mappers>
<mapper class="com.code_study.mapper.MonkAnnotation"/>
</mappers>
url:外部路径,使用很少,不推荐
<mappers>
<mapper url="file:///D:\yy\kk\yy\MOnsterMapper.xml" />
</mappers>
package 方式注册
<mappers>
<package name="com.cody_study.mapper"/>
</mappers>
XxxMapper.xml SQL映射文件
基本介绍
MyBatis 的真正强大在于它的语句映射(在XxxMapper.xml 配置), 由于它的异常强大, 如
果拿它跟具有相同功能的JDBC 代码进行对比,你会立即发现省掉了将近95% 的代码。
MyBatis 致力于减少使用成本,让用户能更专注于SQL 代码。
SQL 映射文件常用的几个顶级元素
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- parameterType - 将会传入这条语句的参数的类全限定名或别名
- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
parameterType(输入参数类型)
- 传入简单类型,比如按照id 查Monster
- 传入POJO 类型,查询时需要有多个筛选条件
- 当有多个条件时,传入的参数就是Pojo 类型的Java 对象
- 当传入的参数类是String 时,也可以使用${} 来接收参数
<!--形参是 String 用 ${ } 不再用 #{ }-->
<!--实现 public List<Monk> findMonsterByName(String name);-->
<select id="findMonsterByName" parameterType="String" resultType="Monk">
SELECT * FROM `monk` WHERE `nickname` LIKE '%${name}%'
</select>
- 传入的是List
时,resultType=“T”类型
<!-- 实现 public List<Monster> findMonsterByNameORId(Monster monster); -->
<select id="findMonsterByNameORId" parameterType="Monk" resultType="Monk">
SELECT * FROM `monk` WHERE `id` = #{id} OR `nickname` = #{nickname}
</select>
传入HashMap
- HashMap 传入参数更加灵活,比如可以灵活的增加查询的属性,而不受限于Monster 这
个Pojo 属性本身 - parameterType="map"
<select id="findMonsterByIdAndSalary_PrameterHashMap" parameterType="map" resultType="Monk">
SELECT * FROM `monk` WHERE `id` > #{id} AND `salary` > #{salary}
</select>
传入和返回HashMap
- parameterType="map"
- resultType="map"
<select id="findMonkByIdAndSalary_ParameterHashMap_ReturnHashMap"
parameterType="map" resultType="map">
SELECT * FROM `monk` WHERE `id` > #{id} AND `salary` > #{salary}
</select>
resultMap(结果集映射)
基本介绍
当实体类的属性和表的字段名字不一致时,我们可以通过resultMap 进行映射,从而屏蔽
实体类属性名和表的字段名的不同.(作用)
案例
public interface UserMapper {
//查询所有的User
public List<User> findAllUser();
}
<!--
说明:
resultMap:表示 定义一个 resultMap
id="findAllUserMap" :表示 程序员指定的 resultMap id,之后通过id可以使用它
type="User": 表示需要返回的对象类型
<result column="user_email" property="useremail"/> :表示 表字段 user_email 和 对象属性 useremail 之间的映射关系
-->
<!--查询所有的User-->
<!--public List<User> findAllUser();-->
<resultMap id="findAllUserMap" type="User">
<result column="user_email" property="useremail"/>
<result column="user_name" property="username"/>
</resultMap>
<!--使用的是resultMap属性而不是resultType或其他属性。
resultMap="findAllUserMap" 表示 使用我们定义的 resultMap , 通过id="findAllUserMap" 关联
-->
<select id="findAllUser" resultMap="findAllUserMap" >
SELECT * FROM `user`
</select>
动态SQL语句
基本介绍
- 在一个实际的项目中,sql 语句往往是比较复杂的
- 为了满足更加复杂的业务需求,MyBatis 的设计者,提供了动态生成SQL 的功能。
- 动态SQL 是MyBatis 的强大特性之一
- 使用JDBC 或其它类似的框架,根据不同条件拼接SQL 语句非常麻烦,例如拼接时要
- 确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等
- SQL 映射语句中的强大的动态SQL 语言, 可以很好的解决这个问题.
- 最重要的是解决一些普通的SQL无法处理的带有复杂逻辑的语句
动态SQL 常用标签
动态SQL 提供了如下几种常用的标签,类似我们Java 的控制语句:
- if [判断]
- where [拼接where 子句]
- choose/when/otherwise [类似java 的switch 语句, 注意是单分支]
- foreach [类似in ]
- trim [替换关键字/定制元素的功能]
- set [在update 的set 中,可以保证进入set 标签的属性被修改,而没有进入set 的,保
持原来的值]
if [判断]
<!-- 配置方法 public List<Monk> findMonkByAage(Integer age);-->
<select id="findMonkById" parameterType="Integer" resultType="Monk">
SELECT * FROM `monk` WHERE 1 = 1
<if test="id >= 0">
AND id > #{id}
</if>
</select>
where [拼接where 子句]
<!-- 如果入参是对象,test表达式中 直接使用属性名即可 例如: test="id>=0" -->
<!-- where标签 会在组织动态sql时,加上where,并去掉多余的 AND-->
<select id="findMonkByIdAndName" parameterType="Monk" resultType="Monk">
SELECT * FROM `monk`
<where>
<if test="id>=0">
AND id > #{id}
</if>
<if test="nickname != null and nickname != ''">
AND `nickname` = #{nic kname}
</if>
</where>
</select>
choose/when/otherwise [类似java 的switch 语句, 注意是单分支]
<!-- 配置方法 List<Monk> findMonsterByIdAndName_choose(Map<String, Object> map);-->
<select id="findMonkByIdOrName_choose" parameterType="map" resultType="Monk">
select * from `monk`
<choose >
<when test="nickname != null and nickname !='' ">
where nickname = #{nickname}
</when>
<when test="id > 0 ">
where id = #{id}
</when>
<otherwise>
where salary > 10000
</otherwise>
</choose>
</select>
foreach [类似in ]
<select id="findMonkById_forEach" parameterType="map" resultType="Monk">
SELECT * FROM `monk`
<if test="ids != null and ids != ''">
<where>
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</if>
</select>
trim [替换关键字/定制元素的功能]
<!-- 配置方法 List<Monk> findMonkByName_Trim(Map<String, Object> map);-->
<select id="findMonkByName_Trim" parameterType="map" resultType="Monk">
SELECT * FROM `monk`
<trim prefix="WHERE" prefixOverrides="AND|OR|ZY">
<if test="id>=0">
ZY id > #{id}
</if>
<if test="nickname != null and nickname != ''">
AND `nickname` = #{nickname}
</if>
</trim>
</select>
set [在update 的set 中,可以保证进入set 标签的属性被修改,而没有进入set 的,保
持原来的值]
<!-- 配置方法 void updateMonster_set(Map<String, Object> map);-->
<update id="updateMonster_set" parameterType="map">
UPDATE `monk`
<set>
<if test="grade != null and grade != ''">
`grade` = #{grade},
</if>
<if test="skill != null and skill != ''">
`skill` = #{skill},
</if>
<if test="nickname != null and nickname != ''">
`nickname` = #{nickname},
</if>
<if test="grade != null and grade != ''">
`grade` = #{grade},
</if>
<if test="salary != null and salary != ''">
`salary` = #{salary},
</if>
<if test="birthTime != null and birthTime != ''">
`birthTime` = #{birthTime},
</if>
<if test="entry != null and entry != ''">
`entry` = #{entry},
</if>
</set>
WHERE id = #{id}
</update>
映射关系一对一
基本介绍
- 项目中1 对1 的关系是一个基本的映射关系,比如:Person(人) --- IDCard(身份证
映射方式
-
通过配置XxxMapper.xml 实现1 对1 [配置方式]
-
通过注解的方式实现1 对1 [注解方式]
映射方式一:配置Mapper.xml实现
- 通过配置XxxMapper.xml 的方式,实现级联查询,通过person 可以获取到对应的idencard 信息
接口:
public interface PersonMapper {
//通过Person 的id 获取到Person,包括这个Person 关联的IdenCard 对象
Person getPersonById(Integer id);
//通过Person 的id 获取到Person,包括这个Person 管理的IdenCard 对象,方式2
Person getPersonById2(Integer id);
//通过Person 的id 获取到Person
Person getPerson(Integer id);
}
public interface IdenCardMapper {
//根据id 获取到身份证序列号
IdenCard getIdenCardById(Integer id);
//通过IdenCard 的id 获取到Person
IdenCard getIdenCardAndPerson(Integer id);
}
mapper.xml:
通过person 可以获取到对应的idencard 信息
方式一 :多表联查
<!-- 通过Person 的id 获取到Person,包括这个Person 关联的IdenCard 对象-->
<resultMap id="PersonResultMap" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--
property="card" 表示 person对象的card属性
javaType="IdenCard" 表示 card属性 的类型
column="id" 是这条语句
select *
from `person` join `idencard` on person.card_id = idencard.id
where person.id = #{id}
查询返回的字段
-->
<association property="card" javaType="IdenCard">
<!--标记出作为id的结果 可以帮助提高整体性能-->
<!--property="id"表示 person的属性 id ;column="id" 表示 对应表的字段 id ,通常是主键-->
<id property="id" column="id"/>
<result property="card_sn" column="card_sn"/>
</association>
</resultMap>
<select id="getPersonById" parameterType="Integer" resultMap="PersonResultMap">
select *
from `person` join `idencard` on person.card_id = idencard.id
where person.id = #{id}
</select>
方式二:分解成单表操作
<resultMap id="PersonResultMap2" type="Person">
<id property="id" column="id"/>
<result property="name" column="name"/>
<association property="card" column="card_id"
select="com.code_study.mapper.IdenCardMapper.getIdenCardById"/>
<!-- column="card_id"是 下面 SELECT * FROM `person` where `id` = #{id} 返回的字段
card_id 会作为getIdenCardById 入参,执行
-->
</resultMap>
<select id="getPersonById2" parameterType="Integer" resultMap="PersonResultMap2">
SELECT * FROM `person` where `id` = #{id}
</select>
映射方式二:注解实现
public interface PersonMapperAnnotation {
@Select(value = "SELECT * FROM `person` where `id` = #{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "card",column = "card_id",
one = @One(select = "com.code_study.mapper.IdenCardMapperAnnotation.getIdenCardById"))
})
Person getPersonById(Integer id);
}
映射关系多对一
基本介绍
- 项目中多对1 的关系是一个基本的映射关系, 多对1, 也可以理解成是1 对多.
- User --- Pet: 一个用户可以养多只宠物
- Dep ---Emp : 一个部门可以有多个员工
映射方式
-
通过配置XxxMapper.xml 实现1 对1 [配置方式]
-
通过注解的方式实现1 对1 [注解方式]
映射方式一:配置Mapper.xml实现
接口:
public interface UserMapper {
//通过id 获取User 对象
public User getUserById(Integer id);
}
public interface PetMapper {
//通过User 的id 来获取pet 对象,可能有多个,因此使用List 接收
public List<Pet> getPetByUserId(Integer userId);
//通过pet 的id 获取Pet 对象
public Pet getPetById(Integer id);
}
mapper.xml
<mapper namespace="com.code_study.mapper.PetMapper">
<!--通过User 的id 来获取pet 对象,可能有多个,因此使用List 接收
public List<Pet> getPetByUserId(Integer userId);
-->
<resultMap id="PetResultMap" type="Pet">
<id property="id" column="id"/>
<result property="nickname" column="nickname"/>
<association property="user" column="user_id"
select="com.code_study.mapper.UserMapper.getUserById"/>
</resultMap>
<select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
SELECT * FROM `mybatis_pet` WHERE user_id = #{userId}
</select>
<!--通过pet 的id 获取Pet 对象-->
<!--public Pet getPetById(Integer id);-->
<select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
select * from `mybatis_pet` where id = #{id}
</select>
</mapper>
<mapper namespace="com.code_study.mapper.UserMapper">
<!--ofType="Pet" : 表示 返回的集合中存放的数据类型是 Pet-->
<resultMap id="PetsResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<collection property="pets" column="id"
ofType="Pet" select="com.code_study.mapper.PetMapper.getPetByUserId" />
</resultMap>
<select id="getUserById" parameterType="Integer" resultMap="PetsResultMap">
SELECT * from `mybatis_user` WHERE id = #{id}
</select>
</mapper>
说明:
- 多对一时,resultMap中 collection 标签表示集合
- 在 collection 标签中 属性 ofType 表示集合中存放的数据类型
- 双向多对一时,toString应该去掉,避免栈溢出
- resultMap中 select 标签表示复用这个目标方法,推荐使用,这样提高代码的复用性,将多表拆解成单表
映射方式二:注解实现
public interface UserMapperAnnotation {
//通过id 获取User 对象
@Select(value = "SELECT * from `mybatis_user` WHERE id = #{id}")
@Results({
@Result(id = true,property = "id",column = "id"),
@Result(property = "name",column = "name"),
@Result(property = "pets",column = "id",
many = @Many(select = "com.code_study.mapper.PetMapperAnnotation.getPetByUserId"))
})
public User getUserById(Integer id);
}
public interface PetMapperAnnotation {
//通过User 的id 来获取pet 对象,可能有多个,因此使用List 接收
@Select(value = "SELECT * FROM `mybatis_pet` WHERE user_id = #{userId}")
@Results(id = "PetResultMap",value = {
@Result(id = true,property = "id",column = "id"),
@Result(property = "nickname",column = "nickname"),
@Result(property = "user",column = "user_id",
one = @One(select = "com.code_study.mapper.UserMapperAnnotation.getUserById"))
})
List<Pet> getPetByUserId(Integer userId);
//通过pet 的id 获取Pet 对象
@Select(value = " select * from `mybatis_pet` where id = #{id}")
@ResultMap(value = "PetResultMap")
Pet getPetById(Integer id);
}
- @Results(id = "PetResultMap"...)可以给@Results设置一个 id 用来复用代码
- 在下面就进行了@Results 的复用
//通过pet 的id 获取Pet 对象
@Select(value = " select * from `mybatis_pet` where id = #{id}")
@ResultMap(value = "PetResultMap")
Pet getPetById(Integer id);
MyBatis缓存
基本介绍
- MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
相关文档
一级缓存
-
默认情况下,mybatis 是启用一级缓存的/本地缓存/local Cache,它是SqlSession 级别的。
-
同一个SqlSession 接口对象调用了相同的select 语句,会直接从缓存里面获取,而不是再
去查询数据库
一级缓存原理图
一级缓存失效的情况
- 关闭sqlSession 会话后, 再次查询,会到数据库查询
- 当执行sqlSession.clearCache() 会使一级缓存失效
- 当对同一个monster 修改,该对象在一级缓存会失效
二级缓存
- 二级缓存和一级缓存都是为了提高检索效率的技术
- 最大的区别就是作用域的范围不一样,一级缓存的作用域是sqlSession 会话级别,在一次
会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效
二级缓存原理图
二级缓存配置
1.在mybatis-config.xml中 全局性的开启缓存
2.使用二级缓存时entity 类实现序列化接口(serializable),因为二级缓存可能使用到序列化技术
*大部分情况不需要这样配置,只是某些第三方缓存库需要序列化到磁盘!
3.再对应的XxxMapper.xml中设置二级缓存的策略
<mapper namespace="com.code_study.mapper.MonsterMapper">
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!--查询-根据id-->
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `monster` WHERE id = #{id}
</select>
</mapper>
注意事项
-
表示创建了FIFO 的策略,每隔30 秒刷新一次,最多存放360 个对象而且返回的对象被认为是只读的。
-
eviction:缓存的回收策略
-
flushInterval:时间间隔,单位是毫秒,
-
size:引用数目,内存大就多配置点,要记住你缓存的对象数目和你运行环境的可用内存
-
资源数目。默认值是1024
-
readOnly:true,只读
四大策略
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象
如何禁用二级缓存
方式一
<settings>
<!--全局性的开启缓存-->
<setting name="cacheEnabled" value="false"/>
</settings>
方式二
在XxxMapper.xml文件中不配做
方式三
直接在XxxMapper.xml文件对应的方法上 设置属性 useCache=false
<!--查询所有的Monster-->
<select id="findAllMonster" resultType="Monster" useCache="false">
SELECT * FROM `monster`
</select>
一级缓存和二级缓存的执行顺序
- 缓存执行顺序是:二级缓存-->一级缓存-->数据库
- 当我们关闭了一级缓存的时候,如果配置了二级缓存,那么一级缓存的数据会放入二级缓存中
- 不会出现一级缓存和二级缓存中有同一个数据。因为二级缓存(数据)是在一级缓存关闭之后才有的
第三方缓存框架---EhCache
基本介绍
- EhCache 是一个纯Java 的缓存框架,具有快速、精干等特点
- MyBatis 有自己默认的二级缓存(前面我们已经使用过了),但是在实际项目中,往往使用
的是更加专业的第三方缓存产品作为MyBatis 的二级缓存,EhCache 就是非常优秀的缓存
产品
配置使用第三方缓存框架
添加依赖
<dependencies>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
在XxxMapper.xml文件中启用EhCache
<mapper namespace="com.code_study.mapper.MonsterMapper">
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<!--查询-根据id-->
<select id="getMonsterById" resultType="Monster">
SELECT * FROM `monster` WHERE id = #{id}
</select>
</mapper>
如何理解EhCache 和MyBatis 缓存的关系
-
MyBatis 提供了一个接口Cacheorg.apache.ibatis.cache.Cache
-
只要实现了该Cache 接口,就可以作为二级缓存产品和MyBatis 整合使用,Ehcache 就
是实现了该接口 -
MyBatis 默认情况(即一级缓存)是使用的PerpetualCache 类实现Cache 接口的,是核心类
-
当我们使用了Ehcahce 后,就是EhcacheCache 类实现Cache 接口的,是核心类.
-
缓存的本质就是Map<Object,Object>
手写MyBatis底层机制
读取配置文件,得到数据库连接
思路
- 引入必要的依赖
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
实现
- 引入必要的依赖
<dependencies>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?
useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="zy"/>
</database>
- 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
public class ZyConfiguration {
//属性 类加载器
private ClassLoader classLoader =
ClassLoader.getSystemClassLoader();
//读取xml文件信息并处理
public Connection build(String resource) {
Connection connection = null;
//加载配置文件,获取对应的InputStream流
InputStream resourceAsStream =
classLoader.getResourceAsStream(resource);
//解析xml文件
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
//解析rootElement
System.out.println("root= "+root);
return evalDataSource(root);
} catch (DocumentException e) {
throw new RuntimeException(e);
}
}
//解析xml文件 并返回一个连接
private Connection evalDataSource(Element node) {
Iterator property = node.elementIterator("property");
String driverClassName = null;
String url = null;
String username = null;
String password = null;
//遍历node子节点 获取属性值
while(property.hasNext()){
Element pro = (Element)property.next();
String name = pro.attributeValue("name");
String value = pro.attributeValue("value");
//判断是否得到了name 和 value
if (name == null || value == null){
throw new RuntimeException("property 节点没有设置name 或 value属性");
}
switch (name){
case "driverClassName":
driverClassName = value;
break;
case "url":
url = value;
break;
case "username":
username = value;
break;
case "password":
password = value;
break;
default:
throw new RuntimeException("属性名没有匹配到");
}
}
Connection connection = null;
try {
Class.forName(driverClassName);
connection = DriverManager.getConnection(url, username, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
return connection;
}
}
编写执行器,输入SQL语句,完成操作
思路
- 需要写一个实体类,对应monster表
- 编写接口executor
- 实现接口,编写自己的执行器
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
实现
- 需要写一个实体类,对应monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {
private Integer id;
private Integer age;
private String name;
private String email;
private Date birthday;
private double salary;
private Integer gender;
}
- 编写接口executor
public interface Executor {
public <T> T query(String statement,Object parameter);
}
- 实现接口,编写自己的执行器
public class ZyExecutor implements Executor{
private ZyConfiguration zyConfiguration = new ZyConfiguration();
@Override
public <T> T query(String sql, Object parameter) {
Connection connection = getConnection();
//查询返回的结果集
ResultSet set = null;
PreparedStatement pre = null;
try {
pre = connection.prepareStatement(sql);
//设置参数,如果参数多,用数组处理
pre.setString(1, parameter.toString());
set = pre.executeQuery();
//把set数据封装到对象 -- monster
Monster monster = new Monster();//简化处理 认为返回的结果就是一个monster记录
//遍历结果集
while(set.next()){
monster.setId(set.getInt("id"));
monster.setName(set.getString("name"));
monster.setEmail(set.getString("email"));
monster.setAge(set.getInt("age"));
monster.setGender(set.getInt("gender"));
monster.setBirthday(set.getDate("birthday"));
monster.setSalary(set.getDouble("salary"));
}
return (T)monster;
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
try {
if (set != null) {
set.close();
}
if (pre != null) {
pre.close();
}
if (connection != null) {
connection.close();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public Connection getConnection(){//Configuration类 返回连接,通过连接对数据库进行操作
return zyConfiguration.build("zy_mybatis.xml");
}
}
- 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作
将Sqlsession封装到执行器
思路
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
实现
- 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
public class ZySqlSession {//搭建连接和执行器之间的桥梁
//执行器
private Executor executor = new ZyExecutor();
//配置
private ZyConfiguration zyConfiguration = new ZyConfiguration();
//操作DB 的具体方法
//SelectOne 返回一条记录-对象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
}
- 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
//操作DB 的具体方法
//SelectOne 返回一条记录-对象
public <T> T selectOne(String statement,Object parameter){
return executor.query(statement,parameter);
}
开发Mapper接口和Mapper.xml
思路
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
实现
- 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
public interface MonsterMapper {
public Monster getMonsterById(Integer id);
}
- 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
- monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper">
<!-- 实现配置接口方法 getMonsterById-->
<select id="getMonsterById" resultType="com.code_study.entity.Monster">
SELECT * FROM monster WHERE id = ?
</select>
</mapper>
开发MapperBean,可以和Mapper接口相映射
思路
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
- 开发 MapperBean类,记录接口信息和接口下的所有方法
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
实现
- 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
//对应 monsterMapper.xml中的信息
public class Function {
private String sqlType;//sql类型,比如select,insert,update,delete
private String funcName;//方法名
private String sql;//执行的sql语句
private Object resultType;//返回类型
private String parameterType;//入参类型
}
- 开发 MapperBean类,记录接口信息和接口下的所有方法
//对应 MonsterMapper 接口中的信息
public class MapperBean {
private String interfaceName;//接口名
// 接口下的所有方法
private List<Function> functions;
}
- Function类 对应 monsterMapper.xml中的信息
- MapperBean类 对应 MonsterMapper 接口中的信息
在Configuration中解析MapperXML获取MapperBean对象
思路
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
实现
- 在Configuration 添加方法readMapper(String path)
- 通过 path 读取接口对应的Mapper方法
- 保存接口下所有的方法信息
- 封装成 MapperBean对象
//解析MapperXML获取MapperBean对象
//path = xml的路径+文件名 是从类的加载路径计算的(如果放在resource目录下 之间传xml文件名即可)
public MapperBean readMapper(String path) {
MapperBean mapperBean = new MapperBean();
InputStream resourceAsStream = classLoader.getResourceAsStream(path);
SAXReader reader = new SAXReader();
try {
Document document = reader.read(resourceAsStream);
Element root = document.getRootElement();
String namespace = root.attributeValue("namespace");
mapperBean.setInterfaceName(namespace);
List<Function> list = new ArrayList<>();//保存接口下所有的方法信息
//得到root的迭代器
Iterator iterator = root.elementIterator();
while(iterator.hasNext()){
Element e = (Element)iterator.next();
String sqlType = e.getName().trim();
String sql = e.getText().trim();
String funcName = e.attributeValue("id");
String resultType = e.attributeValue("resultType");
//ResultType 返回的是一个Object对象 ->反射
Object instance = Class.forName(resultType).newInstance();
//封装 function 对象
Function function = new Function();
function.setSql(sql);
function.setSqlType(sqlType);
function.setFuncName(funcName);
function.setResultType(instance);
//将封装好的function对象 放入 list中
list.add(function);
mapperBean.setFunctions(list);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return mapperBean;
}
动态代理Mapper方法
思路
- 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
- 编写动态代理类 实现 InvocationHandler 接口
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
实现
-
编写动态代理类 实现 InvocationHandler 接口
-
在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
//返回mapper的动态代理对象
public <T> T getMapper(Class<T> clazz){
return (T) Proxy.newProxyInstance(
clazz.getClassLoader(),
new Class[]{clazz},
new ZyMapperProxy(zyConfiguration,clazz,this));
}
- 取出mapperBean的functions 遍历
- 判断 当前要执行的方法和function.getFunctionName是否一致
- 调用方法返回 动态代理对象
public class ZyMapperProxy implements InvocationHandler {
private ZySqlSession zySqlSession;
private String mapperFile;
private ZyConfiguration zyConfiguration;
public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {
this.zySqlSession = zySqlSession;
this.zyConfiguration = zyConfiguration;
this.mapperFile = clazz.getSimpleName() + ".xml";
}
//当执行Mapper接口的代理对象方法时,会执行到invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);
//判断是否为当前xml文件对应的接口
if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
return null;
}
//取出mapperBean的functions
List<Function> functions = mapperBean.getFunctions();
//判断当前的mapperBean 解析对应的MapperXML后,有方法
if (null != functions || 0 != functions.size()){
for (Function function : functions) {
//当前要执行的方法和function.getFunctionName
if (method.getName().equals(function.getFuncName())){
if ("SELECT".equalsIgnoreCase(function.getSqlType())){
return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
}
}
}
}
return null;
}
}
- 编写SqlSessionFactory 会话工厂,可以返回SqlSession
public class ZySqlSessionFactory {
public static ZySqlSession open(){
return new ZySqlSession();
}
}
测试
@Test
public void openSession(){
ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();
System.out.println("zySqlSession= "+zySqlSession);
MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);
Monster monster = mapper.getMonsterById(1);
System.out.println("monster= "+monster);
}
本文学习内容来自韩顺平老师的课程
仅供个人参考学习