介绍
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybatis 是 Apache 软件基金会下的一个开源项目, 前身是 ibatis 框架。 2010 年这个项目由 apache 软件基金会迁移到 google code 下, 改名为 Mybatis。 2013 年 11 月又迁移到了 github
平时我们都用 JDBC 访问数据库,除了需要自己写 SQL 之外,还必须操作 Connection, Statement, ResultSet,这些其实只是手段的辅助类。 不仅如此,访问不同的表,还会写很多相同的代码,显得繁琐和枯燥。
那么用了 Mybatis 之后,只需要自己提供 SQL 语句,其他的工作,诸如建立连接 Statement, JDBC 相关异常处理等等都交给 Mybatis 去做了,那些重复性的工作 Mybatis 也给做掉了,开发者只需要关注在增删改查等操作层面上,而 Mybatis 把技术细节都封装在了我们看不见的地方。
框架原理
1、Mybatis 配置文件 SqlMapConfig.xml :此文件作为 mybatis 的全局配置文件,定义了 mybatis 运行的基础环境信息,如数据库链接信息等。mapper.xml 文件,这些文件是 sql 映射文件,文件配置了操作数据库的 sql 语句,此文件需要在 SqlMapConfig.xml 中配置加载。
2、通过 mybatis 环境等配置信息构造 SqlSessionFactory,即会话工厂。
3、由会话工厂创建 sqlSession 即会话,操作数据库需要通过 sqlSession 进行。
4、mybatis 底层自定义了 Executor 执行器接口操作数据库,Executor 接口有两个实现,一个是基本执行器、一个是缓存执行器。
5、Mapped Statement 也是 mybatis 一个底层封装对象,它包装了 mybatis 配置信息及 sql 映射信息等。mapper.xml 文件中一个 sql 对应一个 Mapped Statement 对象,sql 的 id 即是 Mapped statement 的 id。
6、Mapped Statement 对 sql 执行输入参数进行定义,包括 HashMap、基本类型、pojo,Executor 通过 Mapped Statement 在执行 sql 前将输入的 java 对象映射至 sql 中,输入参数映射就是 jdbc 编程中对 preparedStatement 设置参数。
7、Mapped Statement 对 sql 执行输出结果进行定义,包括 HashMap、基本类型、pojo,Executor 通过 Mapped Statement 在执行 sql 后将输出结果映射至 java 对象中,输出结果映射过程相当于 jdbc 编程中对结果的解析处理过程。
入门实现CRUD
JDK:1.8
mybatis:3.5.7
maven 工程
1、创建 maven 工程,导入依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <dependencies > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.27</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.4.5</version > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.22</version > </dependency > </dependencies > <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > true</filtering > </resource > <resource > <directory > src/main/resources</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > true</filtering > </resource > </resources > </build >
2、创建实体类 1 2 3 4 5 6 7 8 9 10 11 12 public class User { private Integer id; private String name; private String password; public User (Integer id, String name, String password) { this .id = id; this .name = name; this .password = password; } }
3、用户接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public interface UserDao { int saveUser (User user) ; void updateUserById (User user) ; List<User> findAll () ; int deleteById (int id) ; }
4、编写配置文件
在 resources 文件夹中,创建 Mybatis 的主配置文件 SqlMapConfig.xml。它是 mybatis 核心配置文件,配置文件内容为数据源、事务管理。 配置环境:
配置 mysql 的环境:
配置事务的类型;
配置连接池:配置连接数据库的 4 个基本信息;
指定映射配置文件的位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?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 ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/test?autoReconnect=true" /> <property name ="username" value ="root" /> <property name ="password" value ="root" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/mobaijun/dao/mapper/UserDao.xml" /> </mappers > </configuration >
5、映射文件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?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.mobaijun.dao.UserDao" > <insert id ="saveUser" parameterType ="rfam.User" > INSERT INTO user VALUES (#{id}, #{name}, #{password}); </insert > <update id ="updateUserById" parameterType ="rfam.User" > UPDATE user SET name =#{name}, password = #{password} WHERE id = #{id}; </update > <delete id ="deleteById" parameterType ="int" > DELETE FROM user WHERE id = #{id} </delete > <select id ="findAll" resultType ="rfam.User" > SELECT id,name,password FROM user </select > </mapper >
namespace:用来区别不同的类的名字
id: 标识映射文件中的 sql,称为 statement 的 id 将 sql 语句封装到 mappedStatement 对象中,所以将 id 称为 statement 的 id
sql:里面为表所有字段,可自定义或添加别名。
parameterType: 指定输入参数的类型.
resultType: 指定输出结果类型。mybatis 将 sql 查询结果的一行记录数据映射为 resultType 指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器 List 中
1 2 3 1、#{}: 一个占位符。preparedStatement 向占位符中设置值,自动进行 java 类型和 jdbc 类型转换。#{} 可以有效防止 sql 注入。 #{} 可以接收简单类型值或 pojo 属性值。 如果 parameterType 传输单个简单类型值,#{} 括号中可以是 value 或其它名称。 2、${}: 表示拼接 sql 串,通过 ${}可以将 parameterType 传入的内容拼接在 sql 中且不进行 jdbc 类型转换,${}可以接收简单类型值或 pojo 属性值,如果 parameterType 传输单个简单类型值,${}括号中只能是 value。
6、编写测试类
在 test->java 目录下创建测试类 com.mobaijun.test.MybatisTest。实现业务需求,共 7 步。
1 2 3 4 5 6 7 1. 扫描 mappper 配置文件 `SqlMapConfig.xml`2. 创建 `SqlSessionFactoryBuilder` 工厂3. 创建 `SqlSessionFactory` 工厂4. 创建 `SqlSession`,包含 `CRUD` 方法5. 获取 Mapper 接口的代理对象6. 使用代理执行 CRUD 操作7. 关闭资源
示例代码 利用工具类来获取sqlsession 避免重复代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package util;public class MybatisUtil { private static SqlSessionFactory ssf; static { String resource = "mybatis_config.xml" ; try { InputStream is = Resources.getResourceAsStream(resource); ssf = new SqlSessionFactoryBuilder ().build(is); } catch (IOException e) { throw new RuntimeException (e); } } public static SqlSession getSqlSession () { return ssf.openSession(); } }
注意增删改都需要提交事务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class MybatisTest { public void getUsers () { SqlSession session = MybatisUtil.getSqlSession(); UserDao ud = session.getMapper(UserDao.class); List<User> us = ud.findAll(); for (User u : us) { System.out.println(u); } session.commit(); session.close(); } public void deleteById () { SqlSession session = MybatisUtil.getSqlSession(); UserDao userDao = session.getMapper(UserDao.class); userDao.deleteById(1 ); session.close(); } public void updateUserById (User user) { SqlSession session = MybatisUtil.getSqlSession(); UserDao userDao = session.getMapper(UserDao.class); mapper.updateUserById(user); session.commit(); session.close(); } public void saveUser () { SqlSession session = MybatisUtil.getSqlSession(); UserDao userDao = session.getMapper(UserDao.class); User user; for (int i = 0 ; i < 100 ; i++) { user = new User (i+1 , "aa" , "bb" , "123456" ); log.info("代理对象:" + userDao.getClass()); userDao.saveUser(user); session.commit(); System.out.println(user); } session.close(); } }
利用Map传参/获取结果 当实体类或数据库中的表 字段过多 可以考虑使用map
示例: 映射文件和UserDao中新增 saveUserWithMap
sql语句中插入值可以通过map的键获取
1 2 3 4 <insert id ="saveUserWithMap" parameterType ="map" > INSERT INTO user VALUES (#{uid}, #{uname}, #{upwd}); </insert >
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void saveUserWithMap () { SqlSession session = MybatisUtil.getSqlSession(); UserDao ud = session.getMapper(UserDao.class); Map<String, Object> map = new HashMap <>(); map.put("uname" , "bbb" ); map.put("uid" , 2 ); map.put("upwd" , "5351" ); ud.saveUserWithMap(map); session.commit(); session.close(); }
配置文件 利用property引入外部/内部配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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 ="path/to/f.properties" > <property name ="username" value ="root" /> </properties > <environments default ="mysql" > <environment id ="mysql" > <transactionManager type ="JDBC" > </transactionManager > <dataSource type ="POOLED" > <property name ="driver" value ="com.mysql.cj.jdbc.Driver" /> <property name ="url" value ="jdbc:mysql://localhost:3306/test?autoReconnect=true" /> <property name ="username" value ="${username}" /> <property name ="password" value ="root" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="com/mobaijun/dao/mapper/UserDao.xml" /> </mappers > </configuration >
利用typeAlias取别名 这样在映射文件中使用别名就可以 减少冗余
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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 ="path/to/f.properties" > <property name ="username" value ="root" /> </properties > <typeAliases > <typeAlias type ="rfam.User" alias ="User" /> </typeAliases > </configuration >
也可以指定包名 然后在实体类中通过注解取别名
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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 ="path/to/f.properties" > <property name ="username" value ="root" /> </properties > <typeAliases > <typeAlias package ="rfam" /> </typeAliases > </configuration >
1 2 3 4 5 6 7 8 9 10 11 12 13 @Alias("hello") public class User { private Integer id; private String name; private String password; public User (Integer id, String name, String password) { this .id = id; this .name = name; this .password = password; } }
日志 STDOUT_LOGGING
: 标准输出LOG4J
, SLF4J
需要在maven中导入对应的包
1 2 3 4 5 6 7 8 9 10 11 12 13 <?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 ="path/to/f.properties" > <property name ="username" value ="root" /> </properties > <settings > <setting name ="logImpl" value ="STDOUT_LOGGING" /> </settings > </configuration >
生命周期与作用域
SqlSessionFactoryBuilder: 一旦创建就不再需要
SqlSessionFactory: 创建后在应用的运行期间一直存在 适合单例/静态模式
SqlSession: 用于连接连接池, 不是线程安全的,因此不能共享 最佳作用域为方法作用域;用完应释放资源
resultMap 结果集映射 用于将数据库列名映射到类字段名 来构建类对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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.mobaijun.dao.UserDao" > <resultMap id ="rmap" type ="rfam.User" > <result column ="pwd" property ="password" /> <result column ="name" property ="name" /> <result column ="id" property ="id" /> </resultMap > <select id ="findAll" resultMap ="rmap" > SELECT id,name,pwd FROM user </select > </mapper >
分页
使用sql中原生的limit进行分页
使用rowBounds 1 2 RowBounds rowBounds = new RowBounds (1 ,2 ); List<User> us = session.selectList("dao.UserDao.getUsers" , null , rowBounds);
注解开发
可以直接在接口上使用注解实现执行sql语句 1 2 3 4 public interface AnnoUserDao { @Select("select * from user") public List<User> getUsers () ; }
需要在配置文件中绑定接口 1 2 3 <mappers > <mapper class ="dao.AnnoUserDao" /> </mappers >
测试调用底层:反射,对mapper生成动态代理,用于处理后续的CRUD方法
CRUD 1 2 3 4 5 public interface AnnoUserDao { @Select("select * from user") public List<User> getUsers () ; }
多对一 对象当中还包含待查寻对象 (学生及其对应的老师) 测试环境:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 create table teacher ( id int (10 ) not null primary key, name varchar (30 ) null ) charset = utf8; create table student ( id int (10 ) not null primary key, name varchar (30 ) null , tid int (10 ) null , constraint fktid foreign key (tid) references teacher (id) ) charset = utf8; INSERT INTO student(id, name, tid) VALUES (1 , 'SA' , 1 ); INSERT INTO student(id, name, tid) VALUES (2 , 'SB' , 1 ); INSERT INTO student(id, name, tid) VALUES (3 , 'SC' , 1 );
1 2 3 4 5 6 7 8 9 10 public class Student { private int id; private String name; private Teacher teacher; public Student () { } }
1 2 3 4 5 6 7 public class Teacher { private int id; private String name; public Teacher () { } }
按照查询嵌套处理 查询学生信息:
获取所有学生信息
根据tid获取老师信息(子查询)
StudentMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <mapper namespace ="multi.dao.StudentMapper" > <select id ="getStudents" resultMap ="studentTeacher" > select * from student </select > <resultMap id ="studentTeacher" type ="multi.pojo.Student" > <result property ="id" column ="id" /> <result property ="name" column ="name" /> <association property ="teacher" column ="tid" select ="getTeacherById" javaType ="multi.pojo.Teacher" /> </resultMap > <select id ="getTeacherById" resultType ="multi.pojo.Teacher" > select * from teacher where id = #{id} </select > </mapper >
测试输出即可得到结果
1 2 3 Student{id=1 , name='SA' , teacher=Teacher{id=1 , name='Ms.Q' }} Student{id=2 , name='SB' , teacher=Teacher{id=1 , name='Ms.Q' }} Student{id=3 , name='SC' , teacher=Teacher{id=1 , name='Ms.Q' }}
按照结果嵌套处理 注意给所需的列名取别名
1 2 3 4 5 6 7 8 9 10 11 12 <select id ="getStudents2" resultMap ="st" > select student.id sid, student.name sname, tid, teacher.name tname from student, teacher where student.tid = teacher.id</select > <resultMap id ="st" type ="multi.pojo.Student" > <result property ="id" column ="sid" /> <result property ="name" column ="sname" /> <association property ="teacher" javaType ="multi.pojo.Teacher" > <result column ="tid" property ="id" /> <result column ="tname" property ="name" /> </association > </resultMap >
一对多 一个老师包含多个学生
1 2 3 4 5 public class Teacher { private int id; private String name; private List<Student> students; }
按照查询嵌套处理 查询老师信息:
获取所有老师信息
根据tid获取所有学生1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getTeachers2" resultMap ="ts2" > select * from teacher </select > <resultMap id ="ts2" type ="multi.pojo.Teacher" > <result column ="id" property ="id" /> <result column ="name" property ="name" /> <collection property ="students" javaType ="ArrayList" ofType ="multi.pojo.Student" column ="id" select ="getStudentByTeacher" /> </resultMap > <select id ="getStudentByTeacher" resultType ="multi.pojo.Student" > select * from student where tid = #{id} </select >
按照结果嵌套处理 TeacherMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="getTeachers" resultMap ="ts" > select student.id sid, student.name sname, tid, teacher.name tname from student, teacher where student.tid = teacher.id </select > <resultMap id ="ts" type ="multi.pojo.Teacher" > <result column ="tid" property ="id" /> <result column ="tname" property ="name" /> <collection property ="students" ofType ="multi.pojo.Student" > <result property ="id" column ="sid" /> <result property ="name" column ="sname" /> </collection > </resultMap >
测试输出即可得到结果
1 Teacher{id=1 , name='Ms.Q' , students=[Student{id=1 , name='SA' , teacher=null }, Student{id=2 , name='SB' , teacher=null }, Student{id=3 , name='SC' , teacher=null }]}
动态sql 就是指根据不同的条件生成不同的sql语句
环境搭建 1 2 3 4 5 6 7 CREATE TABLE blog ( id varchar (50 ) NOT NULL , title varchar (100 ) NOT NULL , author varcahr(30 ) NOT NULL , create_time datetime NOT NULL , views int (30 ) NOT NULL ) charset= utf8
Blog.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Blog { private String id; private String title; private String author; private Date createTime; private int views; public Blog () { } public Blog (String title, String author, Date createTime, int views) { this .id = UUID.randomUUID().toString().replace("-" , "" ); this .title = title; this .author = author; this .createTime = createTime; this .views = views; } }
添加数据 BlogMapper.xml
1 2 3 4 5 6 7 8 9 10 <?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 ="dynamic.dao.BlogMapper" > <insert id ="addBlog" parameterType ="dynamic.pojo.Blog" > insert into blog values (#{id}, #{title}, #{author}, #{createTime}, #{views}) </insert > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 SqlSession session = MybatisUtil.getSqlSession(); BlogMapper bm = session.getMapper(BlogMapper.class); String id = UUID.randomUUID().toString().replace("-" , "" ); Blog[] blogs = new Blog []{ new Blog ("TA" , "Kat" , new Date (), 100 ), new Blog ("TB" , "Lana" , new Date (), 500 ), new Blog ("TC" , "Bart" , new Date (), 368 ), new Blog ("TD" , "Kat" , new Date (), 100 ), }; for (Blog blog : blogs) { bm.addBlog(blog); } session.commit(); session.close();
if 通过if来拼接sql条件语句 if的判断条件写在test中
1 2 3 4 5 6 <select id ="getBlogs" parameterType ="Map" resultType ="dynamic.pojo.Blog" > select * from blog where 1=1 <if test ="author != null" > and author = #{author} </if > </select >
测试
1 2 3 4 5 6 7 8 9 SqlSession session = MybatisUtil.getSqlSession(); BlogMapper bm = session.getMapper(BlogMapper.class); Map<String, Object> map = new HashMap <>(); List<Blog> blogs = bm.getBlogs(map); for (Blog blog : blogs) { System.out.println(blog); } session.close();
map中不放内容 结果: 1 2 3 4 Blog{id=b25806c7dc91491ab6af58bf3753d2dc, title='TA' , author='Kat' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=100 } Blog{id=914bad57dafe42bc973f20522396777d, title='TB' , author='Lana' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=500 } Blog{id=19f3610a2d6845b9bb54e4c31409dd3d, title='TC' , author='Bart' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=368 } Blog{id=6e334e6bc75c4d8bb36042d378ca7005, title='TD' , author='Kat' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=100 }
map.put("author", "Kat");
结果: 1 2 Blog{id=b25806c7dc91491ab6af58bf3753d2dc, title='TA' , author='Kat' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=100 } Blog{id=6e334e6bc75c4d8bb36042d378ca7005, title='TD' , author='Kat' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=100 }
map.put("author", "Kat"); map.put("title", "TA");
结果: 1 Blog{id=b25806c7dc91491ab6af58bf3753d2dc, title='TA' , author='Kat' , createTime=Sun Feb 25 10 :53 :41 CST 2024 , views=100 }
choose, when, otherwise 类似switch语句 按序匹配 匹配到了就执行对应语句(仅一个) 然后结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getBlogs2" parameterType ="Map" resultType ="dynamic.pojo.Blog" > select * from blog <where > <choose > <when test ="author != null" > author = #{author} </when > <when test ="title != null" > and title = #{title} </when > </choose > </where > </select >
foreach 遍历集合类中的元素进行sql语句拼接
1 2 3 4 5 6 7 8 <select id ="getBlogs3" parameterType ="Map" resultType ="dynamic.pojo.Blog" > select * from blog <where > <foreach collection ="ids" item ="id" open ="(" close =")" separator ="or" > id = #{id} </foreach > </where > </select >
缓存 一级缓存 也叫本地缓存 :SqlSession本身具备 与数据库同一次会话期间查询到的数据会放入本地缓存中;如需再获取相同数据,从缓存中拿即可
缓存失效的情况:
查询不同的数据
增删改操作后,缓存会刷新
手动清理缓存: clearCache
二级缓存 也叫全局缓存,需在mapper.xml中添加<cache/>
会话查询的数据首先会进入一级缓存中
如果当前会话关闭,一级缓存中的数据会进入二级缓存
新的会话就可以从二级缓存中获取数据
不同mapper查询的数据会在自己对应的缓存中