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

一、Redis分布式缓存的实现

1、什么是缓存?
  • 计算机内存中的一段数据。
2、内存中数据特点
  • 读写快
  • 断电立即丢失
3、缓存解决了什么问题?
  • 提高网站吞吐量(请求响应)提高, 网站运行效率快
  • 缓存的存在是用来解决数据库访问压力
4、既然缓存能提高效率, 所有项目中都加缓存吗?
  • 使用缓存时一定是数据库中数据极少发生修改, 更多用于查询情况, 比如地址, 省, 市, 等
5、本地缓存和分布式缓存的区别?
  • 本地缓存 : 存在应用服务器内存中数据称之为本地缓存(Local Cache)
  • 分布式缓存: 存储在当前应用服务器内存之外的数据称之为分布式缓存(Distribute Cache)

集群 : 将同一种服务的多个节点放在一起共同对系统提供服务的过程称之为集群
分布式 : 有多个不同服务集群共同对系统提供服务这个系统称之为分布式系统

6、利用MyBatis自身本地缓存结合Redis实现分布式缓存
  • mybatis中应用级缓存(二级缓存), SqlSessionFactory级别缓存, 所有会话共享
  • 如何开启(二级缓存), mapper.xml中写<cache/>标签, 打开本地缓存
  • 查看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)
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(user -> System.out.println("user = " + user));
    }

}

在mybatis没有开启缓存, 每一次查询同样的sql语句, 都是从数据库查的
在这里插入图片描述
开启mybatis的二级缓存, 在mapper.xml文件中添加<cache/> 且实体类要实现序列化标签即可

    <!-- 开启mybatis的二级缓存 -->
    <cache/>

在这里插入图片描述

  • 此时第二次查询就是从缓存中获取了
  • mybatis虽然提供了这种缓存的方式, 但是这种方式只局限于当前的程序,当程序停止运行(JVM关闭)缓存就没了, 当再次启动程序后, 还是会去数据库中查询, 这种缓存属于本地缓存

MyBatis的缓存默认是实现public class PerpetualCache implements Cache来实现的, 底层使用的是一个HashMap;

在这里插入图片描述


三、自定义RedisCache缓存

1、首先在mapper.xml文件中将的类型改为自定义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 zygui
 * @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);
    }
}

/**
 * Description: 自定义Redis缓存实现
 *
 * @author zygui
 * @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 zygui
 * @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;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_37989980/article/details/107606384