学习起源:Bilibili 遇见狂神说 Mybatis 最新残缺教程 IDEA 版通俗易懂
环境搭建
Maven 模块导入
pom.xml
<!-- mysql-connector-java -->
<!-- mybatis -->
<!-- junit -->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
配置文件
mybatis-config.xml
<configuration>
<!-- 加载类门路下的属性文件 -->
<properties resource="db.properties"/>
<!-- 默认连贯环境配置 -->
<environments default="mysql_developer">
<!-- 连贯环境信息 -->
<environment id="mysql_developer">
<!-- mybatis 应用 jdbc 事务管理形式 -->
<transactionManager type="jdbc"/>
<!-- 应用连接池的形式来获取连贯 -->
<dataSource type="pooled">
<!-- 配置与数据库交互的 4 个必要属性 -->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3307/lpxz_blog?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong" />
<property name="username" value="test" />
<property name="password" value="1234" />
</dataSource>
</environment>
</environments>
</configuration>
编写 Mybatis 工具类获取 sqlSession
MybatisUtil.java
/* 加载 mybatis-config.xml 配置文件 */
static {
try {Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 获取 SqlSession
*/
public static SqlSession getSqlSession() {
// 从以后线程中获取 SqlSession 对象
SqlSession sqlSession = threadLocal.get();
// 如果 SqlSession 对象为空
if (sqlSession == null) {
// 在 SqlSessionFactory 非空的状况下,获取 SqlSession 对象
sqlSession = sqlSessionFactory.openSession();
// 将 SqlSession 对象与以后线程绑定在⼀起
threadLocal.set(sqlSession);
}
// 返回 SqlSession 对象
return sqlSession;
}
SqlSessionFactoryBuilder
这个类能够被实例化、应用和抛弃,一旦创立了 SqlSessionFactory,就不再须要它了。因而 SqlSessionFactoryBuilder 实例的最佳作用域是办法作用域(也就是部分办法变量)。你能够重用 SqlSessionFactoryBuilder 来创立多个 SqlSessionFactory 实例,但最好还是不要始终保留着它,以保障所有的 XML 解析资源能够被开释给更重要的事件。
SqlSessionFactory
SqlSessionFactory 一旦被创立就应该在利用的运行期间始终存在,没有任何理由抛弃它或从新创立另一个实例。应用 SqlSessionFactory 的最佳实际是在利用运行期间不要反复创立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因而 SqlSessionFactory 的最佳作用域是利用作用域。有很多办法能够做到,最简略的就是应用单例模式或者动态单例模式。
SqlSession
每个线程都应该有它本人的 SqlSession 实例。SqlSession 的实例不是线程平安的,因而是不能被共享的,所以它的最佳的作用域是申请或办法作用域。相对不能将 SqlSession 实例的援用放在一个类的动态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的援用放在任何类型的托管作用域中,比方 Servlet 框架中的 HttpSession。如果你当初正在应用一种 Web 框架,思考将 SqlSession 放在一个和 HTTP 申请类似的作用域中。换句话说,每次收到 HTTP 申请,就能够关上一个 SqlSession,返回一个响应后,就敞开它。这个敞开操作很重要,为了确保每次都能执行敞开操作,你应该把这个敞开操作放到 finally 块中。上面的示例就是一个确保 SqlSession 敞开的规范模式:
try (SqlSession session = sqlSessionFactory.openSession()) {// 你的应用逻辑代码}
在所有代码中都遵循这种应用模式,能够保障所有数据库资源都能被正确地敞开。
Dao 层
UserInfoDao.java
public interface UserInfoDao {List<UserInfo> getUserInfoList();
}
接口实现类
UserInfoMapper.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">
<!-- namespace= 绑定一个对应的 Dao/Mapper 接口 -->
<mapper namespace="com.lpxz.lpxzblog.dao.UserInfoDao">
<select id="getUserInfoList" resultType="com.lpxz.lpxzblog.entity.UserInfo">
select * from user_info
</select>
</mapper>
测试类
UserInfoDaoTest.java
@Test
public void findUserInfoById() {SqlSession sqlSession = MybatisUtil.getSqlSession();
UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);
UserInfo userInfo = dao.getUserInfoById(1); // 参数为 id
System.out.println(userInfo);
// 提交并敞开 SqlSession
sqlSession.commit();
sqlSession.close();}
@Test
public void getUserInfoList() {SqlSession sqlSession = MybatisUtil.getSqlSession();
UserInfoDao dao = sqlSession.getMapper(UserInfoDao.class);
List<UserInfo> userInfoList = dao.getUserInfoList();
System.out.println(userInfoList);
// 提交并敞开 SqlSession
sqlSession.commit();
sqlSession.close();}
CRUD (Create, Retrieve, Update, Delete)
namespace
指定了类门路下的 Dao/Mapper 文件
select, insert, update, delete
==id== 对应的 namespace 中的办法名
==resultType== sql 语句执行的返回值类型
==parameterType== 参数类型
Map 传递参数
含糊查问(like“%%”)
Configuration
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
-
environments(环境配置)
-
environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
-
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
类型别名(typeAliases)
办法一 能够自定义别名
<typeAliases>
<typeAlias type="com.lpxz.lpxzblog.entity.UserInfo" alias="UserInfo"/>
</typeAliases>
办法二 通过注解实现别名:@Alias(“${definedName}”)
<typeAliases>
<package name="com.lpxz.lpxzblog.entity"/>
</typeAliases>
resultMap
后果集映射
UserInfo.java
@Data
@TableName("user_info") // @TableName 中的值对应着表名
public class UserInfo {@TableId(type = IdType.AUTO)
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer password; // 将 pwd 改为 passsword
}
UserInfoMapper.xml
<select id="getUserInfoList" resultMap="userMap">
select * from user_info
</select>
<!-- 后果集映射 -->
<resultMap id="userMap" type="userInfo">
<!-- column-> 数据库中的字段 property-> 实体类中的属性 -->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="pwd" property="password"></result>
</resultMap>
简单的应用还没有讲
日志工厂
如果一个数据库操作呈现了异样,咱们须要排错,日志是最好的助手。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Log4j
通过批改配置文件,管制每一条日志的输入格局,定义每一条日志信息的级别,不须要批改利用的代码。
日志能够输入到控制台、文件、GUI 组件
mybatis-config.xml
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
pom.xml
<!-- 日志 Log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j-version}</version>
</dependency>
log4j.properties
# 日志等级
log4j.rootLogger=DEBUG,console,file
# 控制台输入
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Threshold=DEBUG
#log4j.appender.console.ImmediateFlush=true
log4j.appender.console.Target=System.out
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
# 文件输入
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/lpxzLog.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
# 日志输入级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
应用
logger.info("info");
logger.debug("debug");
logger.error("error!");
分页
为什么要分页?
- 缩小数据的处理量
Limit 分页
SELECT * from user limit {startIndex}, {pageSize}; -- 开始地位和页面容量
SELECT * from user limit {n}; -- [0, n]
<mapper>
<!-- 分页 -->
<select id="getUserByLimit" parameterType="map" resultType="UserInfo">
select * from user_info limit #{startIndex}, #{pageSize}
</select>
</mapper>
UserInfoMapperTest.java
/**
* 分页
*/
@Test
public void getUserInfoByLimit() {SqlSession sqlSession = MybatisUtil.getSqlSession();
UserInfoMapper mapper = sqlSession.getMapper(UserInfoMapper.class);
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<UserInfo> userInfoList = mapper.getUserByLimit(map);
for (UserInfo userInfo : userInfoList) {System.out.println(userInfo);
}
sqlSession.close();}
RouBounds 分页
面向对象办法实现分页,仅作理解
应用注解开发
==AOP 面向接口编程思维 ==
UserInfoMapper.java
// 查问全副用户信息 注解形式
@Select("select * from user_info")
List<UserInfo> getUserInfoListAOP();
mybatis-config.xml
<mappers>
<!-- 绑定接口 -->
<mapper class="com.lpxz.lpxzblog.dao.UserInfoMapper"/>
</mappers>
对于 @Param
@Param 注解用于给办法参数起一个名字。以下是总结的应用准则:
- 在办法只承受一个参数的状况下,能够不应用 @Param。
- 在办法承受多个参数的状况下,倡议肯定要应用 @Param 注解给参数命名。
- 如果参数是 JavaBean,则不能应用 @Param。
- 不应用 @Param 注解时,参数只能有一个,并且是 Java Bean。
# 与 $ 的区别
-
#{} 的作用次要是替换预编译语句(PrepareStatement)中的占位符?【举荐应用】
INSERT INTO user (name) VALUES (#{name}); INSERT INTO user (name) VALUES (?);
-
${} 的作用是间接进行字符串替换
INSERT INTO user (name) VALUES ('${name}'); INSERT INTO user (name) VALUES ('kuangshen');
Lombok
==@Data== ==@AllArgsConstructor== ==@NoArgsConstructor==
…
多对一的解决
多对一的了解:
- 多个学生对应一个老师
搭建测试环境
== 创立实体类 ==
@Data
//GET,SET,ToString,有参,无参结构
public class Teacher {
private int id;
private String name;
}
@Data
public class Student {
private int id;
private String name;
// 多个学生能够是同一个老师,即多对一
private Teacher teacher;
}
== 编写实体类对应的 Mapper 接口 ==
public interface StudentMapper {
}
public interface TeacherMapper {}
== 编写 Mapper 接口对应的 mapper.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.kuang.mapper.StudentMapper"/>
<?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.kuang.mapper.TeacherMapper"/>
按查问嵌套解决
StudentMapper.java
// 获取所有学生及对应老师的信息
List<Student> getStudents();
StudentMapper.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.kuang.mapper.StudentMapper">
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!-- 对象:association 汇合:collection -->
<!--association 关联属性 property 属性名 javaType 属性类型 column 在多的一方的表中的列名 -->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>
<select id="getTeacherById" resultType="Teacher">
select * from teacher where id = #{id}
</select>
</mapper>
StudentMapperTest.java
@Test
public void testGetStudents() {SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students) {System.out.println(student);
}
sqlSession.commit();
sqlSession.close();}
按后果嵌套解决
StudentMapper.java
List<Student> getStudents2();
StudentMapper.xml
<select id="getStudents2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!-- 关联对象 property 关联对象在 Student 实体类中的属性 -->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
StudentMapperTest.java
@Test
public void testGetStudents2() {SqlSession sqlSession = MybatisUtil.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents2();
for (Student student : students) {
System.out.println("学生名:" + student.getName()
+ "\t 老师:" + student.getTeacher().getName());
}
}
依照查问进行嵌套解决就像 SQL 中的子查问
依照后果进行嵌套解决就像 SQL 中的联表查问
一对多的解决
== 批改实体类 ==
@Data
public class Teacher {
private int id;
private String name;
private List<Student> studentList;
}
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
依照后果嵌套解决
TeacherMapper.java
Teacher getTeacherById(@Param("tId") int id);
TeacherMapper.xml
<select id="getTeacherById" resultMap="TeacherStudent">
select s.id sId, s.name sName, t.name tName, t.id tId
from student s, teacher t
where s.tId = t.id and t.id = #{tId}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tId"/>
<result property="name" column="tName"/>
<!-- 汇合:collection
javaType="" 指定属性的类型
汇合中的泛型信息,应用 ofType 获取
-->
<collection property="studentList" ofType="Student">
<result property="id" column="sId"/>
<result property="name" column="sName"/>
<result property="tId" column="tId"/>
</collection>
</resultMap>
TeacherMapperTest.java
@Test
public void testGetTeacherById() {SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById(1);
System.out.println(teacher);
sqlSession.commit();
sqlSession.close();}
依照查问嵌套解决
TeacherMapper.java
Teacher getTeacherById2(@Param("tId") int id);
TeacherMapper.xml
<select id="getTeacherById2" resultMap="TeacherStudent2">
select * from teacher where id = #{tId}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="studentList" ofType="Student" select="getStudentByTeacher" column="id"/>
</resultMap>
<select id="getStudentByTeacher" resultType="Student">
select * from student where tId = #{tId}
</select>
TeacherMapperTest.java
@Test
public void testGetTeacherById2() {SqlSession sqlSession = MybatisUtil.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacherById2(1);
System.out.println(teacher);
sqlSession.commit();
sqlSession.close();}
关联 – association 多对一
汇合 – collection 一对多
javaType 用来指定实体类中属性的类型
ofType 用来指定映射到 List 或者汇合中的 POJO 类型,泛型中的束缚类型
动静 SQL
动静 SQL:指依据不同的条件生成不同的 SQL 语句
==if==
*Mapper.xml
<select id="queryBlogIf" parameterType="map" resultType="Blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
==choose==
*Mapper.xml
<select id="queryBlogChoose" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and title = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
第一个之后的 when, otherwise 标签中的语句前不加 and 会报错
==set==
BlogMapper.xml
<update id="updateBlog" parameterType="Blog">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
if 标签中语句末不加逗号会报错
==SQL 片段 ==
有时候可能某个 sql 语句咱们用的特地多,为了减少代码的重用性,简化代码,咱们须要将这些代码抽取进去,而后应用时间接调用。
提取 SQL 片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
援用 SQL 片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if-title-author"/>
</where>
</select>
==Foreach==
将数据库中前三个数据的 id 批改为 1,2,3;
需要:查问 Blog 表中 id 别离为 1,2,3 的博客信息
BlogMapper.java
List<Blog> queryBlogForeach(Map map);
*Mapper.xml
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection: 指定输出对象中的汇合属性
item: 每次遍历生成的对象
open: 开始遍历时的拼接字符串
close: 完结时拼接的字符串
separator: 遍历对象之间须要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
BlogMapperTest.java
@Test
public void testQueryBlogForeach() {SqlSession sqlSession = MybatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<String> ids = new ArrayList<>();
ids.add("1");
ids.add("2");
ids.add("3");
map.put("ids", ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
sqlSession.commit();
sqlSession.close();}
缓存
Mybatis 缓存
- MyBatis 蕴含一个十分弱小的查问缓存个性,它能够十分不便地定制和配置缓存。缓存能够极大的晋升查问效率。
- MyBatis 零碎中默认定义了两级缓存:一级缓存 和二级缓存
-
- 默认状况下,只有一级缓存开启(SqlSession 级别的缓存,也称为本地缓存)
- 二级缓存须要手动开启和配置,他是基于 namespace 级别的缓存
- 为了进步扩展性,MyBatis 定义了缓存接口 Cache。咱们能够通过实现 Cache 接口来自定义二级缓存
一级缓存
一级缓存也叫本地缓存:
- 与数据库同一次会话期间查问到的数据会放在本地缓存中。
- 当前如果须要获取雷同的数据,间接从缓存中拿,没必须再去查询数据库;
测试
1、在 Mybatis 中退出日志,不便测试后果
2、编写接口办法
// 依据 id 查问用户
User queryUserById(@Param("id") int id);
3、接口对应的 Mapper 文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
4、测试
@Test
public void testQueryUserById() {SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
session.close();}
5、后果剖析
一级缓存生效的四种状况
一级缓存是 SqlSession 级别的缓存,是始终开启的,咱们敞开不了它;
一级缓存生效状况:没有应用到以后的一级缓存,成果就是,还须要再向数据库中发动一次查问申请!
1、sqlSession 不同
@Test
public void testQueryUserById(){SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();}
察看后果:发现发送了两条 SQL 语句!
论断:每个 sqlSession 中的缓存互相独立
2、sqlSession 雷同,查问条件不同
@Test
public void testQueryUserById(){SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();}
察看后果:发现发送了两条 SQL 语句!很失常的了解
论断:以后缓存中,不存在这个数据
3、sqlSession 雷同,两次查问之间执行了增删改操作!
减少办法
// 批改用户
int updateUser(Map map);
编写 SQL
<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
</update>
测试
@Test
public void testQueryUserById(){SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();}
察看后果:查问在两头执行了增删改操作后,从新执行了
论断:因为增删改操作可能会对以后数据产生影响
4、sqlSession 雷同,手动革除一级缓存
@Test
public void testQueryUserById(){SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.clearCache();// 手动革除缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();}
一级缓存就是一个 map
二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
-
- 一个会话查问一条数据,这个数据就会被放在以后会话的一级缓存中;
- 如果以后会话敞开了,这个会话对应的一级缓存就没了;然而咱们想要的是,会话敞开了,一级缓存中的数据被保留到二级缓存中;
- 新的会话查问信息,就能够从二级缓存中获取内容;
- 不同的 mapper 查出的数据会放在本人对应的缓存(map)中;
应用步骤
1、开启全局缓存 mybatis-config.xml
<setting name="cacheEnabled" value="true"/>
2、去每个 mapper.xml 中配置应用二级缓存,这个配置非常简单;*Mapper.xml
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
<!-- 这个更高级的配置创立了一个 FIFO 缓存,每隔 60 秒刷新,最多能够存储后果对象或列表的 512 个援用,而且返回的对象被认为是只读的,因而对它们进行批改可能会在不同线程中的调用者产生抵触。-->
3、代码测试
- 所有的实体类先实现序列化接口
- 测试代码
@Test
public void testQueryUserById(){SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();}
论断
- 只有开启了二级缓存,咱们在同一个 Mapper 中的查问,能够在二级缓存中拿到数据
- 查出的数据都会被默认先放在一级缓存中
- 只有会话提交或者敞开当前,一级缓存中的数据才会转到二级缓存中
缓存原理图
EhCache
第三方缓存实现 –EhCache: 查看百度百科
Ehcache 是一种宽泛应用的 java 分布式缓存,用于通用缓存;
要在应用程序中应用 Ehcache,须要引入依赖的 jar 包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
在 mapper.xml 中应用对应的缓存即可
<mapper namespace =“org.acme.FooMapper”>
<cache type =“org.mybatis.caches.ehcache.EhcacheCache”/>
</mapper>
编写 ehcache.xml
文件,如果在加载时未找到 /ehcache.xml 资源或呈现问题,则将应用默认配置。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存门路,ehcache 分为内存和磁盘两级,此属性定义磁盘的缓存地位。参数解释如下:user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件门路
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
<!--
defaultCache:默认缓存策略,当 ehcache 找不到定义的缓存时,则应用这个缓存策略。只能定义一个。-->
<!--
name: 缓存名称。maxElementsInMemory: 缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。eternal: 对象是否永恒无效,一但设置了,timeout 将不起作用。overflowToDisk: 是否保留到磁盘,当零碎当机时
timeToIdleSeconds: 设置对象在生效前的容许闲置工夫(单位:秒)。仅当 eternal=false 对象不是永恒无效时应用,可选属性,默认值是 0,也就是可闲置工夫无穷大。timeToLiveSeconds: 设置对象在生效前容许存活工夫(单位:秒)。最大工夫介于创立工夫和生效工夫之间。仅当 eternal=false 对象不是永恒无效时应用,默认是 0.,也就是对象存活工夫无穷大。diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置 DiskStore(磁盘缓存)的缓存区大小。默认是 30MB。每个 Cache 都应该有本人的一个缓冲区。diskExpiryThreadIntervalSeconds:磁盘生效线程运行工夫距离,默认是 120 秒。memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限度时,Ehcache 将会依据指定的策略去清理内存。默认策略是 LRU(最近起码应用)。你能够设置为 FIFO(先进先出)或是 LFU(较少应用)。clearOnFlush:内存数量最大时是否革除。memoryStoreEvictionPolicy: 可选策略有:LRU(最近起码应用,默认策略)、FIFO(先进先出)、LFU(起码拜访次数)。FIFO,first in first out,这个是大家最熟的,先进先出。LFU,Less Frequently Used,就是下面例子中应用的策略,直白一点就是讲始终以来起码被应用的。如下面所讲,缓存的元素有一个 hit 属性,hit 值最小的将会被清出缓存。LRU,Least Recently Used,最近起码应用的,缓存的元素有一个工夫戳,当缓存容量满了,而又须要腾出中央来缓存新的元素的时候,那么现有缓存元素中工夫戳离以后工夫最远的元素将被清出缓存。-->
</ehcache>