前言
例子在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
- 配置端口与Tomcat的HTTP请求头缓冲区大小,因为springboot内置tomcat,在这里配置,启动项目时会读取该配置到tomcat中。
- 配置mysql数据源,注意点:使用druid数据源的时候,其中driver-class-name应该根据mysql版本来选择,本项目使用的是com.mysql.cj.jdbc.Driver,网上说使用6及以上的版本,不太清除我的mysql版本是5.5.62也需要使用该驱动。
- 配置redis连接以及连接池,因为博主的redis版本是5.0.5,所以连接池使用的是lettuce,这里面要注意的是在pom中需要加入
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- 配置项目使用redis作为缓存,并在启动类中加上@EnableCaching注解。不加的话springboot是启用默认的缓存
- 配置mybatis的mapper和实体类信息,并在启动类中加上@MapperScan(“com.tencent.tusi.springboot_demo_redis_mybatis.dao”),用于扫描到该包下的接口对应mapper。
pom.xml
在依赖文件中有几个重要的依赖包
- mysql连接依赖:
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
- 使用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>
- 使用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>
- 使用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风格。