Mybatis框架介绍与基本使用笔记
注意:一般的一个Maven
工程首先注入的依赖包含数据库驱动依赖,日志依赖,测试依赖
domain
中的实体类实现serizlizable
接口序列化的原因:
最重要的两个原因是:
1、将对象的状态保存在存储媒体中以便可以在以后重新创建出完全相同的副本;
2、按值将对象从一个应用程序域发送至另一个应用程序域。
实现serializable接口的作用是就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没实现序列化怎么才能进行网络传输呢,要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化,如果你不需要分布式应用,那就没那个必要实现序列.
namespace:名称空间;写接口的全类名;相当于告诉Mybatis
这个配置文件是实现哪个接口的;
一、Mybatis相关配置
自己预设定的配置文件:
1.1 config(mybatis-config.xml)全局配置文件
1 |
|
1.1.1 通过properties标签引入外部配置信息(动态配置)
1 |
|
jdbcConfig.properties
1 | jdbc.driver=com.mysql.jdbc.Driver |
1.2 Mapper
Mapper
是具体某张表的映射。
1 |
|
通过实例 – 由Mybatis
创建实现Dao
的接口,进行数据库的查询时,在mapper
中只有id
是无法定位到具体数据库访问的方法的,所以要加上namespace
,限定住工作空间(即限定住全类名,一个类中,方法名是唯一的)。可以简单理解为namespace
是定位到具体的一个dao
接口,id
是定位到当前这个dao
下的具体方法。
1.3 log4j的日志配置文件 – log4j.properties
将log4j
配置文件放入resource资源目录下,记录日志,对日志的操作。
1 | # Set root category priority to INFO and its only appender to CONSOLE. |
1.4 表的初始创建
1 | create DATABASE mybatis; |
二、Mybatis的注意事项
- 两个配置文件的编写
config
(全局环境Mybatis
配置文件)mapper
(映射配置文件) – 要与dao
的目录结构相同select
中的,resultType
的作用就是返回封装的位置,如果你要是不写的话,最后mybatis
是不知道你到底要封装到哪里,会出现错误,我这个是User
表,查询的也是这个,最后返回的结果就封装在User
类中id
要写对应dao
中的方法名称。
三、快速入门 – 实现user表中的全部用户信息的查询
3.1 实体类编写
1 | public class User implements Serializable { |
3.2 dao层接口的编写
1 | public interface UserDao { |
3.3 UserMapper.xml配置文件的创建
1 |
|
3.4 测试方法
1 | public class MybatisTest { |
其中Resources
(ibatis
)类默认加载resource
资源路径下的配置文件。
四、路径问题与代理dao的方式解析
- 绝对路径:
d:/xxx/xxx/xml–> 采用:类加载器,它只能读取类路径的配置文件 - 相对路径:
src/java/main/xxx.xml(在web项目下没有src目录文件) –> 使用ServletContext
对象的getRealPath()
获取web
下真实运行的类的路径。
1 | // 2.创建SqlSessionFactory工厂 |
以上创建工厂使用了构建者模式。 — builder
就是构建者。
构建者模式:把对象的创建细节隐藏,是使用者直接调用方法即可拿到对象。
1 | // 3.使用工厂生产SqlSession对象 |
通过factory
生产SqlSession
使用了工厂模式。优势:解耦(降低类之间的依赖关系)。没有用new
,不需重新边编译,解决了类之间的依赖关系。通过工厂生产对象。
1 | // 4.使用SqlSession创建Dao接口的代理对象 |
创建Dao
接口实现类使用了代理模式。优势:不修改源码的基础上对已有方法增强。
代理dao的方式解析
- 连接数据库信息 – 用于创建
connection
对象。 - 同时在
config
配置中的有了mapper
就有了所需要映射的信息(位置) - 映射到
Mapper
文件,即可查询到全类名,id(接口方法),sql语句。便能获取到PreparedStatement
。
对以上进行读取配置文件(用到解析XML的技术) – 此处用到的是dom4j
解析xml
的技术
继而:
根据配置文件的信息创建Connection对象
注册驱动,获取连接
获取预处理对象PreparedStatement
此时需要SQL语句 –> 从3中的sql语句中获取
执行查询
此节涉及源码,待我理解完了,再填上。
五、Mybatis的增删改查
在Mapper
配置文件中,增加配置信息,其中id
为接口中的方法名.
如果在测试类中,自定义方法上@Before
表示在测试方法前执行;@After
表示在测试方法之后执行。
sqlSession.commit()
– 手动提交事务。resultType
的指定是在对数据库的结果进行封装时的返回值类型,告诉Mybatis
应该封装到哪里的参数。parameterType
指查询的时候查询参数类型
5.1 findAll() – 查找全部用户的信息
1 | <select id="findAll" resultType="cn.lizhi.mybatis_01.domain.User"> |
resultType
为全类名,表示需要封装到的对象。
5.2 saveUser – 保存用户信息(插入用户)
1 | <insert id="saveUser" parameterType="cn.lizhi.mybatis_01.domain.User"> |
parameterType
为提供属性的domain
中的对象全类名。values
中是写{}
是写domain
对象中的属性名称。
如果要想获得当前保存的信息的id
,其使用方法是:
select last_insert_id()
– 获取最后一条插入语句的id
1 | <insert id="saveUser" parameterType="cn.lizhi.mybatis_01.domain.User"> |
5.3 updateUser – 更新用户
1 | <update id="updateUser" parameterType="cn.lizhi.mybatis_01.domain.User"> |
5.4 deleteUser – 删除用户
1 | <delete id="deleteUser" parameterType="Integer"> |
在删除方法中parameterType
只指定id,没有像上面一样指定User
对象,是因为,我们这个是只对表进行操作,没有经过User
对象的取值或使用(即在delete中,指定了我所要删除的是哪张表,并指定了id,那么我就可以通过这两项信息去定位到我所要删除的那条记录)。
5.5 findById – 根据用户id查询一条记录
1 | <select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User"> |
5.6 findByName – 模糊查询
1 | <select id="findById" parameterType="String" resultType="cn.lizhi.mybatis_01.domain.User"> |
在进行MyBatis
时的模糊查询时,通配符需要需要设置在参数中,即和查询的参数组成字符串,而不是将通配符写在mapper
配置文件中。推荐用第一种,预编译可防止SQL
注入。
5.7 findTotal – 聚合函数的使用
1 | <select id="findTotal" resultType="int"> |
六、MyBatis的参数深入
6.1 parameterType(查询参数的输入类型)
Integer
,String
等简单类型OGNL表达式 Object Graphic Navigation Language
通过对象的取值方法来获取数组,在写法上将
get
省去。例如获取用户的名称:
类中的写法:user.getUsername();
OGNL表达式写法:user.username;
Mybatis中能直接写
username
,而不用user.
的原因是: 因为在parameterType中已经提供了属性所属的类,所以此时不需要写对象名。例如:
1
2
3<update id="updateUser" parameterType="c">
update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id};
</update> 上面的参数都是直接写属性名称,而没有通过
对象.属性
的方式进行获取值username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}
这里的用处:当我们的查询条件被封装成一个对象时,就需要采用OGNL表达式。
例如封装了一个对象
QueryVo
:1
2
3
4
5
6
7
8
9
10
11
12public class QueryVo {
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}此时将
QueryVo
作为查询对象时,mapper
配置文件应该写成:1
2
3<select id="findUserByVo" parameterType="cn.lizhi.mybatis_01.domain.QueryVo" resultType="cn.lizhi.mybatis_01.domain.User">
select * from user where username like #{user.username}
</select>{user.username}
的解释,根据上面的解释,我们知道user
是QueryVo
类的一个属性,我们可以直接获取属性,而username
是user
类的属性,所以可以继而通过对象.属性
(继续对ONGL表达式的嵌套使用)的方式获取。以上这种适用于由多个对象组成的查询条件,实现对数据库的进行查询
传递
pojo
对象Mybatis
使用ognl
表达式解析对象字段的值,#{}
或者${}
括号中的值为pojo
属性名称。其中对
pojo
和javaBean
两者的区别,参考链接:java对象 POJO和JavaBean的区别。可以简单的把pojo
理解为我们定义的实体类。传递
pojo
包装对象开发中通过
pojo
传递查询条件,查询条件时综合的查询条件,不仅包括用户查询条件还包含其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。Pojo
类中包含pojo
.例如上面的:根据用户名查询用户信息,查询条件放到
QueryVo
的user
属性中。
6.2 resultType(输出类型)
- 可以输出
Integer
、String
等简单类型 pojo
对象pojo
列表
问题一
在属性和
Mysql
数据库表中字段不统一时,会无法进行封装。注意:
windows
下的MySQL
不区分大小写;Linux
下的MySQL
严格区分大小写。解决方式:
在
Mapper
配置文件中的sql
语句中对操作字段起别名。别名和pojo
中的属性名称相同。例如:
select id as userId,username as userName,address as userAddress,sex as UserSex from user;
这种方式运行效率最高,因为在数据库层面进行了解决,速度更快。
配置查询结果的列名和实体类的属性名的对应关系
1
2
3
4
5
6
7
8
9<resultMap id="userMap" type="cn.lizhi.mybatis_01.domain.User">
<!-- 主键字段的对应 -->
<id property="userId" column="id"></id>
<!-- 非主键字段的对应 -->
<result property="userName" column="username"></result>
<result property="userAddress" column="address"></result>
<result property="userSex" column="sex"></result>
<result property="userBirthday" column="birthday"></result>
</resultMap>id
:唯一标志 – 随便填写。用于select
、insert
、delete
等配置的类型映射。type
:查询的实体类,所对应的实体类是哪一个 – 全类名举例使用:
1
2
3<select id="findAll" resultMap="userMap">
select * from user;
</select>即将以前的
resultType
替换成resultMap
,值匹配resultMap
配置中的id
,这样就能够进行映射匹配。这种方式能够提升开发效率。
6.3 配置别名文件
typeAliases
配置别名,用于配置domain
中类的别名 – 指定实体类别名
1 | <typeAliases> |
typeAlias
用于配置别名。type
属性指定的是实体类全限定类名。alias
属性指定别名,当指定了别名就不再区分大小写。
package
用于指定要配置别名的包,当指定后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写。 – 指定实体类别名
1 | <typeAliases> |
- 作用于接口,
package
标签是用于指定dao
接口所在的包,当指定了之后,就不需要再写mapper以及resource
或者class
.
1 | <mapper> |
七、Mybatis实现Dao层的开发
7.1 Dao实现类的使用
由于是自己写实现类,所以就不需要代理对象对我们的方法进行增强。
首先实现类的方法:
1 | public class UserDaoImpl implements UserDao { |
测试类:
1 |
|
7.2 session中各种操作数据库语句
1 | // 保存方法 |
7.3 源码分析
PreparedStatement
对象的执行方法execute
:执行CRUD
中的任意一种语句。它的返回值是一个boolean
类型,表示是否有结果集。有结果集是true
,没有结果集是false
。executeUpdate
:只能执行CUD
语句,查询语句无法执行。他的返回值是影响数据库记录的记录数。executeQuery
:只能执行select
语句,无法执行增删改。执行结果封装的结果集ResultSet
对象。
以后再补上…
八、Mybatis连接池与事务相关
连接池介绍:可以减少我们获取连接所消耗的时间。用于存储连接的一个容器。
- 容器其实就是一个集合对象,该集合必须是线程安全的,不能两个线程拿到同一连接。该集合还必须实现队列的特性 – 先进先出
8.1 Mybatis连接池
配置位置:
主配置文件
SqlMapConfig.xml
中的dataSource
标签,type
属性就是表示采用何种连接池方式。type
属性:POOLED
:采用传统的javax.sql.DataSource
规范中的连接池,Mybatis
中有针对规范的实现(每次从池中获取连接,连接完以后归还)UNPOOLED
:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource
接口,但是并没有实现池的思想(每次创建一个连接来使用)JNDI
:采用服务器提供的JNDI
技术实现,来获取DataSource
对象,不同的服务器所能拿到DataSource
是不一样的。注意:如果不是
web
或者maven
的war
工程,是不能使用的。这里采用
dbcp
连接池。
Mybaits POOLED
的连接池原理:
8.2 事务
解决4个问题
- 什么是事务
- 事务的四大特性
ACID
- 不考虑隔离性会产生的3个问题
- 解决办法:四种隔离级别
Mybatis
底层实现还是借用JDBC
。
九、Mybatis动态sql
9.1 动态查询
动态查询(组合查询) – 查询条件不确定有没有的情况.
查询条件:可以根据username
、gender
、age
等多条件组合查询
Mapper
中的配置:
1 | <!-- 根据条件进行查询 --> |
根据可能提供的条件,进行组合查询(这些条件可能存在也可能不存在),例如:
主查询代码:
1 |
|
对应表的配置文件查询:
1 | <select id="findUserByCondition" parameterType="cn.lizhi.mybatis_01.domain.User" resultType="cn.lizhi.mybatis_01.domain.User"> |
9.2 子查询
子查询:根据被封装对象(例如queryvo)的id
集合,查询用户信息
1 | <!-- 根据queryvo中的Id集合实现查询用户列表 --> |
十、Mybatis中的多表查询
示例:用户和账户
- 一个用户可以有多个账户(一对多)
- 一个账户只能属于一个用户 (多个账户也可以属于同一个用户;一对一或者多对一)
步骤:
建立两张表:用户表、账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出一对多的关系
建立两个配置文件
- 用户的配置文件
- 账户的配置文件
注意:一张数据库表对应一个实体类且对应一个配置文件,并在相应的配置文件中进行配置操作。
实现配置
- 当我们查询用户时,可以同时得到用户下所包含的账户信息
- 当我们查询账户时,可以同时得到账户的所属用户信息
一的一方是主表;多的一方是主表。
10.1 Mybatis一对一的查询
查询所有账户同时包含用户名和地址信息
方式一:新建子类对象accountUser
继承account
accountUser
实体类中包含需要展示的User
实体类中的信息,例如需要展示username
,address
。那么accountUser
定义如下:
1 | public class AccountUser extends Account{ |
mapper
中配置的查询信息
1 | <!-- 新建子类对象accountUsr继承account --> |
具体使用,创建AccountMap.xml
配置文件。
1 |
|
测试方法:
1 |
|
结果:
1 | Account{id=41, uid=41, money=1000.0, user=null}AccountUser{username='老王', address='北京'} |
方式二:定义封装account
和user
的resultMap
例如在从表domain
中Account
的实体类:
1 | public class Account implements Serializable { |
在Mybatis
中多对一或一对一中,从表的实体类和主表的实体类关系:从表实体应该包含主表实体的对象应用。
定义封装account
和user
的resultMap
1 | <!-- 定义封装account和user的resultMap --> |
查询所有,一对一的查询(在查询账户时,也会查询出用户信息)
1 | <select id="findAll" resultMap="accountUserMap"> |
使用左外连接的查询语句,通过此种方式,其输出结果:
1 | <select id="findAll" resultMap="accountUserMap"> |
10.2 Mybatis一对多的查询
一对多映射:主表实体应该包含从表实体的集合引用。
Domain
中User
加入从表实体的集合引用。
1 | private Integer id; |
对应的mapper
配置文件:
1 | <!-- 定义封装User的resultMap --> |
1 | <select id="findAll" resultMap="userAccountMap"> |
在resultMap
中,需要注意两个id
中是否存在column
重名的情况,如果相同将其中一个进行修改(查询数据库时起别名)。查询时,建议将所有字段内容都查询出,便于对对象的封装(对象的封装的内容就是来源于查询的内容返回的内容,property
与column
对应)。
10.3 Mybatis 多对多查询
实例:用户和角色
- 一个用户可以有多个角色
- 一个角色可以赋予多个用户
步骤:
建立两张表:用户表、角色表
让用户表和角色表之间具备多对多的关系:需要使用中间表,中间表包含各自的主键,在中间表中是外键。
建立两个实体类:用户实体类和角色实体类
让用户和角色的实体类能体现出多对多的关系
各自包含对方一个集合引用
建立两个配置文件
- 用户的配置文件
- 角色的配置文件
实现配置
- 当我们查询用户时,可以同时得到用户下所包含的角色信息
- 当我们查询角色时,可以同时得到角色的所赋予的用户信息
两个实体类中各自加入多对多的实体关系映射。
第一种:查询所有角色,同时获取角色的所赋予的用户。即:在查询角色时,同时获取到它的全部用户信息。
以中间表作为连接的媒介。确定出查询角色时,同时获取角色下的全部用户信息(可以简单的看成一对多的查询关系)。这里角色表作为主表(用左外连接,保存主表的全部信息)
Mapper
配置同一对多。
第二种:查询所有的用户,同时获取用户的所拥有的角色。
- 思路同第一种
10.4 JNDI补充
模仿windows
的注册表。
插图
创建
Maven
的war
工程。在
webapp
下创建META-INF
,将context.xml
方入此目录下。替换原有的
SqlMapConfig
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
<!-- 导入约束 -->
<configuration>
<typeAliases>
<package name="com.itheima.domain"></package>
</typeAliases>
<!-- 配置mybatis的环境 -->
<environments default="mysql">
<!-- 配置mysql的环境 -->
<environment id="mysql">
<!-- 配置事务控制的方式 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置连接数据库的必备信息 type属性表示是否使用数据源(连接池)-->
<dataSource type="JNDI">
<property name="data_source" value="java:comp/env/jdbc/eesy_mybatis"/>
</dataSource>
</environment>
</environments>
<!-- 指定mapper配置文件的位置 -->
<mappers>
<mapper resource="com/itheima/dao/IUserDao.xml"/>
</mappers>
</configuration>将之前的测试方法写在
jsp
的java代码块中。原因是有
tomcat
服务器进行,将.jsp
翻译成.java
再进行编译,运行字节码文件,进而使用tomcat
服务器内部的资源连接池再通过Mybatis
访问数据库。
十一、Mybatis缓存相关
11.1 Mybatis中的延迟加载
问题:
在一对多中,当我们有一个用户,它有100个账户。(用户对象-user;accounts(集合,size=100))
问:在查询用户的时候,要不要把关联的账户查询出来?
答:在查询用户时,用户下的账户信息应该是,什么时候使用,什么时候查询的。
问:在查询账户的时候,要不要把关联的用户查询出来?
答:在查询用户时,账户的所属用户信息应该是随着账户查询时一起查询出来。
延迟加载:在真正使用数据时才发起查询,不用的时候不查询。按需加载(懒加载)。
立即加载:无论是否使用,只要一调用方法,马上发起查询。
在对应的四种表关系中:一对多,多对一,一对一,多对多
- 一对多,多对多:通常情况下都采用延迟加载。
- 多对一,一对一:通常情况下采用立即加载。
11.1.1 一对一(association)
延迟加载:
全局配置文件需设置
lazyLoadingEnabled
:true
aggressiveLazyLoading
:false
1 | <settings> |
mapper
的配置文件:
1 | <!-- 一对一的查询 --> |
select属性
:查询用户的唯一标识,这个例子中是关联信息的主键id
。例如这里account
表中的uid
对应着user
表中的id
。所以我们要使得uid
和id
进行匹配,故在user中的唯一标识就是id
。反之,如果我们通过查询user
表同时将其关联的account
表的信息一并查出,此时user
表中的id
对应着account
表中的uid
,那么uid
便是所要查询的关联表的唯一标识。
这里对唯一标识的理解是两个表之间相关联的字段。
column属性
:查询用户是根据id查询,而id对应的是account
表中的uid
,所需要的参数的值(外键)- 根据当前表中的uid去查询user表的记录(对应到user表中的主键)。即,拿着当前表的uid(外键),去user表中匹配主键,查询出记录。
cn.lizhi.mybatis_01.dao.UserDao.findById
:
1 | <select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User"> |
cn.lizhi.mybatis_01.dao.AccountDao.findAllByCount
1 | <select id="findAllByCount" resultMap="accountUserMap"> |
以上可以实现懒加载。
11.1.2 一对多(Collection)
使用方法同上:
1 | <!-- 一对多的查询 --> |
延迟加载的两点:
mapper
中内容的填写。即sql
语句的写法,resultMap
中标签的写法。- 开启全局配置文件的懒加载
11.2 Mybatis中的一级缓存
指的是Mybatis
中SqlSession
对象的缓存。当我们执行查询之后,查询的结果会同时存入到SqlSession
为我们提供一块区域中。该区域的结构是一个Map
。当我们再次查询同样的数据,Mybatis
会先去SqlSession
中查询是否有,有的话直接拿出来用。当SqlSession
对象消失时,Mybatis
的一级缓存也就消失了。
默认开启了一级缓存。当SqlSession
关闭(sqlSession.close()
)时,缓存会自动消失。
同时sqlSession.clearCache()
,也可以清楚缓存。
当数据库中的内容和缓存中数据不同时,Mybatis
的做法:
一级缓存是SqlSession
范围的缓存,当调用SqlSession
的修改,添加,修改,commit()
,close()
等方法时,就会清空一级缓存,直接从数据库中查询,并将查询的记录添加入缓存当中。
11.3 Mybatis中的二级缓存
指的是Mybatis
中SqlSessionFactory
对象的缓存。由同一个SqlSessionFactory
对象创建的SqlSession
共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis
框架支持二级缓存(在SqlMapConfig.xml
中配置,可以不用配置,默认是开启状态)
1 | <settings> |
第二步:让当前的映射文件支持二级缓存(在UserDao.xml
中配置)
1 | <!-- 开启user支持二级缓存--> |
第三部:让当前的操作支持二级缓存(在select
标签中配置,配置userCache
值为true
)
1 | <select id="findById" parameterType="Integer" resultType="cn.lizhi.mybatis_01.domain.User" useCache="true"> |
二级缓存中存放的内容是数据(json
数据),而不是对象。当发起查询时,创建一个新的对象,然后将二级缓存中的数据填充到对象当中,所以前后两次查询中对象不同。
十二、Mybatis的注解开发
<packaging>jar</packaging>
在Mybatis
中针对CRUD
的四个注解:
@Select
、@Insert
、@Update
、@Delete
在对应的Dao
方法中直接写上对应的注解,注解中(@Select)
写入操作数据库的语句即可。
注意:dao.XML
配置文件会和注解产生冲突。故不能混用(dao.xml
配置文件存放到其他地方或者不用)
12.1 Mybatis中的属性与字段不一致解决办法
使用注解@Results
例如:
1 | "userMap",value={ (id= |
id
用于表示是否是主键,默认值是false
。外部的id="userMap"
,用于指定唯一标识,可以让其他的注解进行复用。
如果需要让当前dao
下其他的方法也能使用,指定注解@ResultMap
.即:@ResultMap("userMap")
,其中为数组类型,可以指定多个,例如:@ResultMap(value={"userMap",...})
。可以理解为对应在dao.xml
配置文件中的resultMap
。
12.2 Mybatis中的多表查询 – 注解方式
12.2.1 一对一
思想同dao.xml
一样。通过配置@Results
注解实现。
1 | "select * from account") ( |
select
是指向如何查询封装对象的唯一标识 – 全限定类名.方法名。由于select
直接定位到方法名,查询到具体的对象,所以在@Select
语句中只查询了account
(account
中包含了user
的属性,且在下方的注解中指定了uid
去查询对应的user
对象,进行封装) ,注解的方式包含了加载的方式,故采用这种查询方式。
12.2.2 一对多
1 | "select * from user") ( |
以懒加载的形式,去理解这个查询语句及配置语句。
以上中,在@Result
属性中需要关注的属性是select
以及fetchType
。
12.3 Mybatis中的二级缓存 – 注解方式
在Mybaits
中一级缓存是默认打开的。
注解使用二级缓存的步骤:
- 二级缓存中,同样在全局配置中开启二级缓存。
- 在对应的
Dao
接口上配置全局的注解 –@CacheNamespace(blocking = true)