使用springboot实现redis作为mysql缓存

使用springboot实现redis作为mysql缓存

前言

例子在github上使用springboot实现redis作为mysql缓存.
因为博主的linux通过购买阿里服务器并使用宝塔linux系统,博主通过宝塔系统提供的软件商店下载的Mysql和Redis。通过命令行安装网络上已经有很多,这里就不在赘述。
在这里插入图片描述
不过,大同小异之处在一下两点。
1. 开放3306和6379端口。可以在windows端使用telnet 服务器ip地址 端口号来检测服务器中是否开启该端口号,如下图:
在这里插入图片描述
端口号若开启,则会出现如下图所示
在这里插入图片描述
2. 修改mysql的host为*,因为默认的host为127.0.0.1,只能被本地访问
修改redis的配置文件,bind 0.0.0.0、daemonize yes
使用navicat测试mysql是否能够被连接,使用RedisDesktopManager测试redis是否能够被连接
使用redis缓存注意点
1.数据读频率远远高于写频率时使用redis作为缓存可以大大提高读数据的效率。
2.数据写频率远远高于读频率时不适合使用redis作为缓存,或者说不应该设计缓存。因为每次往数据库写数据时都会更新缓存。
需要根据自己的业务场景来选择是否需要设计缓存

项目代码

配置文件

新建application-local.yml作为本地配置,并在系统生成的application.properties中加上

spring.profiles.active=local

表示启用application-local中的配置。一般,这个配置是作为本地测试时使用的配置。生产环境的配置文件名为application-prod.yml,该配置是生产环境的配置。

application-local.yml

# server config
server:
  port: 8085
  max-http-header-size: 8192
# spring config
spring:
  datasource:
#    数据库连接
    name: ssm_demo_redis
    url: jdbc:mysql://yourhostip/ssm_demo_redis
    username: root
    password: password
#    使用druid数据源
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
#    数据源配置
    dbcp2:
      initial-size: 2
      minIdle: 5
      max-total: 20
      max-wait-millis: 60000
      time-between-eviction-runs-millis: 60000
      soft-min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      pool-prepared-statements: false
      max-open-prepared-statements: 20
        # 时区设置为东八区
  jackson:
    time-zone: GMT+8
  redis:
    database: 0 # Redis 数据库索引(默认为 0)
    host: your host ip # Redis 服务器地址
    port: 6379 # Redis 服务器连接端口
    password: password # Redis 服务器连接密码(默认为空)
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-idle: 8 # 连接池中的最大空闲连接 默认 8
        min-idle: 0 # 连接池中的最小空闲连接 默认 0
  cache:
    type: redis
    redis:
      cache-null-values: true  # Allow caching null values.
      key-prefix: # Key prefix.
      time-to-live: # 缓存到期时间,默认不主动删除永远不到期
      use-key-prefix: true  # Whether to use the key prefix when writing to Redis.

# MyBatis Configure
mybatis:
  mapperLocations: classpath:mapper/*.xml
  typeAliasesPackage: com.tencent.tusi.springboot_demo_redis_mybatis.entity
  1. 配置端口与Tomcat的HTTP请求头缓冲区大小,因为springboot内置tomcat,在这里配置,启动项目时会读取该配置到tomcat中。
  2. 配置mysql数据源,注意点:使用druid数据源的时候,其中driver-class-name应该根据mysql版本来选择,本项目使用的是com.mysql.cj.jdbc.Driver,网上说使用6及以上的版本,不太清除我的mysql版本是5.5.62也需要使用该驱动。
  3. 配置redis连接以及连接池,因为博主的redis版本是5.0.5,所以连接池使用的是lettuce,这里面要注意的是在pom中需要加入
<dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-pool2</artifactId>
       </dependency>
  1. 配置项目使用redis作为缓存,并在启动类中加上@EnableCaching注解。不加的话springboot是启用默认的缓存
  2. 配置mybatis的mapper和实体类信息,并在启动类中加上@MapperScan(“com.tencent.tusi.springboot_demo_redis_mybatis.dao”),用于扫描到该包下的接口对应mapper。

pom.xml
在依赖文件中有几个重要的依赖包

  1. mysql连接依赖:
<!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
  1. 使用mybatis的相关依赖
<!--mybatis-->
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.4.5</version>
       </dependency>
       <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis-spring</artifactId>
           <version>1.3.1</version>
       </dependency>
       <dependency>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-starter</artifactId>
           <version>1.3.2</version>
       </dependency>
       <!--druid-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>druid</artifactId>
           <version>1.1.10</version>
       </dependency>
  1. 使用redis作为缓存的依赖
<!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  1. 使用lombok插件,简化开发。在实体类上加上@Data注解自动生成getter/setter方法以及toString()方法等
<dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

代码示例

redis配置信息

/*
 * Copyright © 1998 - 2018 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * @author shanpeng
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    
    @Bean
    public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
    }
}

因为redis存储的键值对是通过StringRedisTemplate来存储的,但是值只有String类型,但是实际生产中大多数都是使用对象。虽然redis也支持存储对象,其思路是先对对象进行序列化,默认使用的是JdkSerializationRedisSerializer对pojo进行序列化,然后保存二进制数据到redis数据库中,但是此时使用RedisDesktopManager来查看数据时,因为是二进制数据,所以无法直观的看到到底写进来的是什么。
当前思路是改变默认的序列化方法,使用Jackson2JsonRedisSerializer将pojo保存为JSON数据存储在数据库中,一者,这种方式效率很高,另外,这种方式也解决了乱码问题。关键还是这四行代码,指定了redis以何种方式存储键值对

redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

这一步直接写进redis数据库是没有问题的,但是redis作为缓存,功能实现还不够完整,所以需要下面的代码块,配置缓存的序列化和redisTemplate保持一致,因为上面已经配置了setXXX,这里只要通过redisTemplate.getValueSerializer()就可以获取到redisTemplate中值的序列化方式,并通过RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith()来设置redis缓存默认的序列化方式
实体类信息

/*
 * Copyright © 1998 - 2020 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * @author shanpeng
 */
@Data
public class AccountInfo implements Serializable {

    private static final long serialVersionUID = -1340095119555693650L;

    public AccountInfo() {
    }

    private int id;

    private String accountName;

    private String accountAge;

    private String nickName;

    private String password;

    private Date createTime;

    private Date updateTime;
}

因为要缓存数据之前,要对数据进行序列化,所以该实体类实现了Serializable 接口,并通过IDEA自动生成序列ID,如何配置可以自行搜索步骤,关键点在这:在这里插入图片描述
AccountInfoDao

/*
 * Copyright © 1998 - 2018 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.dao;

import com.tencent.tusi.springboot_demo_redis_mybatis.entity.AccountInfo;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Qualifier;

import java.util.List;

/**
 * @author shanpeng
 */
@Qualifier(value = "accountDao")
public interface AccountInfoDao {
    int saveAccountInfo(@Param("pojo")AccountInfo accountInfo);

    int removeAccountById(@Param("id")int id);

    AccountInfo findAccountInfoById(@Param("id")int id);

    int updateAccountInfo(@Param("pojo")AccountInfo accountInfo);

    List<AccountInfo> findAllAccountInfo();
}

包含了常用的增删查改。

AccountInfoMapper.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.tencent.tusi.springboot_demo_redis_mybatis.dao.AccountInfoDao">

    <resultMap id="allColumns" type="com.tencent.tusi.springboot_demo_redis_mybatis.entity.AccountInfo">
        <id column="id" property="id"/>
        <result column="c_name" property="accountName"/>
        <result column="c_age" property="accountAge"/>
        <result column="c_nickname" property="nickName"/>
        <result column="c_password" property="password"/>
        <result column="createtime" property="createTime"/>
        <result column="updatetime" property="updateTime"/>
    </resultMap>

    <insert id="saveAccountInfo" useGeneratedKeys="true" keyProperty="id">
        insert into t_account
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="pojo.accountName != null and pojo.accountName != ''">c_name,</if>
            <if test="pojo.accountAge != null and pojo.accountAge != 0">c_age,</if>
            <if test="pojo.nickName != null and pojo.nickName != ''">c_nickname,</if>
            <if test="pojo.password != null and pojo.password != ''">c_password,</if>
            createtime,
            updatetime
        </trim>
        values
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="pojo.accountName != null and pojo.accountName != ''">#{pojo.accountName},</if>
            <if test="pojo.accountAge != null and pojo.accountAge != 0">#{pojo.accountAge},</if>
            <if test="pojo.nickName != null and pojo.nickName != ''">#{pojo.nickName},</if>
            <if test="pojo.password != null and pojo.password != ''">#{pojo.password},</if>
            now(),
            now()
        </trim>
    </insert>
    <update id="updateAccountInfo">
        update t_account
        <set>
            <if test="pojo.accountName != null and pojo.accountName != ''">c_name = #{pojo.accountName},</if>
            <if test="pojo.accountAge != null and pojo.accountAge != 0">c_age = #{pojo.accountAge},</if>
            <if test="pojo.nickName != null and pojo.nickName != ''">c_nickname = #{pojo.nickName},</if>
            <if test="pojo.password != null and pojo.password != 0">c_password = #{pojo.password},</if>
            updatetime = now()
        </set>
        where id = #{pojo.id}
    </update>
    <delete id="removeAccountById">
        delete from t_account
        where id = #{io}
    </delete>
    <select id="findAccountInfoById" resultMap="allColumns">
        select * from t_account
        where id = #{id}
    </select>
    <select id="findAllAccountInfo" resultMap="allColumns">
        select * from t_account
    </select>
</mapper>

与dao对应,具体的sql语句。
service接口

/*
 * Copyright © 1998 - 2018 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.service.service;

import com.tencent.tusi.springboot_demo_redis_mybatis.entity.AccountInfo;

import java.util.List;

/**
 * @author shanpeng
 */
public interface AccountInfoService {
    int saveAccountInfo(AccountInfo accountInfo);

    int removeAccountInfoById(int id);

    AccountInfo findAccountInfoById(int id);

    int updateAccountInfo(AccountInfo accountInfo);

    List<AccountInfo> findAllAccountInfo();
}

service层接口实现类

/*
 * Copyright © 1998 - 2018 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.service.impl;

import com.tencent.tusi.springboot_demo_redis_mybatis.dao.AccountInfoDao;
import com.tencent.tusi.springboot_demo_redis_mybatis.entity.AccountInfo;
import com.tencent.tusi.springboot_demo_redis_mybatis.service.service.AccountInfoService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author shanpeng
 */
@Service
public class AccountInfoServiceImpl implements AccountInfoService {

    @Resource
    AccountInfoDao accountInfoDao;

    @Override
    @Caching(evict = {@CacheEvict(cacheNames = "accountList",allEntries = true)},
             put = {@CachePut(cacheNames = "account",key = "#accountInfo.id")})
    public int saveAccountInfo(AccountInfo accountInfo) {
        return accountInfoDao.saveAccountInfo(accountInfo);
    }

    @Override
    @Caching(evict = {@CacheEvict(cacheNames = "accountList",allEntries = true),
                      @CacheEvict(cacheNames = "account",key = "#id")})
    public int removeAccountInfoById(int id) {
        return accountInfoDao.removeAccountById(id);
    }

    @Override
    @Cacheable(value = "account")
    public AccountInfo findAccountInfoById(int id) {
        return accountInfoDao.findAccountInfoById(id);
    }

    @Override
    @Cacheable(value = "accountList")
    public List<AccountInfo> findAllAccountInfo(){
        return accountInfoDao.findAllAccountInfo();
    }

    @Override
    @Caching(evict = {@CacheEvict(cacheNames = "accountList",allEntries = true)},
            put = {@CachePut(cacheNames = "account",key = "#accountInfo.id")})
    public int updateAccountInfo(AccountInfo accountInfo) {
        return accountInfoDao.updateAccountInfo(accountInfo);
    }
}

几个注意点:
1. 使用@Resource注入,试过使用@Autowired以及结合使用@Qualifier,无法注入。在SSM框架中使用@Autowied可以注入dao。
2. 查找数据时,使用@Cacheable注解,并指定它的名称。下图是我运行项目后redis数据库中的信息。再次查找数据时,会通过value到redis中查找并返回,如果没有数据则会直接到mysql数据库中查找。

3. 删除数据时的思路,先删除数据库里面的信息,再删除对应的缓存信息,如果缓存信息删除失败,则回滚数据库。使用@CacheEvict注解,通过cacheNames 值删除对应的缓存。
4. 更新时,根据需求删除缓存,或者是更新缓存。更新缓存的注解是@CachePut,这里面指定的cacheNames 是根据名称找到缓存值,key是根据key值来更新它所对应的缓存值。
控制层(Controller)

/*
 * Copyright © 1998 - 2018 Tencent. All Rights Reserved
 * www.tencent.com
 * All rights reserved.
 */
package com.tencent.tusi.springboot_demo_redis_mybatis.controller;

import com.tencent.tusi.springboot_demo_redis_mybatis.entity.AccountInfo;
import com.tencent.tusi.springboot_demo_redis_mybatis.service.service.AccountInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author shanpeng
 */
@Controller
@RequestMapping(value = "demo")
public class AccountInfoController {

    @Autowired
    AccountInfoService accountInfoService;

    @PostMapping(value = "/saveAccount")
    @ResponseBody
    public int saveAccount(@RequestBody AccountInfo accountInfo){
        return accountInfoService.saveAccountInfo(accountInfo);
    }

    @GetMapping(value = "/getAccount/{id}")
    @ResponseBody
    public AccountInfo findAccount(@PathVariable(value = "id")int id){
        return accountInfoService.findAccountInfoById(id);
    }

    @GetMapping(value = "/getAllAccount")
    @ResponseBody
    public List<AccountInfo> findAllAccount(){
        return accountInfoService.findAllAccountInfo();
    }

    @RequestMapping(value = "/removeAccount/{id}",method = RequestMethod.DELETE)
    @ResponseBody
    public int removeAccountById(@PathVariable("id")int id){
        return accountInfoService.removeAccountInfoById(id);
    }

    @RequestMapping(value = "/updateAccount",method = RequestMethod.PUT)
    @ResponseBody
    public int updateAccount(@RequestBody AccountInfo accountInfo){
        return accountInfoService.updateAccountInfo(accountInfo);
    }
}

这里只是用于使用POSTMAN测试用的,并没有什么细节信息。该接口并未符合Restful风格。

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值