我们先不开启二级缓存,然后使用两个SqlSession 来执行相同的操作。测试代码如下:
@org.junit.Test
public void Test2() throws Exception{
InputStream inputStream= Resources.getResourceAsStream(“SqlMapConfig.xml”);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSession();
SqlSession sqlSession1 = build.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
System.out.println(“第一次:”);
List users = mapper.selectAll();
System.out.println(users.get(0));
sqlSession.close();
System.out.println(“第二次:”);
List users2 = mapper1.selectAll();
System.out.println(users2.get(0));
System.out.println(“两对象是否一致”);
System.out.println(users.equals(users2));
}
这里我们注意一下,在第二次操作前,要先将第一次的SqlSession 关闭掉。知道为什么吗?因为事物的特性,大家可以试验下嘿嘿。
可以看到两个SqlSession 分别从数据库读取数据,并且两个对象是不一样的。我们现在开启二级缓存
在mapper.xml 文件中开启
在实体类对象中需要序列化对象,因为二级缓存不一定是放在内存中,也可能放在磁盘中。当然我们这里是否序列化都没有关系。
接下来就可以测试了,还是上面的代码,但是执行结果却不同了。
可以看到第一次是从数据库读取的,但是第二次就没有从数据库读取,而是从缓存中读取的数据这个0.5 表示命中率。说明不同的 SqlSession 是可以共享数据的。但是我们也会发现虽然第二次事从缓存中读取的,但是两个对象却不相等,这是为什么呢?因为二级缓存,存储的仅仅是数据,而不是对象,在其他Sqlsession 从缓存中取数据的时候,创建一个新的对象返回。而一级缓存是保存的对象,所以是相同的。
========================================================================
我们就不用考虑太复杂,手动实现以下只是为了我们更熟悉核心思想。每个SqlSession 一个缓存,缓存是使用的存储格式,以及失效生效场景。
我们首先借用 mybatis 中这几个类。CacheKey 主要是存储key的。而PerpetualCache 是实现缓存的实现类。
这里面主要就是重写 equals 方法和 hashCode 方法。所以这四个部分代码就不贴了。
我们直接来看Executor 类中我们怎么实现。
1、首先初始化 localCache 对象,这样不同的SqlSession 就是不同的缓存了。限制作用域。
2、query 方法,创建一个cacheKey ,判断 localCache 中是否存在这个 cacheKey ,存在直接从缓存中取出返回,不存在就从数据库中查询。
3、createCacheKey 方法,这里我们就把mapper.id ,boundSql,以及参数作为key 的关键信息。这里如果对代码有些看不明白的可以先参考我的另一篇文章,自己定义一个持久层框架。我们这里增加缓存的功能就是在这个框架上修改的。
public CacheKey createCacheKey(Mapper mapper, BoundSql boundSql, Object… parameter) throws Exception{
// 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// 设置 id
cacheKey.update(mapper.getId());
// 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
List parameterMappingList = boundSql.getParameterMappingList();
Class<?> parmType = mapper.getParmType();
for (int i = 0; i < parameterMappingList.size(); i++) {
Object value=getValue(parmType,parameterMappingList.get(i),parameter);
cacheKey.update(value);
}
return cacheKey;
}
private Object getValue(Class<?> parmType,ParameterMapping parameterMapping, Object… parameter) throws Exception {
Object value=null;
String content = parameterMapping.getContent();
if (isJavaType(parmType)) {
value=parameter[0];
}else {
Field declaredField = parmType.getDeclaredField(content);
declaredField.setAccessible(true);
value = declaredField.get(parameter[0]);
}
return value;
}
4、queryFromDatabase 方法,从数据库查询操作,会将结果保存到缓存中。
// 从数据库中读取操作
private List queryFromDatabase(Configuration configuration,Mapper mapper,CacheKey key, BoundSql boundSql,Object parameter) throws Exception {
List list;
try {
// 执行读操作
list = doQuery(configuration,mapper, boundSql,parameter);
} finally {
localCache.removeObject(key);
}
// 添加到缓存中
localCache.putObject(key, list);
return list;
}
5、update ()方法,需要增加清除缓存操作。
到这里我们就基本上实现了一级缓存的效果。这里我贴出 SimpleExecutor 类完整代码吧。都是在这个类中实现的。感兴趣的可以看下,不感兴趣的直接跳过吧。
public class SimpleExecuter implements Executer{
private Connection connection;
protected PerpetualCache localCache;
public SimpleExecuter(){
this.localCache = new PerpetualCache(“LocalCache”);
}
public List query(Configuration configuration, Mapper mapper, Object… parameter) throws Exception {
BoundSql boundSql=getBoundSql(mapper.getSql());
CacheKey key = createCacheKey(mapper,boundSql,parameter);
// 先从缓存中获取
List list=(List) localCache.getObject(key);
if (list != null) {
return list;
} else {
// 获得不到,则从数据库中查询
list = queryFromDatabase(configuration,mapper, key, boundSql,parameter);
}
return list;
}
// 从数据库中读取操作
private List queryFromDatabase(Configuration configuration,Mapper mapper,CacheKey key, BoundSql boundSql,Object parameter) throws Exception {
List list;
try {
// 执行读操作
list = doQuery(configuration,mapper, boundSql,parameter);
} finally {
localCache.removeObject(key);
}
// 添加到缓存中
localCache.putObject(key, list);
return list;
}
public List doQuery(Configuration configuration, Mapper mapper,BoundSql boundSql, Object… parameter) throws Exception{
PreparedStatement preparedStatement=preHandle(configuration,mapper,boundSql,parameter);
//执行sql
ResultSet resultSet = preparedStatement.executeQuery();
return resultHandle(mapper,resultSet);
}
@Override
public int update(Configuration configuration, Mapper mapper, Object… parameter) throws Exception {
// 清空本地缓存
clearLocalCache();
BoundSql boundSql=getBoundSql(mapper.getSql());
// 执行写操作
PreparedStatement preparedStatement=preHandle(configuration,mapper,boundSql,parameter);
return preparedStatement.executeUpdate();
}
public void clearLocalCache() {
// 清理 localCache
localCache.clear();
}
/**
-
创建cacheKey
-
@param mapper
-
@param boundSql
-
@param parameter
-
@return
-
@throws Exception
*/
public CacheKey createCacheKey(Mapper mapper, BoundSql boundSql, Object… parameter) throws Exception{
// 创建 CacheKey 对象
CacheKey cacheKey = new CacheKey();
// 设置 id
cacheKey.update(mapper.getId());
// 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
List parameterMappingList = boundSql.getParameterMappingList();
Class<?> parmType = mapper.getParmType();
for (int i = 0; i < parameterMappingList.size(); i++) {
Object value=getValue(parmType,parameterMappingList.get(i),parameter);
cacheKey.update(value);
}
return cacheKey;
}
/**
-
准备工作
-
连接数数据库
-
解析sql
-
替换占位符
-
@param configuration
-
@param mapper
-
@param parameter
-
@return
-
@throws Exception
*/
private PreparedStatement preHandle(Configuration configuration, Mapper mapper,BoundSql boundSql, Object… parameter)throws Exception{
String sql=boundSql.getSqlText();
List parameterMappingList = boundSql.getParameterMappingList();
//获取连接
connection=configuration.getDataSource().getConnection();
//获取preparedStatement,并传递参数值
PreparedStatement preparedStatement=connection.prepareStatement(sql);
Class<?> parmType = mapper.getParmType();
for (int i = 0; i < parameterMappingList.size(); i++) {
Object value=getValue(parmType,parameterMappingList.get(i),parameter);
preparedStatement.setObject(i+1,value);
}
System.out.println(sql);
return preparedStatement;
}
/**
-
获取参数对应的值
-
@param parmType
-
@param parameterMapping
-
@param parameter
-
@return
-
@throws Exception
*/
private Object getValue(Class<?> parmType,ParameterMapping parameterMapping, Object… parameter) throws Exception {
Object value=null;
String content = parameterMapping.getContent();
if (isJavaType(parmType)) {
value=parameter[0];
}else {
Field declaredField = parmType.getDeclaredField(content);
declaredField.setAccessible(true);
value = declaredField.get(parameter[0]);
}
return value;
}
private boolean isJavaType(Class<?> parmType){
String s = parmType.getSimpleName().toUpperCase();
return s.equals(“INTEGER”)|| s.equals(“STRING”)|| s.equals(“LONG”);
}
/**
-
封装结果集
-
@param mapper
-
@param resultSet
-
@param
-
@return
-
@throws Exception
*/
private List resultHandle(Mapper mapper,ResultSet resultSet) throws Exception{
ArrayList list=new ArrayList<>();
//封装结果集
Class<?> resultType = mapper.getResultType();
while (resultSet.next()) {
ResultSetMetaData metaData = resultSet.getMetaData();
Object o = resultType.newInstance();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
//属性名
String columnName = metaData.getColumnName(i);
//属性值
Object value = resultSet.getObject(columnName);
//创建属性描述器,为属性生成读写方法
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultType);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,value);
}
list.add((E) o);
}
return list;
}
/**
-
解析自定义占位符
-
@param sql
-
@return
*/
private BoundSql getBoundSql(String sql){
ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser = new GenericTokenParser(“#{”,“}”,parameterMappingTokenHandler);
String parse = genericTokenParser.parse(sql);
return new BoundSql(parse,parameterMappingTokenHandler.getParameterMappings());
}
public void close() throws Exception {
connection.close();
}
@Override
public void commit() throws Exception{
connection.commit();
}
}
我们一样的来写一个测试类
@org.junit.Test
public void Test11() throws Exception{
InputStream inputStream= Resources.getResources(“SqlMapperConfig.xml”);
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
System.out.println(“第一次:”);
List users = mapper.selectAll();
System.out.println(users.get(0));
System.out.println(“第二次:”);
List users2 = mapper.selectAll();
System.out.println(users2.get(0));
System.out.println(“两对象是否一致”);
System.out.println(users.equals(users2));
}
下面是执行结果
可以明显的看到,第二次是走的缓存,并且返回的对象是相同的。
我们接下来,在两次查询中间,增加一次修改操作。
@org.junit.Test
public void Test11() throws Exception{
InputStream inputStream= Resources.getResources(“SqlMapperConfig.xml”);
SqlSession sqlSession = new SqlSessionFactoryBuilder().build(inputStream).openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
System.out.println(“第一次:”);
List users = mapper.selectAll();
System.out.println(users.get(0));
System.out.println(“修改操作”);
User user=new User();
user.setUsername(“张三”);
user.setId(1);
mapper.update(user);
System.out.println(“第二次:”);
List users2 = mapper.selectAll();
System.out.println(users2.get(0));
System.out.println(“两对象是否一致”);
System.out.println(users.equals(users2));
}
结果如下:
可以看到这个时候第二次就没有从缓存中读取了,而是从数据库中读取的。
我们再来看看两个SqlSession 的能公用一级缓存不能。
@org.junit.Test
public void Test12() throws Exception{
InputStream inputStream= Resources.getResources(“SqlMapperConfig.xml”);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSqlSession();
SqlSession sqlSession1 = build.openSqlSession();
UserDao mapper = sqlSession.getMapper(UserDao.class);
UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
System.out.println(“第一次:”);
List users = mapper.selectAll();
System.out.println(users.get(0));
System.out.println(“第二次:”);
List users2 = mapper1.selectAll();
System.out.println(users2.get(0));
System.out.println(“两对象是否一致”);
System.out.println(users.equals(users2));
}
结果如下:
说明是没有公用了,那么到这里我们就验证了我们自定义的一级缓存,和 mybatis 的一级缓存的效果是一样的。这也是 mybatis 一级缓存的核心实现了。有兴趣的小伙伴可以是试试,并且可以试试怎么实现二级缓存啊哈哈。
===========================================================================
上面我们说二级缓存在分布式环境下存在问题。比喻说一个用户请求了这台服务,在这台服务上有缓存,但是另外一个用户请求了另一台服务,导致没有从缓存中获取,而是从数据库获取的。这就存在问题啦。
所以解决上述问题,我们很容易就想到使用 redis 做二级缓存,不管那台服务都会先从 redis 中查询,没有查到才会到数据库中查询,并包结果保存到缓存中。
你想到的事情,人家也想到了哈哈,所以有现成的实现工具,我们直接来使用试试。
1、增加依赖
org.mybatis.caches
mybatis-redis
1.0.0-beta2
2、mapper.xml 中type 指向我们的缓存
3、增加redis.properties 配置
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
上面这样就配置好了一个基于redis 的二级缓存啦。
我们这里简单测试一下,只要测试redis 缓存生效没有就可以了。
测试方法还是和上面一样:
@org.junit.Test
public void Test3() throws Exception{
InputStream inputStream= Resources.getResourceAsStream(“SqlMapConfig.xml”);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = build.openSession();
SqlSession sqlSession1 = build.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
System.out.println(“第一次:”);
List users = mapper.selectAll();
System.out.println(users.get(0));
sqlSession.close();
System.out.println(“第二次:”);
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
按照上面的过程,4个月的时间刚刚好。当然Java的体系是很庞大的,还有很多更高级的技能需要掌握,但不要着急,这些完全可以放到以后工作中边用别学。
学习编程就是一个由混沌到有序的过程,所以你在学习过程中,如果一时碰到理解不了的知识点,大可不必沮丧,更不要气馁,这都是正常的不能再正常的事情了,不过是“人同此心,心同此理”的暂时而已。
“道路是曲折的,前途是光明的!”
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
去过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-Deb8TjVi-1713475815771)]
[外链图片转存中…(img-pouX0ffD-1713475815773)]
[外链图片转存中…(img-dKZm9IAY-1713475815774)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
按照上面的过程,4个月的时间刚刚好。当然Java的体系是很庞大的,还有很多更高级的技能需要掌握,但不要着急,这些完全可以放到以后工作中边用别学。
学习编程就是一个由混沌到有序的过程,所以你在学习过程中,如果一时碰到理解不了的知识点,大可不必沮丧,更不要气馁,这都是正常的不能再正常的事情了,不过是“人同此心,心同此理”的暂时而已。
“道路是曲折的,前途是光明的!”
[外链图片转存中…(img-EHFte063-1713475815775)]
[外链图片转存中…(img-BhMFgsmR-1713475815776)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!