目录
一、Redis实现分布式缓存
1、什么是缓存?
- 计算机
内存
中的一段数据
。
2、内存中数据特点
读写快
断电立即丢失
3、缓存解决了什么问题?
- 提高网站
吞吐量(请求响应)提高
, 网站运行效率快 缓存
的存在是用来解决数据库访问压力
4、既然缓存能提高效率, 所有项目中都加缓存吗?
- 使用缓存时一定是数据库中数据
极少发生修改, 更多用于查询情况
, 比如地址, 省, 市,
等
5、本地缓存
和分布式缓存
的区别?
- 本地缓存 : 存在
应用服务器内存中数据
称之为本地缓存(Local Cache)
— MyBatis中的一级/二级缓存就属于本地缓存 - 分布式缓存: 存储
在当前应用服务器内存之外的数据
称之为分布式缓存(Distribute Cache)
— Redis缓存输入分布式缓存, 因为它不局限于某个应用, 可以在多个应用之间使用
集群 :
将同一种服务的创建多个节点放在一起共同对系统提供服务的过程称之为集群
。
分布式 :
有多个不同服务集群
共同对系统提供服务
这个系统称之为分布式系统
6、利用MyBatis自身本地缓存结合Redis实现分布式缓存
跳转到目录
MyBatis的缓存相关详细请参考 : MyBatis一级、二级缓存
- mybatis中应用级缓存
(二级缓存)
,SqlSessionFactory
级别缓存, 所有会话共享
- 如何开启(二级缓存), mapper.xml 中写
<cache/>
标签, 在mybatis全局配置文件中
的<setting>
开启缓存打开本地缓存 - MyBatis中的一级/二级缓存的实现都是PerpetualCache, 查看Cache标签缓存实现
org.apache.ibatis.cache.impl.PerpetualCache
实现 - 自定义
RedisCache
实现- 通过mybatis默认cache源码得知, 可以使用自定义Cache类 implements Cache接口, 并对里面方法进行实现
public class RedisCache implements Cache { }
- 使用RedisCache实现
<cache type="com.zy.cache.RedisCache"/>
- 通过mybatis默认cache源码得知, 可以使用自定义Cache类 implements Cache接口, 并对里面方法进行实现
二、搭建SpringBoot和MyBatis整合测试
跳转到目录
注意: 这里连接的Redis
和MySQL
服务都在远程服务器上
1、pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<groupId>com.zy</groupId>
<artifactId>red</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--引入测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入依赖 spring data redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、application.yml 全局配置文件
spring:
redis:
host: 192.168.80.131
port: 6379
database: 0
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.80.131/redis_cache?characterEncoding=UTF-8
username: root
password: 1111
mybatis:
mapper-locations: classpath:com/zy/mapper/*.xml
type-aliases-package: com.zy.entity
logging:
level:
com:
zy:
dao: debug
3、entity, dao, service,启动类
@Data
@Accessors(chain = true) // 该注解的作用, user对象的set方法可以链式方式设置
public class User implements Serializable {
private String id;
private String name;
private Integer age;
private Date bir;
}
// ------------------------
public interface UserDao {
List<User> findAll();
}
// ------------------------
public interface UserService {
List<User> findAll();
}
@Service
@Transactional // 增删改要添加事务
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public List<User> findAll() {
return userDao.findAll();
}
}
// ------------------------
@SpringBootApplication
@MapperScan("com.zy.dao")
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
4、mapper文件
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zy.dao.UserDao">
<!--findAll-->
<select id="findAll" resultType="User">
SELECT id, name, age, bir FROM t_user
</select>
</mapper>
5、测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestUserService {
@Resource
private UserService userService;
@Test
public void test() {
List<User> users = userService.findAll();
users.forEach(user -> System.out.println("user = " + user));
System.out.println("------------------------------------");
userService.findAll().forEach(System.out::println);
}
}
在mybatis没有开启缓存, 每一次查询同样的sql语句, 都是从数据库查的
开启mybatis的二级缓存
, 在mapper.xml
文件中添加<cache/>
且实体类要实现序列化
标签即可
<!-- 开启mybatis的二级缓存 -->
<cache/>
- 此时第二次查询就是从缓存中获取了
注意:
mybatis虽然提供了这种缓存的方式, 但是这种方式只局限于当前的应用程序,当程序停止运行(JVM关闭)缓存就没了, 当再次启动程序后, 还是会去数据库中查询, 这种缓存属于本地缓存
MyBatis的缓存默认是实现public class PerpetualCache implements Cache
来实现的, 底层使用的是一个HashMap
;
- mybatis的缓存是基于
[namespace:sql语句:参数]
来进行缓存的,意思就是,SqlSession的HashMap
存储缓存数据时 - 使用
[namespace:sql:参数]
作为key
,查询返回的语句
作为value
保存的。
三、自定义RedisCache缓存
1、首先在mapper.xml文件中将<cache>
的类型改为自定义RedisCache
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zy.dao.UserDao">
<!-- 开启mybatis的二级缓存, 默认类型是 org.apache.ibatis.cache.impl.PerpetualCache -->
<!-- <cache/> -->
<!-- 使用自定义的RedisCache -->
<cache type="com.zy.cache.RedisCache"/>
<!--findAll-->
<select id="findAll" resultType="User">
SELECT id, name, age, bir FROM t_user
</select>
</mapper>
2、RedisCache
因为RedisCache
的实例化是由mybatis
来操作的, 并不是Spring容器
, 所以不能直接注入RedisTemplate; 此时的问题是如何拿到RedisTemplate
, 往putObject / getObject
方法中 放/取
值呢?
/**
* Description: 用来获取SpringBoot创建好的工厂
*
* @author
* @date Created on 2020/7/27 17:12
*/
@Configuration
public class ApplicationContextUtils implements ApplicationContextAware {
// 此时就是内部创建好的工厂
private static ApplicationContext applicationContext;
// 将创建好的工厂以参数的形式传递给这个类
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 提供在工厂中获取对象的方法
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
}
自定义RedisCache
/**
* Description: 自定义Redis缓存实现
*
* @author
* @date Created on 2020/7/27 16:55
*/
public class RedisCache implements Cache {
private final String id;
// 必须存在构造方法
public RedisCache(String id) {
// id 就是当前放入缓存的mapper的namespace ---> com.zy.dao.UserDao
System.out.println("id =============> " + id);
this.id = id;
}
// 返回cache的唯一标识
@Override
public String getId() {
return this.id;
}
// 往缓存中放值 --> 使用 RedisTemplate往缓存中放值
// key ---> -983043073:3242099914:com.zy.dao.UserDao.findAll:0:2147483647:SELECT id, name, age, bir FROM t_user:SqlSessionFactoryBean
@Override
public void putObject(Object key, Object value) {
System.out.println("key = " + key);
// 通过ApplicationContextUtils来获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用hash类型作为缓存存储模型 <key, <hashkey, value>> ==> <namespace <当前方法的key, 返回值>>
redisTemplate.opsForHash().put(id, key.toString(), value);
}
// 往缓存中取值, 这个key
// key ---> -983043073:3242099914:com.zy.dao.UserDao.findAll:0:2147483647:SELECT id, name, age, bir FROM t_user:SqlSessionFactoryBean
@Override
public Object getObject(Object key) {
System.out.println("key = " + key);
// 通过ApplicationContextUtils来获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 根据key从redis的hash类型中key获取数据
return redisTemplate.opsForHash().get(id, key.toString());
}
@Override
public Object removeObject(Object o) {
return null;
}
@Override
public void clear() {
}
@Override
public int getSize() {
return 0;
}
}
此时运行程序就会发现, 第一次用来查询数据库
, 然后将查询的语句存到Redis
中, 第二次就从Redis中获取了
;
此时当程序结束, 再次启动程序后, 两次查询都会丛Redis中获取了
我们再测试一个查询, 根据指定id
来查询, 一条信息
四、关于增删改的RedisCache的操作
- 当执行
增删改
会执行RedisCache的clear
方法, 清除Redis中的缓存
; 防止缓存中的数据是没有更新过的脏数据 !
// 当执行增删改会调用这个方法
@Override
public void clear() {
System.out.println("清空缓存");
}
- 所以我们要对这个方法进行
清空redis中的缓存
, 否则, 执行完删除操作
后, 数据库中的数据被删除了, 但是之前查询的操作还在redis中; 仍然可以查询出来
RedisCache类
/**
* Description: 自定义Redis缓存实现
*
* @author
* @date Created on 2020/7/27 16:55
*/
public class RedisCache implements Cache {
private final String id;
// 必须存在构造方法
public RedisCache(String id) {
// id 就是当前放入缓存的mapper的namespace ---> com.zy.dao.UserDao
System.out.println("id =============> " + id);
this.id = id;
}
// 返回cache的唯一标识
@Override
public String getId() {
return this.id;
}
// 往缓存中放值 --> 使用 RedisTemplate往缓存中放值
// key ---> -983043073:3242099914:com.zy.dao.UserDao.findAll:0:2147483647:SELECT id, name, age, bir FROM t_user:SqlSessionFactoryBean
@Override
public void putObject(Object key, Object value) {
// 使用hash类型作为缓存存储模型 <key, <hashkey, value>> ==> <namespace <当前方法的key, 返回值>>
getRedisTemplate().opsForHash().put(id, key.toString(), value);
}
// 往缓存中取值, 这个key
// key ---> -983043073:3242099914:com.zy.dao.UserDao.findAll:0:2147483647:SELECT id, name, age, bir FROM t_user:SqlSessionFactoryBean
@Override
public Object getObject(Object key) {
System.out.println("key = " + key);
// 根据key从redis的hash类型中key获取数据
return getRedisTemplate().opsForHash().get(id, key.toString());
}
// 为mybatis的保留方法, 默认没有实现
@Override
public Object removeObject(Object o) {
System.out.println("根据指定key删除缓存");
return null;
}
// 当执行增删改会调用这个方法
@Override
public void clear() {
System.out.println("清空缓存");
getRedisTemplate().delete(id); // 清空缓存
}
// 用来计算缓存数量
@Override
public int getSize() {
// 获取hash中key value的数量
return getRedisTemplate().opsForHash().size(id).intValue();
}
//封装redisTemplate
private RedisTemplate getRedisTemplate(){
//通过application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}