Mybatis【分页插件,缓存,一级缓存,二级缓存,常见缓存面试题】
文章目录
- MyBatis缓存
- 分页
- 延迟加载和立即加载
- 什么是立即加载?
- 什么是延迟加载?
- 延迟加载/懒加载的配置
- 缓存
- 什么是缓存?
- 缓存的术语
- 什么是MyBatis 缓存?
- 缓存的适用性
- 缓存的分类
- 一级缓存
- 引入案例
- 一级缓存的配置
- 一级缓存的工作流程
- 一级缓存失效的情况
- 二级缓存
- XML实现
- 注解实现
- 二级缓存的缺点
- 自定义缓存的分类
- 总结(面试题汇总):
MyBatis缓存
分页
在Mybatis的配置文件中进行声明该插件:
<?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>
<properties resource="jdbc.properties"></properties>
<typeAliases>
<!-- 给单个类起别名 -->
<!-- <typeAlias alias="Student" type="bean.Student"/> -->
<!-- 批量别名定义,包扫描,别名为类名,扫描整个包下的类 -->
<package name="bean" />
</typeAliases>
<!-- 分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
<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.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注册sqlmapper文件 -->
<!-- 1.同包 接口和sqlMapper 2.同名 接口和sqlMapper 3.sqlMapper的namespace指向接口的类路径 -->
<!-- <mapper resource="mapper/StudentMapper.xml" /> -->
<!-- <mapper class="mapper.StudentMapper"/> -->
<package name="mapper" />
</mappers>
</configuration>
// 逻辑分页,减少对磁盘的读取,但是占用内存空间大
@Select("select * from student")
public List<Student> findStudentRowBounds(RowBounds rb);
// 分页插件(推荐)
@Select("select * from student")
public List<Student> findStudentPageHelper();
方式1: 使用Map集合来保存分页需要数据,来进行分页
package mapper;
public interface StudentMapper {
// 物理分页,多次读取磁盘,占用内存小
@Select("select * from student limit #{cpage},#{size}")
public List<Student> selectLimit(@Param("cpage") int cpage, @Param("size") int size);
}
package test;
public class Test01 {
public static void main(String[] args) {
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = studentMapper.selectLimit((1 - 1) * 3, 3);
list.forEach(System.out::println);
DaoUtil.closeSqlSession(sqlSession);
}
}
方式2: 使用RowBounds集合来保存分页需要数据,来进行分页
package mapper;
public interface StudentMapper {
// 逻辑分页,减少对磁盘的读取,但是占用内存空间大
@Select("select * from student")
public List<Student> findStudentRowBounds(RowBounds rb);
}
package test;
public class Test01 {
public static void main(String[] args) {
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
RowBounds rb = new RowBounds((1 - 1) * 3, 3);
List<Student> list = studentMapper.findStudentRowBounds(rb);
list.forEach(System.out::println);
DaoUtil.closeSqlSession(sqlSession);
}
}
方式3: 使用分页插件来进行分页【推荐】
package mapper;
public interface StudentMapper {
// 分页插件(推荐)
@Select("select * from student")
public List<Student> findStudentPageHelper();
}
package test;
public class Test01 {
public static void main(String[] args) {
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// PageHelper分页插件
// (页码,每页多少个)
// 分页第一页少做一次计算,sql语句也不同
Page<Object> page = PageHelper.startPage(10, 1);
// 获取page对象
System.out.println(page);
List<Student> list = studentMapper.findStudentPageHelper();
// 详细分页对象
PageInfo<Student> pageinfo = new PageInfo<Student>(list, 10);
System.out.println(pageinfo);
list.forEach(System.out::println);
DaoUtil.closeSqlSession(sqlSession);
}
}
延迟加载和立即加载
什么是立即加载?
立即加载是: 不管用不用信息,只要调用,马上发起查询并进行加载
比如: 当我们查询学生信息时,就需要知道学生在哪个班级中,所以就需要立马去查询班级的信息
通常:当 一对一或者 多对一 的时候需要立即加载
什么是延迟加载?
延迟加载是: 在真正使用数据时才发起查询,不用的时候不查询,按需加载(也叫 懒加载)
比如: 在查询班级信息,每个班级都会有很多的学生(假如每个班有100个学生),如果我们只是查看 班级信息,但是学生对象也会加载到内存中,会造成浪费。 所以我门需要进行懒加载,当确实需要查看班级中的学生信息,我门在进行加载班级中的学生信息。
通常: 一对多,或者多对多的是需要使用延迟加载
延迟加载/懒加载的配置
如果设置 lazyLoadingEnabled = false,则禁用延迟加载,会级联加载所有关联对象的数据
如果设置 lazyLoadingEnabled = true,默认情况下mybatis 是按层级延时加载的。
aggressiveLazyLoading = true,mybatis 是按层级延时加载 aggressiveLazyLoading = false,mybatis 按需求加载。
延迟加载的sqlmap
实现:
StudentMapper
@Results({ @Result(column = "classid", property = "classid"),
@Result(column = "classid", property = "clazz", one = @One(select = "mapper.ClazzMapper.selectAll")) })
@Select("select * from student")
public List<Student> findStudentAndClassid();
测试类
public class Test02 {
public static void main(String[] args) {
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = studentMapper.findStudentAndClassid();
Student stu = list.get(0);
System.out.println(stu);
// list.forEach(System.out::println);
DaoUtil.closeSqlSession(sqlSession);
}
}
发现这里执行了多条sql,但是我只需要List集合中第一个学生的所有数据
这里就需要进行懒加载!
将上面的StudentMapper改为:
// mybatis底层默认立即加载
// FetchType.DEFAULT 从配置文件进行读取加载
// FetchType.EAGER 立即加载
// FetchType.LAZY 延迟加载,懒加载
@Results({ @Result(column = "classid", property = "classid"),
@Result(column = "classid", property = "clazz", one = @One(select = "mapper.ClazzMapper.selectAll", fetchType = FetchType.LAZY)) })
@Select("select * from student")
public List<Student> findStudentAndClassid();
就解决了查询一个Student而执行了多条SQL的问题
缓存
什么是缓存?
缓存(cache),数据交换的缓冲区,当应用程序需要读取数据时,先从数据库中将数据取出,放置在缓冲区中,应用程序从缓冲区读取数据。
特点:数据库取出的数据保存在内存中,具备快速读取和使用。
限制:读取时无需再从数据库获取,数据可能不是最新的;导致数据不一致性。
缓存的术语
针对缓存数据:
命中 需要的数据在缓存中找到结果。
未命中 需要的数据在缓存中未找到,重新获取。
什么是MyBatis 缓存?
功能 减少Java Application 与数 据库的交互次数,从而提升程 序的运行效率;
方式 通过配置和定制。
缓存的适用性
适合使用缓存: 经常查询并且不经常改变的 数据的正确与否对最终结果影响不大的 比如:一个公司的介绍,新闻等
不适合用于缓存: 经常改变的数据 数据的正确与否对最终结果影响很大 比如商品的库存,股市的牌价等
缓存的分类
一级缓存
将数据放在SqlSession对象中,一般默认开启一级缓存
引入案例
StudentMapper
@Select("select * from student where sid=#{v}")
public Student findStudentBySid(int sid);
测试类
情况1:
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper stuMapper = sqlSession.getMapper(StudentMapper.class);
Student s1 = stuMapper.findStudentBySid(10);
System.out.println(s1);
System.out.println();
Student s2 = stuMapper.findStudentBySid(10);
System.out.println(s1 == s2);//true
DaoUtil.closeSqlSession(sqlSession);
从同一个SqlSession的一级缓存中拿的Student是同一个对象
情况2:从两个SqlSession的一级缓存中查询同一个对象,返回的不是同一个Student对象
【发生了一级缓存失效】
SqlSession sqlSession1 = DaoUtil.getSqlSession();
StudentMapper stuMapper1 = sqlSession1.getMapper(StudentMapper.class);
Student s1 = stuMapper1.findStudentBySid(10);
System.out.println(s1);
for (int i = 0; i < 100; i++) {
System.out.print(".");
}
System.out.println();
SqlSession sqlSession2 = DaoUtil.getSqlSession();
StudentMapper stuMapper2 = sqlSession2.getMapper(StudentMapper.class);
Student s2 = stuMapper2.findStudentBySid(10);
System.out.println(s2);
System.out.println(s1 == s2);// false
情况3:
清空SQLSession后,查询的不是同一个Student对象
【发生了一级缓存失效】
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper stuMapper = sqlSession.getMapper(StudentMapper.class);
Student s1 = stuMapper.findStudentBySid(10);
System.out.println(s1);
for (int i = 0; i < 100; i++) {
System.out.print(".");
}
System.out.println();
sqlSession.clearCache();//清空SqlSession()
Student s2 = stuMapper.findStudentBySid(10);
System.out.println(s2);
System.out.println(s1 == s2);// false
DaoUtil.closeSqlSession(sqlSession);
关闭sqlsession 或者清空sqlsession缓存都可以实现
注意:当调用sqlsession的修改,添加,删除,commit(),close() 等方法时, 就会清空一级缓存
一级缓存的配置
一级缓存的工作流程
一级缓存失效的情况
1.不同SqlSession对应不同的一级缓存
2.同一个SqlSession单查询条件不同
3.同一个SqlSession两次查询期间执行了任何一次增删改操作
4.同一个SqlSession两次查询期间手动清空了缓存
案例:
MappertStudent
@Insert("insert into student(sname) values (#{sname})")
public int addStudent(Student s);
@Select("select * from student where sid=#{v}")
public Student findStudentBySid(int sid);
package test;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import bean.Student;
import dao.DaoUtil;
import mapper.StudentMapper;
public class Test03 {
public static void main(String[] args) {
SqlSession sqlSession = DaoUtil.getSqlSession();
StudentMapper stuMapper = sqlSession.getMapper(StudentMapper.class);
Student s1 = stuMapper.findStudentBySid(10);
System.out.println(s1);
for (int i = 0; i < 100; i++) {
System.out.print(".");
}
stuMapper.addStudent(new Student());
System.out.println();
// sqlSession.clearCache();//清空SqlSession()
Student s2 = stuMapper.findStudentBySid(10);
System.out.println(s2);
System.out.println(s1 == s2);// false
}
}
这里在两个查询之间进行了插入insert数据操作,就使一级缓存失效了,第二次查询的数据不是从缓存中拿,而是从数据库中去查询。
二级缓存
二级缓存就是在SqlSessionFactory,然后通过同一个Factory工厂,去获得相同的Cache,通过namespace去拿到对应的Student对象
XML实现
在mybatis中进行配置的参数说明:
<?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>
<properties resource="jdbc.properties"></properties>
<settings>
<!-- 全局启用或者禁用延迟加载
<setting name=" lazyLoadingEnabled" value="true" />
当启用时有延迟加载属性的对象会在被调用时按需进行加载,如果设置为false,会按层级进行延迟加载,默认为true
<setting name=" aggressiveLazyLoading" value="true" /> -->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<!-- 给单个类起别名 -->
<!-- <typeAlias alias="Student" type="bean.Student"/> -->
<!-- 批量别名定义,包扫描,别名为类名,扫描整个包下的类 -->
<package name="bean" />
</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.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注册sqlmapper文件 -->
<!-- 1.同包 接口和sqlMapper 2.同名 接口和sqlMapper 3.sqlMapper的namespace指向接口的类路径 -->
<!-- <mapper resource="mapper/StudentMapper.xml" /> -->
<!-- <mapper class="mapper.StudentMapper"/> -->
<package name="mapper" />
</mappers>
</configuration>
step1:
设置为true
<settings>
<!-- 全局启用或者禁用延迟加载
<setting name=" lazyLoadingEnabled" value="true" />
当启用时有延迟加载属性的对象会在被调用时按需进行加载,如果设置为false,会按层级进行延迟加载,默认为true
<setting name=" aggressiveLazyLoading" value="true" /> -->
<setting name="cacheEnabled" value="true"/>
</settings>
step2:
表明这个映射文件开启了二级缓存
<cache/>
step3:
useCache="true"表明这条查询用到了二级缓存
<select id="findStudent" parameterType="int"
resultType="student" useCache="true">
select * from student where sid = #{value}
</select>
注解实现
step1:
<?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>
<properties resource="jdbc.properties"></properties>
<settings>
<!-- 全局启用或者禁用延迟加载
<setting name=" lazyLoadingEnabled" value="true" />
<setting name=" aggressiveLazyLoading" value="true" /> -->
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<!-- 给单个类起别名 -->
<!-- <typeAlias alias="Student" type="bean.Student"/> -->
<!-- 批量别名定义,包扫描,别名为类名,扫描整个包下的类 -->
<package name="bean" />
</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.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<!-- 注册sqlmapper文件 -->
<!-- 1.同包 接口和sqlMapper 2.同名 接口和sqlMapper 3.sqlMapper的namespace指向接口的类路径 -->
<!-- <mapper resource="mapper/StudentMapper.xml" /> -->
<!-- <mapper class="mapper.StudentMapper"/> -->
<package name="mapper" />
</mappers>
</configuration>
step2:
在接口前面加上@CacheNamespace(blocking = true),表示这个接口中的所有查询都是二级缓存
package mapper;
//让此处的所有内容都为二级缓存
@CacheNamespace(blocking = true)
public interface StudentMapper {
@Select("select * from student where sid=#{v}")
public Student findStudentBySid(int sid);
}
案例:
说明使用到了二级缓存,需要实体类实现序列化接口
序列化后的两个student对象不是同一个对象,二级缓存的数据存在磁盘上。
二级缓存的缺点
当数据库服务器和客户端是通过网络传输的,这里用二级缓存是为了减少由于网络环境不好加载时间。主要是为了解决数据库不在本机,且网络不稳定带来的问题,但是现在不推荐使用
1.Mybatis 的二级缓存相对于一级缓存来说, 实现了缓存数据的共享,可控性也更强;
2.极大可能会出现错误数据,有设计上的缺陷, 安全使用的条件比较苛刻;
3.分布式环境下,必然会出现读取到错误 数据,所以不推荐使用。
分布式就是同一个数据库连接多台服务器,给多个用户服务。二级缓存在分布式情况下必然会出错,二级缓存绝对不可能用。
但是现在基本不用,弊端如下:
案例完整代码:
bean.Student实体类
package bean;
import java.io.Serializable;
import java.util.Date;
public class Student implements Serializable{
private int sid;
private String sname;
private Date birthday;
private String Ssex;
private int classid;
private Clazz clazz;
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSsex() {
return Ssex;
}
public void setSsex(String ssex) {
Ssex = ssex;
}
public int getClassid() {
return classid;
}
public void setClassid(int classid) {
this.classid = classid;
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
public Student(int sid, String sname, Date birthday, String ssex, int classid, Clazz clazz) {
super();
this.sid = sid;
this.sname = sname;
this.birthday = birthday;
Ssex = ssex;
this.classid = classid;
this.clazz = clazz;
}
public Student() {
super();
}
@Override
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + ", birthday=" + birthday + ", Ssex=" + Ssex + ", classid="
+ classid + ", clazz=" + clazz + "]";
}
}
Daoutil工具类
package dao;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class DaoUtil {
static SqlSessionFactory factory = null;
static {
try {
// 1.读取配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 2.生产sqlSession的工厂
factory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static SqlSession getSqlSession() {
// 3.返回sqlSession对象
return factory.openSession();
}
public static void closeSqlSession(SqlSession sqlSession) {
// 4.释放资源
sqlSession.close();
}
}
StudentMapper
package mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.Update;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.jdbc.SQL;
import org.apache.ibatis.mapping.FetchType;
import org.apache.ibatis.session.RowBounds;
import bean.Student;
//让此处的所有内容都为二级缓存
@CacheNamespace(blocking = true)
public interface StudentMapper {
@Insert("insert into student(sname) values (#{sname})")
public int addStudent(Student s);
// 物理分页,多次读取磁盘,占用内存小
@Select("select * from student limit #{cpage},#{size}")
public List<Student> selectLimit(@Param("cpage") int cpage, @Param("size") int size);
// 逻辑分页,减少对磁盘的读取,但是占用内存空间大
@Select("select * from student")
public List<Student> findStudentRowBounds(RowBounds rb);
// 分页插件(推荐)
@Select("select * from student")
public List<Student> findStudentPageHelper();
// mybatis底层默认立即加载
// FetchType.DEFAULT 从配置文件进行读取加载
// FetchType.EAGER 立即加载
// FetchType.LAZY 延迟加载,懒加载
@Results({ @Result(column = "classid", property = "classid"),
@Result(column = "classid", property = "clazz", one = @One(select = "mapper.ClazzMapper.selectAll", fetchType = FetchType.LAZY)) })
@Select("select * from student")
public List<Student> findStudentAndClassid();
@Select("select * from student where sid=#{v}")
public Student findStudentBySid(int sid);
}
测试类
package test;
import org.apache.ibatis.session.SqlSession;
import bean.Student;
import dao.DaoUtil;
import mapper.StudentMapper;
public class Test04 {
public static void main(String[] args) {
SqlSession sqlSession1 = DaoUtil.getSqlSession();
StudentMapper stuMapper1 = sqlSession1.getMapper(StudentMapper.class);
Student s1 = stuMapper1.findStudentBySid(10);
System.out.println(s1);
DaoUtil.closeSqlSession(sqlSession1);
SqlSession sqlSession2 = DaoUtil.getSqlSession();
StudentMapper stuMapper2 = sqlSession2.getMapper(StudentMapper.class);
Student s2 = stuMapper2.findStudentBySid(10);
System.out.println(s1);
DaoUtil.closeSqlSession(sqlSession2);
}
}
自定义缓存的分类
总结(面试题汇总):
一级缓存和二级缓存的区别:
一级缓存指的是一个对象存到了SqlSession里面了,它是内存式的缓存,写在内存上的
二级缓存指的是缓存在SqlSessionFactory里面了,它是写在磁盘上的
二级缓存不用的原因:
分布式环境下,必然会出现读取到错误 数据,所以不推荐使用。
分页查询
• 什么是缓存
• 数据交换的缓冲区,当应用程序需要读取数据时,先从数据库中将数据取出,放置在缓冲区中,应用程序从缓冲区读取数据;
• 什么是一级缓存
• 相对同一个 SqlSession 对象而言的缓存;
• 什么是二级缓存
• 一个 namespace 下的所有操作语句,都影响着同一个Cache;
• 自定义缓存的方式
• 实现 org. apache. ibatis. cache. Cache 接口自定义缓存;
• 引入 Redis 等第三方内存库作为 MyBatis 缓存。
补充:
缓存击穿、雪崩、穿透
缓存击穿、雪崩、穿透