文章目录
MyBatis
MyBatis 是一个持久层框架,前身是 ibatis, 在 ibatis3.x 时,更名为 MyBatis。
MyBatis 在 java 和 sql 之间提供更灵活的映射方案,mybatis 可以将对数据表的操作(sql,方法)等等直接剥离,写到 xml 配置文件,实现和 java代码的解耦。
中文网:http://www.mybatis.cn/mybatis/index.html
1. 原理图
2. 快速入门
1)创建数据表
CREATE DATABASE `mybatis` CREATE TABLE `monster` (
`id` INT NOT NULL AUTO_INCREMENT,
`age` INT NOT NULL,
`birthday` DATE DEFAULT NULL,
`email` VARCHAR(255) NOT NULL ,
`gender` TINYINT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`salary` DOUBLE NOT NULL,
PRIMARY KEY (`id`)
) CHARSET=utf8
2)导入相关依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>compile</scope>
</dependency>
3)创建mybatis配置文件mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="hsp"/>
</dataSource>
</environment>
</environments>
</configuration>
4)创建实体类
@Data
public class Monster {
private Integer id;
private Integer age;
private String name;
private String email;
private Date birthday;
private double salary;
private Integer gender;
}
@Data
为lombok注解,能自动生成getter、setter以及toString方法。
5)创建Mapper接口
public interface MonsterMapper {
//添加方法
public void addMonster(Monster monster);
}
6)创建配置文件MonsterMapper.xml配置映射关系
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.MonsterMapper">
<insert id="addMonster" parameterType="com.lhs.pojo.Monster" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO monster (age,birthday,email,gender,name,salary)
VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary})
</insert>
</mapper>
说明:
- select:实现一个查询操作
- insert:表示一个添加操作
- id=“addMonster” 表示 MonsterMapper 接口的方法名
- resultType=“xx” 返回的结果类型,如果没有就不需要写
- parameterType=“com.lhs.pojo.Monster” 表示该方法输入的参数类型
- (age,birthday,email,gender,name,salary) 表的字段名
- #{age},#{birthday},#{email},#{gender},#{name},#{salary} 是实体类 Monster 的属性名
7)在mybatis-config.xml 中添加配置文件
<mappers>
<!-- 这里会引入(注册)我们的 Mapper.xml 文件 -->
<mapper resource="com/lhs/mapper/MonsterMapper.xml"/>
</mappers>
8)创建工具类来获取sqlsession
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//使用 mybatis 第一步:获取 sqlSessionFactory 对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
SqlSession 提供了对数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
9)测试
@Test
public void testAdd(){
Monster monster = new Monster();
monster.setAge(18);
monster.setName("lxg");
monster.setBirthday(new Date());
monster.setEmail("123@11.com");
monster.setSalary(11.11);
monster.setGender(1);
monsterMapper.addMonster(monster);
// 执行完sql语句后会自动填充id的值
System.out.println(monster.getId());
//增删改,需要提交事务
if (sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("保存成功!");
}
3. 日志输出
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<scope>compile</scope>
</dependency>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
4. 原生api完成crud
我们除了创建mapper对象完成crud外,也可以用sqlSession直接来操作mapper
手动提交事务
@After
public void commit(){
if (sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
}
4.1 增
@Test
public void test03(){
Monster monster = new Monster();
monster.setAge(18);
monster.setName("xxx");
monster.setBirthday(new Date());
monster.setEmail("12345@11.com");
monster.setSalary(111.11);
monster.setGender(1);
int res = sqlSession.insert("com.lhs.mapper.MonsterMapper.addMonster",monster);
System.out.println(res);
}
4.2 删
@Test
public void test04(){
Monster monster = new Monster();
monster.setId(1);
int res = sqlSession.delete("com.lhs.mapper.MonsterMapper.deleteMonster",monster);
System.out.println(res);
}
4.3 改
@Test
public void test05(){
Monster monster = new Monster();
monster.setId(3);
monster.setName("yyy");
int res = sqlSession.delete("com.lhs.mapper.MonsterMapper.updateMonster",monster);
System.out.println(res);
}
4.4 查
@Test
public void test06(){
List<Monster> monsters = sqlSession.selectList("com.lhs.mapper.MonsterMapper.getAllMonster");
System.out.println(monsters);
}
5. 注解完成crud
我们可以直接将sql语句写在注解上,就不用在mapper.xml文件中配置sql语句了。
public interface MonsterMapper {
@Insert(value = "INSERT INTO monster (age,birthday,email,gender,name,salary) VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary})")
public void addMonster(Monster monster);
@Select(value = "select * from monster")
public List<Monster> getAllMonster();
@Delete(value = "delete from monster where id = #{id}")
public void deleteMonster(Monster monster);
@Update(value = "update from monster (age,birthday,email,gender,name,salary) VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary}) where id = #{id}")
public void updateMonster(Monster monster);
}
在mapper.xml中指定mapper接口即可
<mapper namespace="com.lhs.mapper.MonsterMapper" />
6. 配置文件
6.1 properties 属性
通过该属性,可以指定一个外部的 jdbc.properties 文件,引入我们的 jdbc 连接信息
jdbc.properties:
jdbc.user=root
jdbc.password=83929317fan
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?userSSL=false&userUnicode=true&characterEncoding=UTF-8
jdbc.driver=com.mysql.jdbc.Driver
mybatis-config.xml:
<!-- 这里就是引入 jdbc.properties 文件 -->
<properties resource="jdbc.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
6.2 typeAliases 别名处理器
<typeAliases>
<package name="com.lhs.pojo"/>
</typeAliases>
<select id="getAllMonster" resultType="Monster">
select * from monster
</select>
6.3 完整配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<!-- 如果一个包下有很多的类,我们可以直接引入包,这样
该包下面的所有类名,可以直接使用
-->
<package name="com.lhs.pojo"/>
<!-- 为某个 mapper 指定一个别名, 下面可以在 XxxxxMapper.xml 做相应简化处理 -->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 这里会引入(注册)我们的 Mapper.xml 文件 -->
<!-- <mapper resource="com/lhs/mapper/MonsterMapper.xml"/>-->
<!-- 也可直接指定包名-->
<package name="com.lhs.mapper"/>
</mappers>
</configuration>
7. SQL 映射文件
MyBatis 的真正强大在于它的语句映射(在 XxxMapper.xml 配置), 由于它的异常强大, 如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件常用的几个顶级元素(按照应被定义的顺序列出):
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
- parameterType - 将会传入这条语句的参数的类全限定名或别名
- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
7.1 parameterType(输入参数类型)
parameterType可以传入简单类型,比如按照 id 查 Monster,也可以传入 POJO 类型
当传入的参数类是 String 时,可以使用 ${} 来接收参数
<!-- 实现 findMonsterByNameORId -->
<select id="findMonsterByNameORId" parameterType="Monster"
resultType="Monster">
SELECT * FROM monster
WHERE id=#{id} OR name=#{name}
</select>
<!-- 看看模糊查询的使用 取值 需要 ${value} 取值-->
<select id="findMonsterByName" parameterType="String" resultType="Monster">
SELECT * FROM monster
WHERE name LIKE '%${value}%' </select>
7.2 传入HashMap
HashMap 传入参数更加灵活,比如可以灵活的增加查询的属性,而不受限于Pojo 属性本身。
可以将返回结果也设置为map:
resultType="map"
1)增加接口
//查询 id > 10 并且 salary 大于 40, 要求传入的参数是 HashMap
public List<Monster> findMonsterByIdAndSalary_PrameterHashMap(Map<String, Object> map);
2)修改mapper
<!-- 实现 findMonsterByIdAndSalary_PrameterHashMap -->
<select id="findMonsterByIdAndSalary_PrameterHashMap" parameterType="map" resultType="Monster">
SELECT * FROM monster
WHERE id > #{id} AND salary > #{salary}
</select>
3)测试
@Test
public void findMonsterByIdAndSalary_PrameterHashMap() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("id", 1);//条件
map.put("salary", 924);
List<Monster> monsterList =
monsterMapper.findMonsterByIdAndSalary_PrameterHashMap(map);
for (Monster monster : monsterList) {
System.out.println(monster);
}
if (sqlSession != null) {
sqlSession.close();
}
}
7.3 resultMap(结果集映射)
当实体类的属性和表的字段名字不一致时,我们可以通过 resultMap 进行映射,从而屏蔽实体类属性名和表的字段名的不同。
<mapper namespace="com.lhs.mapper.UserMapper">
<!-- 实现 addUser -->
<insert id="addUser" parameterType="User">
INSERT INTO user (user_name,user_email) VALUES(#{username}, #{useremail})
</insert>
<!-- 实现 findAllUser【resultmap 屏蔽属性名和字段名不一致 1.复用性好,高大上 】 -->
<!-- 1. resultMap 表示我们定义一个结果映射
2. type="User" 返回的真正的数据类型还是 User
3. id="findAllUserMap", 给 resultMap 取名
4. column="user_name" 表中的字段
5. property="username" 表示对象的属性
-->
<resultMap type="User" id="findAllUserMap">
<result column="user_name" property="username"/>
<result column="user_email" property="useremail"/>
</resultMap>
<select id="findAllUser" resultMap="findAllUserMap" >
SELECT * FROM user
</select>
</mapper>
8. 动态 SQL 语句
动态 SQL 是 MyBatis 的强大特性之一,使用 JDBC 或其它类似的框架,根据不同条件拼接 SQL 语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等,SQL 映射语句中的强大的动态 SQL 语言, 可以很好的解决这个问题 。
比如我们查询Monster 时,如果程序员输入的age 不大于0, 我们的sql语句就不带age 。更新 Monster 对象时,没有设置的新的属性值,就保持原来的值,设置了新的值,才更新。
动态 SQL 提供了如下几种常用的标签,类似我们 Java 的控制语句:
- if [判断]
- where [拼接 where 子句]
- choose/when/otherwise [类似 java 的 switch 语句, 注意是单分支]
- foreach [类似 in ]
- trim [替换关键字/定制元素的功能]
- set [在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值]
8.1 if
需求:请查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0, 则输出所有的妖怪
//根据 age 查询结果
public List<Monster>
findMonsterByAge(@Param("age") Integer age);
<!-- 实现 findMonsterByAge -->
<select id="findMonsterByAge" resultType="Monster" parameterType="Integer">
SELECT * FROM monster WHERE 1=1
<if test="age > 0">
AND age > #{age}
</if>
</select>
8.2 where
需求:查询 id 大于 20 的,并且名字是 “牛魔王” 的所有妖怪,注意,如果名字为空,或者输入的 id 小于 0, 则不拼接 sql 语句
//根据 id 和名字来查询结果
public List<Monster> findMonsterByIdAndName(Monster monster);
<!-- 实现 findMonsterByIdAndName 【where 和 if】 -->
<select id="findMonsterByIdAndName" parameterType="Monster" resultType="Monster">
SELECT * FROM monster
<!-- 使用 where 标签开始拼接
where 标签的好处是. 会自动的加入 where 子句. mybatis 会自动的去掉多余的 AND
-->
<where>
<if test="id > 0">
AND id > #{id}
</if>
<if test="name != null and name != ''">
AND name = #{name}
</if>
</where>
</select>
8.3 choose/when/otherwise
需求:如果给的 name 不为空,就按名字查询妖怪,如果指定的 id>0,就按 id 来查询妖怪, 要求使用 choose/when/otherwise 标签实现, 传入参数要求使用 Map
public List<Monster>
findMonsterByIdAndName_choose(Map<String, Object> map);
<!-- findMonsterByIdAndName_choose[使用 choose 标签] -->
<select id="findMonsterByIdAndName_choose" parameterType="map"
resultType="Monster">
SELECT * FROM monster
<choose>
<!-- 这里 test="name" 这 name 就是你 传入的 map 参数对应的 key -->
<when test="name != null and name != ''">
WHERE name = #{name}
</when>
<when test="id > 0">
WHERE id > #{id}
</when>
<otherwise>
WHERE 1 = 1
</otherwise>
</choose>
</select>
8.4 forEach
需求:查询 monster_id 为 20, 22, 34 的妖怪
public List<Monster>
findMonsterById_forEach(Map<String, Object> map);
<!-- 实现 findMonsterById_forEach[使用 forEach] -->
<select id="findMonsterById_forEach" parameterType="map" resultType="Monster">
SELECT * FROM monster
<if test="ids != null and ids != ''">
<where>
id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</if>
</select>
8.5 trim
trim 可以替换一些关键字
需求:按名字查询妖怪,如果 sql 语句有 and | or 就替换成 where
public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
<select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">
select * from monster
<trim prefix="where" prefixOverrides="and|or">
<if test="name !=null and name!=''">
and name=#{name}
</if>
</trim>
</select>
8.6 set
需求: 请对指定 id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值
public void updateMonster_set(Map<String, Object> map);
<!-- updateMonster_set set 标签 -->
<update id="updateMonster_set" parameterType="map">
UPDATE monster
<set>
<if test="age != null and age != ''">
age = #{age}, </if>
<if test="birthday != null and birthday != ''">
birthday = #{birthday}, </if>
<if test="email != null and email != ''">
email = #{email}, </if>
<if test="gender != null and gender != ''">
gender = #{gender}, </if>
<if test="name != null and name != ''">
name = #{name}, </if>
<if test="salary != null and salary != ''">
salary = #{salary}, </if>
</set>
WHERE id = #{id}
</update>
9. 映射关系一对一
项目中 1 对 1 的关系是一个基本的映射关系,比如:Person(人) — IDCard(身份证)
9.1 通过mapper文件完成映射
public interface PersonMapper {
//通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
public Person getPersonById(Integer id);
}
方式一:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.PersonMapper">
<!-- 实现 getPersonById [方式 1] -->
<!-- 定义一个 resultMap 来映射返回的结果 -->
<resultMap type="Person" id="personResultMap">
<!-- id:一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能, 通常指定主键
property="id" 表示 person 对象的哪个属性代表主键
column="id" 表示对应的表的字段 -->
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- association: 一个复杂类型的关联 -->
<association property="card" javaType="com.lhs.entity.IdenCard">
<!-- 将关联的 card 对象哪些属性放入到这个 resultMap -->
<result property="id" column="id"/>
<result property="card_sn" column="card_sn"/>
</association>
</resultMap>
<select id="getPersonById" parameterType="Integer" resultMap="personResultMap">
select * from person , idencard WHERE person.id=#{id}
AND person.card_id = idencard.id;
</select>
</mapper>
方式二:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.PersonMapper">
<!-- 1 对 1 的第二种方式[推荐] -->
<!-- 定义一个 resultMap 来映射返回的结果 -->
<resultMap type="Person" id="personResultMap2">
<!-- id:一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能, 通常指定主键
property="id" 表示 person 对象的哪个属性代表主键
column="id" 表示对应的表的字段 -->
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 说明当前这个 person 的 card 属性,这个数据从 getIdenCardById 获取
card_id: 是从 select * from person WHERE id=#{id}给他的. 相当于, 把多表联查分解成单表操作, 简洁,易于维护, 推荐 -->
<association property="card" column="card_id" select="com.lhs.mapper.IdenCardMapper.getIdenCardById"
/>
</resultMap>
<select id="getPersonById2" parameterType="Integer"
resultMap="personResultMap2">
select * from person WHERE id=#{id}
</select>
</mapper>
9.2 通过注解完成映射
public interface PersonMapperAnnotation {
//通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
@Select("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.lhs.mapper.IdenCardMapperAnnotaion.getIdenCardById"))
})
public Person getPersonById(Integer id);
}
10. 映射关系多对一
项目中多对 1 的关系是一个基本的映射关系,多对 1, 也可以理解成是 1 对多。
例如:User — Pet: 一个用户可以养多只宠物。
10.1 通过mapper文件完成映射
userMapeer.xml:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.UserMapper">
<!-- collection : 一对多关联查询, 表示一个用户可能对应多个 pet 对象
ofType: 集合中元素对象的类型 -->
<resultMap type="com.lhs.entity.User" id="UserResultMap">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- column="id" 就是当前 User 表的主键字段名,通过这个 user 的 id,
去查询对应的 pet 有哪些 -->
<collection property="pets" ofType="com.lhs.entity.Pet" column="id" select="com.lhs.mapper.PetMapper.getPetByUserId"></collection>
</resultMap>
<select id="getUserById" parameterType="Integer" resultMap="UserResultMap">
select * from mybatis_user where id=#{id}
</select>
</mapper>
petMapper.xml:
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hspedu.mapper.PetMapper">
<resultMap type="com.hspedu.entity.Pet" id="PetResultMap">
<id property="id" column="id"/>
<result property="nickname" column="nickname"/>
<association property="user" column="user_id" select="com.hspedu.mapper.UserMapper.getUserById"></association>
</resultMap>
<!-- 老韩解读:
1. user_id=#{userId} userId 就是调用 getPetByUserId 传入的用户 id
2. userId 名称,由程序员来确定,比如你写成 id,uId 等都可以-->
<select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
select * from mybatis_pet where user_id=#{userId}
</select>
<select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
select * from mybatis_pet where id=#{id}
</select>
</mapper>
10.2 通过注解完成映射
userMapper:
public interface UserMapperAnnotation {
@Select("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.lhs.mapper.PetMapperAnnotation.getPetByUserId"))
})
public User getUserById(Integer id);
}
petMapper:
public interface PetMapperAnnotation {
//通过 User 的 id 来获取 pet 对象,可能有多个,因此使用 List 接收
@Select("select * from mybatis_pet where user_id=#{userId}")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "nickname", column = "nickname")
})
public List<Pet> getPetByUserId(Integer userId);
//通过 pet 的 id 获取 Pet 对象
@Select("select id AS tnId,nickname AS tnNickname, user_id AS tnUser_id from
mybatis_pet where id=#{id}")
@Results({
@Result(id = true, property = "id", column = "tnId"),
@Result(property = "nickname", column = "tnNickname"),
@Result(property = "user", column = "tnUser_id", one = @One(select =
"com.lhs.mapper.UserMapperAnnotation.getUserById"))
})
public Pet getPetById(Integer id);
}
11. 缓存
默认情况下,mybatis 是启用一级缓存的/本地缓存/local Cache,它是 SqlSession 级别的。
同一个 SqlSession 接口对象调用了相同的 select 语句,会直接从缓存里面获取,而不是再去查询数据库。
11.1 一级缓存
需求: 当我们第 1 次查询 id=1 的 Monster 后,再次查询 id=1 的 monster 对象,就会直接从一级缓存获取,不会再次发出 sql。
@Test
public void level1CacheTest() {
Monster monster = monsterMapper.getMonsterById(1);
System.out.println("--" + monster + "--");
// 当我们再次查询 id=1 的 Monster 时,直接从一级缓存获取,不会再次发出 sql
monster = monsterMapper.getMonsterById(1);
System.out.println("--" + monster + "--");
if (sqlSession != null) {
sqlSession.commit();
sqlSession.close();
}
System.out.println("操作成功");
}
失效原因分析:
- 关闭 sqlSession 会话后, 再次查询,会到数据库查询
- 当执行 sqlSession.clearCache() 会使一级缓存失效
- 当对同一个 monster 修改,该对象在一级缓存会失效
11.2 二级缓存
二级缓存和一级缓存都是为了提高检索效率的技术,最大的区别就是作用域的范围不一样,一级缓存的作用域是 sqlSession 会话级别,在一次会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效。
使用步骤:
1)在mybatis-config.xml中开启二级缓存(不设置也行,默认就是开启状态)
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2)在xxxMapper.xml中配置二级缓存
<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/>
说明:
- eviction=“FIFO” :缓存策略 先进先出
- flushInterval=“30000”:每 30000 毫秒 刷新一次,和数据库保持一致
- size:二级缓存设置最大保持的 360 个对象, 超过了,就启用 fifo 策略处理 ,默认1024
- readOnly:只读,为了提高效率
四大策略:
- LRU – 最近最少使用的:移除最长时间不被使用的对象,它是默认
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象
11.3 EhCache 缓存
EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点。
MyBatis 有自己默认的二级缓存(前面我们已经使用过了),但是在实际项目中,往往使用的是更加专业的第三方缓存产品 作为 MyBatis 的二级缓存,EhCache 就是非常优秀的缓存产品。
使用步骤:
1)引入相关依赖
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
2)加入配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!--diskStore:为缓存路径,ehcache 分为内存和磁盘两级,此属性定义磁盘的缓存位置。
参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--defaultCache:默认缓存策略,当 ehcache 找不到定义的缓存时,则使用这个缓存策略。
只能定义一个。
-->
<defaultCache
eternal="false" maxElementsInMemory="10000"
overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/>
</ehcache>
参数解释:
- name:缓存名称。
- maxElementsInMemory:缓存最大数目。
- maxElementsOnDisk:硬盘最大缓存个数。
- eternal:对象是否永久有效,一但设置了,timeout 将不起作用。
- overflowToDisk:是否保存到磁盘,当系统宕机时。
- timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
- timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。
- diskPersistent:是否缓存虚拟机重启期数据。
- memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。
- clearOnFlush:内存数量最大时是否清除。
3)在 XxxMapper.xml 中启用 EhCache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
细节说明:
- MyBatis 提供了一个接口 Cache,只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用,Ehcache 就是实现了该接口
- MyBatis 默认情况(即一级缓存)是使用的 PerpetualCache 类实现 Cache 接口的,是核心类
- 当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的,是核心类