7.Mybatis缓存
发布日期:2021-05-08 11:39:01 浏览次数:18 分类:原创文章

本文共 14837 字,大约阅读时间需要 49 分钟。

第七部分:Mybatis缓存


缓存就是内存中的数据,常常来自于对数据库查询结果的保存,使用缓存,我们可以避免频繁地与数据库进行交互,


进而提高响应速度。


mybatis也提供对缓存的支持,分为一级缓存和二级缓存,可以通过以下的图进行解释



1.一级缓存是SqlSession级别的缓存,在操作时需要构造SqlSession对象,对象中有一个数据结构


HashMap用户存储数据,不同SqlSession的存储区域HashMap互不影响。


2.二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用一个


mapper缓存,二级缓存是跨SqlSession的


7.1 ⼀级缓存



①、在⼀个
sqlSession
中,对
User
表根据
id
进⾏两次查询,查看他们发出
sql
语句的情况


 



@Test


public
void
test1
(){


//
根据
sqlSessionFactory
产⽣
session


SqlSession sqlSession
=
sessionFactory
.
openSession
();


UserMapper userMapper
=
sqlSession
.
getMapper
(
UserMapper
.
class
);


//
第⼀次查询,发出
sql
语句,并将查询出来的结果放进缓存中


User u1
=
userMapper
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u1
);


//
第⼆次查询,由于是同⼀个
sqlSession,
会在缓存中查询结果


//
如果有,则直接从缓存中取出来,不和数据库进⾏交互


User u2
=
userMapper
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u2
);


sqlSession
.
close
();


}



一级缓存模型





、同样是对
user
表进⾏两次查询,只不过两次查询之间进⾏了⼀次
update
操作。


 @Testpublic void firstLevelCache()  {     //第一次查询id为1的用户     User user1=iUserMapper.findUserById(1);     User user = new User();     user.setId(1);     user.setUsername("zhangsan");     iUserMapper.updateUser(user);     //第二次查询     User user2=iUserMapper.findUserById(1);     System.out.println(user1==user2); }


控制台结果



缓存模型




③、
总结


1
、第⼀次发起查询⽤户
id

1
的⽤户信息,先去找缓存中是否有
id

1
的⽤户信息,如果没有,从 数据


库查询⽤户信息。得到⽤户信息,将⽤户信息存储到⼀级缓存中。


2
、 如果中间
sqlSession
去执⾏
commit
操作(执⾏插⼊、更新、删除),则会清空
SqlSession
中的 ⼀


级缓存,这样做的⽬的为了让缓存中存储的是最新的信息,避免脏读。


3
、 第⼆次发起查询⽤户
id

1
的⽤户信息,先去找缓存中是否有
id

1
的⽤户信息,缓存中有,直 接从


缓存中获取⽤户信息


 





 



⼀级缓存原理探究与源码分析


⼀级缓存到底是什么?⼀级缓存什么时候被创建、⼀级缓存的⼯作流程是怎样的?相信你现在应该会有


 


这⼏个疑问,那么我们本节就来研究⼀下⼀级缓存的本质


 


⼤家可以这样想,上⾯我们⼀直提到⼀级缓存,那么提到⼀级缓存就绕不开
SqlSession,
所以索性我们 


 


就直接从
SqlSession
,看看有没有创建缓存或者与缓存有关的属性或者⽅法


 





 



调研了⼀圈,发现上述所有⽅法中,好像只有
clearCache()
和缓存沾点关系,那么就直接从这个⽅ 法⼊


⼿吧,分析源码时
,我们要看它
(
此类
)
是谁,它的⽗类和⼦类分别⼜是谁
,对如上关系了解了,你才 会


对这个类有更深的认识,分析了⼀圈,你可能会得到如下这个流程图


 





 



再深⼊分析,流程⾛到
Perpetualcache
中的
clear()
⽅法之后,会调⽤其
cache.clear()
⽅法,那 么这个


cache
是什么东⻄呢?点进去发现,
cache
其实就是
private Map cache = new


HashMap()
;也就是⼀个
Map
,所以说
cache.clear()
其实就是
map.clear()
,也就是说,缓存其实就是


本地存放的⼀个
map
对象,每⼀个
SqISession
都会存放⼀个
map
对象的引⽤,那么这个
cache
是何 时创


建的呢?


你觉得最有可能创建缓存的地⽅是哪⾥呢?我觉得是
Executor
,为什么这么认为?因为
Executor
是 执


⾏器,⽤来执⾏
SQL
请求,⽽且清除缓存的⽅法也在
Executor
中执⾏,所以很可能缓存的创建也很 有可


能在
Executor
中,看了⼀圈发现
Executor
中有⼀个
createCacheKey
⽅法,这个⽅法很像是创 建缓存的


⽅法啊,跟进去看看,你发现
createCacheKey
⽅法是由
BaseExecutor
执⾏的,代码如


 




CacheKey cacheKey
=
new
CacheKey
();
//MappedStatement

id


// id
就是
Sql
语句的所在位置包名
+
类名
+ SQL
名称


cacheKey
.
update
(
ms
.
getId
());


// offset
就是
0 ,rowBounds是分页参数


cacheKey
.
update
(
rowBounds
.
getOffset
());


// limit
就是
Integer.MAXVALUE


cacheKey
.
update
(
rowBounds
.
getLimit
());


//
具体的
SQL
语句


cacheKey
.
update
(
boundSql
.
getSql
());


//
后⾯是
update

sql
中带的参数


cacheKey
.
update
(
value
);


...


if
(
configuration
.
getEnvironment
()
!=
null
) {


// issue #176


cacheKey
.
update
(
configuration
.
getEnvironment
().
getId
());


}







创建缓存
key
会经过⼀系列的
update
⽅法,
udate
⽅法由⼀个
CacheKey
这个对象来执⾏的,这个


update
⽅法最终由
updateList

list
来把五个值存进去,对照上⾯的代码和下⾯的图示,你应该能 理解


这五个值都是什么了


 





 


这⾥需要注意⼀下最后⼀个值,
configuration.getEnvironment().getId()
这是什么,这其实就是 定义在


mybatis-config.xml
中的标签,⻅如下。


 




<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>



 



那么我们回归正题,那么创建完缓存之后该⽤在何处呢?总不会凭空创建⼀个缓存不使⽤吧?绝对不会


的,经过我们对⼀级缓存的探究之后,我们发现⼀级缓存更多是⽤
于查询操作,毕竟⼀级缓存也叫做查


询缓存吧,为什么叫查询缓存我们⼀会⼉说
。我们先来看⼀下这个缓存到底⽤在哪了,我们跟踪到


query
⽅法如下:


 




Override


public
<
E
>
List
<
E
>
query
(
MappedStatement ms
,
Object
parameter
,
RowBounds


rowBounds
,
ResultHandler resultHandler
)
throws
SQLException
{


BoundSql boundSql
=
ms
.
getBoundSql
(
parameter
);


//
创建缓存


CacheKey key
=
createCacheKey
(
ms
,
parameter
,
rowBounds
,
boundSql
);


return
query
(
ms
,
parameter
,
rowBounds
,
resultHandler
,
key
,
boundSql
);


}


@SuppressWarnings
(
"unchecked"
)


Override


public
<
E
>
List
<
E
>
query
(
MappedStatement ms
,
Object
parameter
,
RowBounds


rowBounds
,
ResultHandler resultHandler
,
CacheKey key
,
BoundSql boundSql
)


throws
SQLException
{


...


list
=
resultHandler
==
null
?
(
List
<
E
>
)
localCache
.
getObject
(
key
) :
null
;


if
(
list
!=
null
) {


//
这个主要是处理存储过程⽤的。


handleLocallyCachedOutputParameters
(
ms
,
key
,
parameter
,
boundSql
);


}
else
{


list
=
queryFromDatabase
(
ms
,
parameter
,
rowBounds
,
resultHandler
,
key
,


boundSql
);


}


...


}


// queryFromDatabase
⽅法


private
<
E
>
List
<
E
>
queryFromDatabase
(
MappedStatement ms
,
Object
parameter
,


RowBounds rowBounds
,
ResultHandler resultHandler
,
CacheKey key
,
BoundSql


boundSql
)
throws
SQLException
{


List
<
E
>
list
;


localCache
.
putObject
(
key
,
EXECUTION_PLACEHOLDER
);


try
{


list
=
doQuery
(
ms
,
parameter
,
rowBounds
,
resultHandler
,
boundSql
);


}
finally
{


localCache
.
removeObject
(
key
);


}


localCache
.
putObject
(
key
,
list
);


if
(
ms
.
getStatementType
()
==
StatementType
.
CALLABLE
) {


localOutputParameterCache
.
putObject
(
key
,
parameter
);


}


return
list
;


}






如果查不到的话,就从数据库查,在
queryFromDatabase
中,会对
localcache
进⾏写⼊。
localcache


对象的
put
⽅法最终交给
Map
进⾏存放




private
Map
<
Object
,
Object
>
cache
=
new
HashMap
<
Object
,
Object
>
();


@Override


public
void
putObject
(
Object
key
,
Object
value
) {
cache
.
put
(
key
,
value
);


}



7.2 ⼆级缓存



⼆级缓存的原理和⼀级缓存原理⼀样,第⼀次查询,会将数据放⼊缓存中,然后第⼆次查询则会直接去


缓存中取。但是⼀级缓存是基于
sqlSession
的,⽽⼆级缓存是基于
mapper
⽂件的
namespace
的,也 就


是说多个
sqlSession
可以共享⼀个
mapper
中的⼆级缓存区域,并且如果两个
mapper

namespace



同,即使是两个
mapper,
那么这两个
mapper
中执⾏
sql
查询到的数据也将存在相同的⼆级缓存区域 中


 





 



如何使⽤⼆级缓存


 




开启⼆级缓存


 


和⼀级缓存默认开启不⼀样,⼆级缓存需要我们⼿动开启


 


⾸先在全局配置⽂件
sqlMapConfig.xml
⽂件中加⼊如下代码
:


 




<!--
开启⼆级缓存
-->


<settings>


<setting
name
=
"cacheEnabled"
value
=
"true"
/>


</settings>




 如果是使用mapper配置文件进行开发的就是用以下配置





<!--
开启⼆级缓存
-->


<cache></cache>




如果使用注解开发,在接口中加上@CacheNamespace注解



@CacheNamespacepublic interface IUserMapper {




我们可以看到
mapper.xml
⽂件中就这么⼀个空标签,其实这⾥可以配置
,PerpetualCache
这个类是


mybatis
默认实现缓存功能的类。我们不写
type
就使⽤
mybatis
默认的缓存,也可以去实现
Cache
接⼝


来⾃定义缓存。


注解实现具体化


@CacheNamespace(implementation = PerpetualCache.class)



 





 




public class
PerpetualCache
implements
Cache
{


private final
String
id
;


private
MapcObject
,
Object
>
cache
=
new
HashMapC
);


 


public
PerpetualCache
(
St ring id
) {
this
.
id
=
id
;


}



我们可以看到⼆级缓存底层还是HashMap结构




public class
User
implements
Serializable
(


//
⽤户
ID


private
int
id
;


//
⽤户姓名


private
String
username
;


}

 




开启了⼆级缓存后,还需要将要缓存的
pojo
实现
Serializable
接⼝,为了将缓存数据取出执⾏反序列化操


作,因为⼆级缓存数据存储介质多种多样,不⼀定只存在内存中,有可能存在硬盘中,如果我们要再取


这个缓存的话,就需要反序列化了。所以
mybatis
中的
pojo
都去实现
Serializable
接⼝


 



③、测试


⼀、测试⼆级缓存和
sqlSession
⽆关

 




@Test
public
void
testTwoCache
(){


//
根据
sqlSessionFactory
产⽣
session


SqlSession sqlSession1
=
sessionFactory
.
openSession
();


SqlSession sqlSession2
=
sessionFactory
.
openSession
();


 


UserMapper userMapper1
=
sqlSession1
.
getMapper
(
UserMapper
.
class
);


UserMapper userMapper2
=
sqlSession2
.
getMapper
(
UserMapper
.
class
);


//
第⼀次查询,发出
sql
语句,并将查询的结果放⼊缓存中


User u1
=
userMapper1
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u1
);


sqlSession1
.
close
();
//
第⼀次查询完后关闭
sqlSession


 


//
第⼆次查询,即使
sqlSession1
已经关闭了,这次查询依然不发出
sql
语句


User u2
=
userMapper2
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u2
);


sqlSession2
.
close
();



 可以看出上⾯两个不同的sqlSession,第⼀个关闭了,第⼆次查询依然不发出sql查询语句



⼆、测试执⾏
commit()
操作,⼆级缓存数据清空


 




@Test


public
void
testTwoCache
(){


//
根据
sqlSessionFactory
产⽣
session


SqlSession
sqlSession1
=
sessionFactory
.
openSession
();


SqlSession
sqlSession2
=
sessionFactory
.
openSession
();


SqlSession
sqlSession3
=
sessionFactory
.
openSession
();


String
statement
=
"com.lagou.pojo.UserMapper.selectUserByUserld"
;


UserMapper
userMapper1
=
sqlSession1
.
getMapper
(
UserMapper
.
class
);


UserMapper
userMapper2
=
sqlSession2
.
getMapper
(
UserMapper
.
class
);


UserMapper
userMapper3
=
sqlSession2
.
getMapper
(
UserMapper
.
class
);


//
第⼀次查询,发出
sql
语句,并将查询的结果放⼊缓存中


User u1
=
userMapperl
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u1
);


sqlSessionl
.
close
();
//
第⼀次查询完后关闭
sqlSession


 


//
执⾏更新操作,
commit()


u1
.
setUsername
(
"aaa"
);


userMapper3
.
updateUserByUserId
(
u1
);


sqlSession3
.
commit
();


 


//
第⼆次查询,由于上次更新操作,缓存数据已经清空
(
防⽌数据脏读
)
,这⾥必须再次发出
sql



User u2
=
userMapper2
.
selectUserByUserId
(
1
);


System
.
out
.
println
(
u2
);


sqlSession2
.
close
();



查看控制台情况:




④、
useCache

flushCache


mybatis
中还可以配置
userCache

flushCache
等配置项,
userCache
是⽤来设置是否禁⽤⼆级缓 存


的,在
statement
中设置
useCache=false
可以禁⽤当前
select
语句的⼆级缓存,即每次查询都会发出
sql


去查询,默认情况是
true,
即该
sql
使⽤⼆级缓存



<select
id
=
"selectUserByUserId"
useCache
=
"false"


resultType
=
"com.lagou.pojo.User"
parameterType
=
"int"
>


select * from user where id=#{id}


</select>


注解开发方式,在对应的接口方法上加上注解:



@Options(useCache = true)//启用二级缓存@Select("select * from user where id=#{id}")public User findUserById(Integer id);



这种情况是针对每次查询都需要最新的数据
sql,
要设置成
useCache=false
,禁⽤⼆级缓存,直接从数 据


库中获取。



mapper
的同⼀个
namespace
中,如果有其它
insert

update, delete
操作数据后需要刷新缓 存,如


果不执⾏刷新缓存会出现脏读。


设置
statement
配置中的
flushCache="true”
属性,默认情况下为
true,
即刷新缓存,如果改成
false
则 不


会刷新。使⽤缓存时如果⼿动修改数据库表中的查询数据会出现脏读。


 




<select
id
=
"selectUserByUserId"
flushCache
=
"true"
useCache
=
"false"


resultType
=
"com.lagou.pojo.User"
parameterType
=
"int"
>


select * from user where id=#{id}


</select>



 ⼀般下执⾏完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏



读。所以我们不⽤设置,默认即可


 

7.3 ⼆级缓存整合redis


上⾯我们介绍了 mybatis⾃带的⼆级缓存,但是这个缓存是单服务器⼯作,⽆法实现分布式缓存。 那么



什么是分布式缓存呢?假设现在有两个服务器
1

2,
⽤户访问的时候访问了
1
服务器,查询后的缓 存就


会放在
1
服务器上,假设现在有个⽤户访问的是
2
服务器,那么他在
2
服务器上就⽆法获取刚刚那个 缓


存,如下图所示:


 





 



为了解决这个问题,就得找⼀个分布式的缓存,专⻔⽤来存储缓存数据的,这样不同的服务器要缓存数


据都往它那⾥存,取缓存数据也从它那⾥取,如下图所示:


 





 



如上图所示,在⼏个不同的服务器之间,我们使⽤第三⽅缓存框架,将缓存都放在这个第三⽅框架中
,


然后⽆论有多少台服务器,我们都能从缓存中获取数据。


这⾥我们介绍
mybatis

redis
的整合。


刚刚提到过,
mybatis
提供了⼀个
eache
接⼝,如果要实现⾃⼰的缓存逻辑,实现
cache
接⼝开发即可。


mybati s
本身默认实现了⼀个,但是这个缓存的实现⽆法实现分布式缓存,所以我们要⾃⼰来实现。


redis
分布式缓存就可以,
mybatis
提供了⼀个针对
cache
接⼝的
redis
实现类,该类存在
mybatis-redis






 



1. pom
⽂件





<dependency>


<groupId>
org.mybatis.caches
</groupId>


<artifactId>
mybatis-redis
</artifactId>


<version>
1.0.0-beta2
</version>


</dependency>






2.
配置⽂件


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.lagou.mapper.IUserMapper"
>


<cache
type
=
"org.mybatis.caches.redis.RedisCache"
/>


<select
id
=
"findAll"
resultType
=
"com.lagou.pojo.User"
useCache
=
"true"
>


select * from user


</select>



 注解配置:



@CacheNamespace(implementation = RedisCache.class) //开启二级缓存public interface IUserMapper {



3.redis.properties


 




redis.host
=
localhost


redis.port
=
6379


redis.connectionTimeout
=
5000


redis.password
=


redis.database
=
0



4.测试




@Test


public
void
SecondLevelCache
(){


SqlSession sqlSession1
=
sqlSessionFactory
.
openSession
();
SqlSession sqlSession2
=
sqlSessionFactory
.
openSession
();


SqlSession sqlSession3
=
sqlSessionFactory
.
openSession
();


IUserMapper mapper1
=
sqlSession1
.
getMapper
(
IUserMapper
.
class
);


lUserMapper mapper2
=
sqlSession2
.
getMapper
(
lUserMapper
.
class
);


lUserMapper mapper3
=
sqlSession3
.
getMapper
(
IUserMapper
.
class
);


User user1
=
mapper1
.
findUserById
(
1
);


sqlSession1
.
close
();
//
清空⼀级缓存


 


User user
=
new
User
();


user
.
setId
(
1
);


user
.
setUsername
(
"lisi"
);


mapper3
.
updateUser
(
user
);


sqlSession3
.
commit
();


User user2
=
mapper2
.
findUserById
(
1
);


System
.
out
.
println
(
user1
==
user2
);


}

 




源码分析:


RedisCache
和⼤家普遍实现
Mybatis
的缓存⽅案⼤同⼩异,⽆⾮是实现
Cache
接⼝,并使⽤
jedis
操作缓


存;不过该项⽬在设计细节上有⼀些区别;


 




public final class
RedisCache
implements
Cache
{


public
RedisCache
(
final
String
id
) {


if
(
id
==
null
) {


throw new
IllegalArgumentException
(
"Cache instances require anID"
);


}


this
.
id
=
id
;


RedisConfig redisConfig
=


RedisConfigurationBuilder
.
getInstance
().
parseConfiguration
();


pool
=
new
JedisPool
(
redisConfig
,
redisConfig
.
getHost
(),


redisConfig
.
getPort
(),


redisConfig
.
getConnectionTimeout
(),


redisConfig
.
getSoTimeout
(),
redisConfig
.
getPassword
(),


redisConfig
.
getDatabase
(),
redisConfig
.
getClientName
());


}




RedisCache

mybatis
启动的时候,由
MyBatis

CacheBuilder
创建,创建的⽅式很简单,就是调⽤


RedisCache
的带有
String
参数的构造⽅法,即
RedisCache(String id)
;⽽在
RedisCache
的构造⽅法中,


调⽤了
RedisConfigu rationBuilder
来创建
RedisConfig
对象,并使⽤
RedisConfig
来创建
JedisPool



RedisConfig
类继承了
JedisPoolConfig
,并提供了
host,port
等属性的包装,简单看⼀下
RedisConfig



属性:




public class
RedisConfig
extends
JedisPoolConfig
{


private
String
host
=
Protocol
.
DEFAULT_HOST
;


private
int
port
=
Protocol
.
DEFAULT_PORT
;


private
int
connectionTimeout
=
Protocol
.
DEFAULT_TIMEOUT
;


private
int
soTimeout
=
Protocol
.
DEFAULT_TIMEOUT
;


private
String
password
;


private
int
database
=
Protocol
.
DEFAULT_DATABASE
;


private
String
clientName
;



 RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要⽅法:




public
RedisConfig
parseConfiguration
(
ClassLoader classLoader
) {


Properties config
=
new
Properties
();


InputStream input
=


classLoader
.
getResourceAsStream
(
redisPropertiesFilename
);


if
(
input
!=
null
) {


try
{


config
.
load
(
input
);


}
catch
(
IOException e
) {


throw new
RuntimeException
(


"An error occurred while reading classpath property '"


+
redisPropertiesFilename


+
"', see nested exceptions"
,
e
);


}
finally
{


try
{


input
.
close
();


}
catch
(
IOException e
) {


// close quietly


}


}


}


RedisConfig jedisConfig
=
new
RedisConfig
();


setConfigProperties
(
config
,
jedisConfig
);


return
jedisConfig
;


}


 核⼼的⽅法就是parseConfiguration⽅法,该⽅法从classpath中读取⼀个redis.properties⽂件




host
=
localhost


port
=
6379


connectionTimeout
=
5000


soTimeout
=
5000


password
=
database
=
0 clientName
=



并将该配置⽂件中的内容设置到
RedisConfig
对象中,并返回;接下来,就是
RedisCache
使⽤


RedisConfig
类创建完成
edisPool
;在
RedisCache
中实现了⼀个简单的模板⽅法,⽤来操作
Redis



 




private
Object
execute
(
RedisCallback callback
) {


Jedis jedis
=
pool
.
getResource
();


try
{


return
callback
.
doWithRedis
(
jedis
);


}
finally
{


jedis
.
close
();


}


}



 模板接⼝为RedisCallback,这个接⼝中就只需要实现了⼀个doWithRedis⽅法⽽已:




public interface
RedisCallback
{


Object
doWithRedis
(
Jedis jedis
);


}



接下来看看
Cache
中最重要的两个⽅法:
putObject

getObject
,通过这两个⽅法来查看
mybatis-redis


 


储存数据的格式:


 




@Override


public
void
putObject
(
final
Object
key
,
final
Object
value
) {


execute
(
new
RedisCallback
() {


@Override


public
Object
doWithRedis
(
Jedis jedis
) {


jedis
.
hset
(
id
.
toString
().
getBytes
(),
key
.
toString
().
getBytes
(),


SerializeUtil
.
serialize
(
value
));


return
null
;


}


});


}


@Override


public
Object
getObject
(
final
Object
key
) {


return
execute
(
new
RedisCallback
() {


 


@Override


public
Object
doWithRedis
(
Jedis jedis
) {


return
SerializeUtil
.
unserialize
(
jedis
.
hget
(
id
.
toString
().
getBytes
(),


key
.
toString
().
getBytes
()));


}


});


}



 可以很清楚的看到,mybatis-redis在存储数据的时候,是使⽤的hash结构,把cacheid作为这个hash




key (cache

id

mybatis
中就是
mapper

namespace)
;这个
mapper
中的查询缓存数据作为
hash


 



field,
需要缓存的内容直接使⽤
SerializeUtil
存储,
SerializeUtil
和其他的序列化类差不多,负责 对象


 


的序列化和反序列化;
上一篇:8.mybatis插件
下一篇:6.Mybatis注解开发

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2025年04月01日 07时13分43秒