利用MyBatis自身本地缓存结合Redis实现分布式缓存 (一)

目录

一、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"/>

在这里插入图片描述

二、搭建SpringBoot和MyBatis整合测试

跳转到目录
注意: 这里连接的RedisMySQL服务都在远程服务器上

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;
    }
}
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值