今天,我們繼續講SpringBoot整合Redis ,也就緩存,它將與我們的Springboot整合
Redis 簡介
Redis
是當前比較熱門的NOSQL系統之一,它是一個開源的使用ANSI c語言編寫的
key-value
存儲系統(區別於MySQL的二維表格的形式存儲。)。
和
Memcache
類似,但很大程度補償了Memcache
的不足。和Memcache
一樣,Redis數據都是緩存在計算機內存中,不同的是,Memcache
只能將數據緩存到內存中,無法自動定期寫入硬盤,這就表示,一斷電或重啓,內存清空,數據丟失。所以Memcache
的應用場景適用於緩存無需持久化的數據。而Redis不同的是它會週期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,實現數據的持久化
特點
- Redis讀取的速度是110000次/s,寫的速度是81000次/s
原子 。 - Redis的所有操作都是原子性的,同時Redis還支持對幾個操作全並後的原子性執行。
支持多種數據結構:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合) - 持久化,主從複製(集羣)
- 支持過期時間,支持事務,消息訂閱。
- 官方不支持window,但是又第三方版本。
好了,現在知道他是幹什麼來的 ,我開動吧,相信你也等急了
準備工作
本文使用 Linux 版本的 redis ,當然你也可以使用 Windows ,Windows的話,只需要點擊
當然我們要複雜一點,不然寫博客太沒意思了,就以Linux版本爲例
-
1.Linux系統
-
2.安裝redis(也可以安裝docker,然後再docker中裝redis,本文章就直接用Linux安裝redis做演示)
redis下載地址:http://download.redis.io/releases/redis-4.0.14.tar.gz
修改Redis,開啓遠程訪問
默認Redis是不支持遠程訪問的 ,我們需要手動開啓
找到redis中的redis.conf文件並編輯(在安裝路徑中找到)
vim ./redis.conf
找到bind 127.0.0.1並註釋掉
-
默認127.0.0.1只能本地訪問,註釋掉即可ip訪問
-
修改 protected-mode 屬性值爲no,註釋掉並把保護模式禁用以後可以IP訪問
-
修改daemonize屬性將no 改爲yes,將daemonize設置爲yes即啓動後臺運行
-
開放6379端口
/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT
- 最後一步,開啓Redis
redis-server /myconf/redis.conf
- 測試連接 redis-server默認在/usr/local/bin路徑下,redis.conf在redis的安裝路徑下
redis-cli -h 192.168.126.129 -p 6379
redis-cli -h redis服務器IP -p 6379 -a 密碼(沒有設置redis密碼不要寫空,否則報錯)
Quick Start
第一步 : 加依賴
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis 與 spring boot 2.x的整合包 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql JDBC驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
application.yml
下面是springboot的配置文件application.yml,配置redis(裏面都有註釋解釋)
server:
port: 8081
#數據庫連接
spring:
datasource:
url: jdbc:mysql://localhost:3306/mytest_springboot_cache?useUnicode=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
## Redis 配置
redis:
## Redis數據庫索引(默認爲0)
database: 0
## Redis服務器地址
host: 192.168.126.129
## Redis服務器連接端口
port: 6379
## Redis服務器連接密碼(默認爲空)
password:
jedis:
pool:
## 連接池最大連接數(使用負值表示沒有限制)
#spring.redis.pool.max-active=8
max-active: 8
## 連接池最大阻塞等待時間(使用負值表示沒有限制)
#spring.redis.pool.max-wait=-1
max-wait: -1
## 連接池中的最大空閒連接
#spring.redis.pool.max-idle=8
max-idle: 8
## 連接池中的最小空閒連接
#spring.redis.pool.min-idle=0
min-idle: 0
## 連接超時時間(毫秒)
timeout: 1200
#將themilef的默認緩存禁用,熱加載生效
thymeleaf:
cache: false
#mybatis的下劃線轉駝峯配置
configuration:
map-underscore-to-camel-case: true
#另外一種打印語句的方式
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#打印sql時的語句
logging:
level:
com:
acong:
dao: debug
file: d:/logs/bsbdj.log
接着是實體類,這個比較簡單就不多說了
package com.spiritmark.springbootstudytest.bean;
import java.io.Serializable;
/**
* @author spiritmark
* create 2019-09-18-22:32
*/
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int uid;
private String userName;
private String passWord;
private int salary;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public User(int uid, String userName, String passWord, int salary) {
super();
this.uid = uid;
this.userName = userName;
this.passWord = passWord;
this.salary = salary;
}
public User() {
super();
}
}
Controller
package com.spiritmark.springbootstudytest.controller;
import com.spiritmark.springbootstudytest.bean.User;
import com.spiritmark.springbootstudytest.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author spiritmark
* create 2019-09-18-22:36
*/
@RestController
public class TestController {
@Autowired
private UserService userService;
@RequestMapping("/queryAll")
public List<User> queryAll(){
List<User> lists = userService.queryAll();
return lists;
}
@RequestMapping("/findUserById")
public Map<String, Object> findUserById(@RequestParam int id){
User user = userService.findUserById(id);
Map<String, Object> result = new HashMap<>();
result.put("uid", user.getUid());
result.put("uname", user.getUserName());
result.put("pass", user.getPassWord());
result.put("salary", user.getSalary());
return result;
}
@RequestMapping("/updateUser")
public String updateUser(){
User user = new User();
user.setUid(1);
user.setUserName("cat");
user.setPassWord("miaomiao");
user.setSalary(4000);
int result = userService.updateUser(user);
if(result != 0){
return "update user success";
}
return "fail";
}
@RequestMapping("/deleteUserById")
public String deleteUserById(@RequestParam int id){
int result = userService.deleteUserById(id);
if(result != 0){
return "delete success";
}
return "delete fail";
}
}
之前都是學過的內容,下面是新增的 也就是Redis配置redistemplate序列化
package com.spiritmark.springbootstudytest.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
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.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
/**
* @author spiritmark
* create 2019-09-24-15:07
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
/**
* 選擇redis作爲默認緩存工具
* @param redisConnectionFactory
* @return
*/
/*@Bean
//springboot 1.xx
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
return rcm;
}*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)); // 設置緩存有效期一小時
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
/**
* retemplate相關配置
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置連接工廠
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(默認使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修飾符範圍,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化輸入的類型,類必須是非final修飾的,final修飾的類,比如String,Integer等會跑出異常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 值採用json序列化
template.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer來序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 設置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
/**
* 對hash類型的數據操作
*
* @param redisTemplate
* @return
*/
@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}
/**
* 對redis字符串類型數據操作
*
* @param redisTemplate
* @return
*/
@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}
/**
* 對鏈表類型的數據操作
*
* @param redisTemplate
* @return
*/
@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}
/**
* 對無序集合類型的數據操作
*
* @param redisTemplate
* @return
*/
@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}
/**
* 對有序集合類型的數據操作
*
* @param redisTemplate
* @return
*/
@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}
數據訪問層
接着是Mapper持久層Dao,這裏主要用註解寫比較方便,也可以使用mybatis的xml配置文件寫sql語句
package com.spiritmark.springbootstudytest.mapper;
import com.spiritmark.springbootstudytest.bean.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
/**
* @author SpiritMark
* create 2019-09-18-22:32
*/
@Mapper
public interface UserDao {
@Select("select * from user")
List<User> queryAll();
@Select("select * from user where uid = #{id}")
User findUserById(int id);
@Update("UPDATE USER SET username = CASE WHEN (#{userName} != NULL) AND (#{userName} != '') THEN #{userName},PASSWORD = CASE WHEN (#{passWord} != NULL) AND (#{passWord} != '') THEN #{passWord},salary = CASE WHEN (#{salary} != 0) THEN #{salary} WHERE uid = #{uid}")
int updateUser(@Param("user") User user);
@Delete("delete from user where uid = #{id}")
int deleteUserById(int id);
}
service層,這裏主要是使用redis模板來寫
業務邏輯層,就是重點講
RedisTemplate
package com.spiritmark.springbootstudytest.service;
import com.spiritmark.springbootstudytest.bean.User;
import com.spiritmark.springbootstudytest.mapper.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author spiritmark
* create 2019-09-18-22:33
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RedisTemplate redisTemplate;
public List<User> queryAll() {
return userDao.queryAll();
}
/**
* 獲取用戶策略:先從緩存中獲取用戶,沒有則取數據表中 數據,再將數據寫入緩存
*/
public User findUserById(int id) {
String key = "user_" + id;
ValueOperations<String, User> operations = redisTemplate.opsForValue();
//判斷redis中是否有鍵爲key的緩存
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
User user = operations.get(key);
System.out.println("從緩存中獲得數據:"+user.getUserName());
System.out.println("------------------------------------");
return user;
} else {
User user = userDao.findUserById(id);
System.out.println("查詢數據庫獲得數據:"+user.getUserName());
System.out.println("------------------------------------");
// 寫入緩存
operations.set(key, user, 5, TimeUnit.HOURS);
return user;
}
}
/**
* 更新用戶策略:先更新數據表,成功之後,刪除原來的緩存,再更新緩存
*/
public int updateUser(User user) {
ValueOperations<String, User> operations = redisTemplate.opsForValue();
int result = userDao.updateUser(user);
if (result != 0) {
String key = "user_" + user.getUid();
boolean haskey = redisTemplate.hasKey(key);
if (haskey) {
redisTemplate.delete(key);
System.out.println("刪除緩存中的key-----------> " + key);
}
// 再將更新後的數據加入緩存
User userNew = userDao.findUserById(user.getUid());
if (userNew != null) {
operations.set(key, userNew, 3, TimeUnit.HOURS);
}
}
return result;
}
/**
* 刪除用戶策略:刪除數據表中數據,然後刪除緩存
*/
public int deleteUserById(int id) {
int result = userDao.deleteUserById(id);
String key = "user_" + id;
if (result != 0) {
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
System.out.println("刪除了緩存中的key:" + key);
}
}
return result;
}
}
這裏主要是使用RedisTemplate來對遠程redis操作,每次訪問controller暴露的接口,
- 首先判斷redis緩存中是否存在該數據
- 若不存在就從數據庫中讀取數據,然後保存到redis緩存中
- 當下次訪問的時候,就直接從緩存中取出來。這樣就不用每次都執行sql語句,能夠提高訪問速度。
- 但是在保存數據到緩存中,通過設置鍵和值和超時刪除,注意設置超時刪除緩存時間不要太長,否則會給服務器帶來壓力。
執行spring boot的啓動類,訪問 http://localhost:8081/findUserById?id=1
再次訪問 http://localhost:8081/findUserById?id=1就是從緩存中獲取保存的數據