前面学习整合了mongodb和 Thymeleaf,这里来整合redis和mongodb。学习参考:http://gitbook.cn/gitchat/column/5acda6f6d7966c5ae1086f2b/topic/5acdaa62d7966c5ae108708a
这里需要用到mongodb,和redis,这两个都可以使用docker来启动redis和mongodb的容器,设置好用户名和密码启动容器就可以建立连接了,具体的操作略掉了,网上资料很多。mongodb容器可以采用之前博客建立的mongodb容器
1,创建一个springboot2的项目
pom.xml代码如下:
<?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>
<groupId>com.jack</groupId>
<artifactId>webflux_redis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>webflux_redis</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Boot 响应式 MongoDB 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties的配置如下:
## Redis 配置
## Redis服务器地址
spring.redis.host=xx.xx.xx.xx
## Redis服务器连接端口
spring.redis.port=6379
## Redis服务器连接密码(默认为空)
spring.redis.password=xxxxxxxx
# 连接超时时间(毫秒),这里注意的是连接超时时间不能太少或者为 0,不然会引起异常 RedisCommandTimeoutException: Command timed out
spring.redis.timeout=5000
#mongodb的配置
spring.data.mongodb.host=192.168.0.104
spring.data.mongodb.database=admin
spring.data.mongodb.port=27017
spring.data.mongodb.username=admin
spring.data.mongodb.password=admin
2,实体类代码如下:
package com.jack.webflux_redis.domain;
import lombok.Data;
import org.springframework.data.annotation.Id;
import java.io.Serializable;
/**
* create by jack 2018/5/13
* City 必须实现序列化,因为需要将对象序列化后存储到 Redis。如果没实现 Serializable,
* 会引出异常:java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable
* payload but received an object of type。
如果不是用默认的序列化,需要自定义序列化实现,只要实现 RedisSerializer 接口去实现即可,
然后在使用 RedisTemplate.setValueSerializer 方法去设置你实现的序列化实现,支持 JSON、XML 等。
*/
@Data
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
* @Id 注解标记对应库表的主键或者唯一标识符。因为这个是我们的 DO,数据访问对象一一映射到数据存储。
*/
@Id
private Long id;
/**
* 省份编号
*/
private Long provinceId;
/**
* 城市名称
*/
private String cityName;
/**
* 描述
*/
private String description;
}
3,redis的测试代码如下:
redis的测试controller如下,此代码不具有reactive特性:
package com.jack.webflux_redis.controller;
import com.jack.webflux_redis.domain.City;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
/**
* create by jack 2018/5/13
*
* 使用 @Autowired 注入 RedisTemplate 对象,这个对象和 Spring 的 JdbcTemplate 功能十分相似,RedisTemplate 封装了 RedisConnection,具有连接管理、序列化和各个操作等,还有针对 String 的支持对象 StringRedisTemplate。
删除 Redis 某对象,直接通过 key 值调用 delete(key)。
Redis 操作视图接口类用的是 ValueOperations,对应的是 Redis String/Value 操作,get 是获取数据;set 是插入数据,可以设置失效时间,这里设置的失效时间是 60 s。
还有其他的操作视图,ListOperations、SetOperations、ZSetOperations 和 HashOperations。
*/
@RestController
@RequestMapping(value = "/city")
public class CityWebFluxController {
/**
* RedisTemplate 实现操作 Redis,但操作是同步的,不是 Reactive 的
*/
@Autowired
private RedisTemplate redisTemplate;
@GetMapping(value = "/{id}")
public Mono<City> findCityById(@PathVariable("id") Long id) {
String key = "city_" + id;
ValueOperations<String, City> operations = redisTemplate.opsForValue();
boolean hasKey = redisTemplate.hasKey(key);
City city = operations.get(key);
if (!hasKey) {
return Mono.create(monoSink -> monoSink.success(null));
}
return Mono.create(monoSink -> monoSink.success(city));
}
@PostMapping()
public Mono<City> saveCity(@RequestBody City city) {
String key = "city_" + city.getId();
ValueOperations<String, City> operations = redisTemplate.opsForValue();
operations.set(key, city, 60, TimeUnit.SECONDS);
return Mono.create(monoSink -> monoSink.success(city));
}
@DeleteMapping(value = "/{id}")
public Mono<Long> deleteCity(@PathVariable("id") Long id) {
String key = "city_" + id;
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
}
return Mono.create(monoSink -> monoSink.success(id));
}
}
运行程序打开postman,添加一个城市信息,
通过redis获取一个城市代码如下:
4,添加一个reactive的redis操作
上面的redis操作不具有reactive的特性,下面是具有reactive特性的redis操作,代码如下:
package com.jack.webflux_redis.controller;
import com.jack.webflux_redis.domain.City;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.core.ReactiveValueOperations;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
/**
* create by jack 2018/5/13
*/
@RestController
@RequestMapping(value = "/city2")
public class CityWebFluxReactiveController {
/**
*
* 持 Reactive 的操作类为 ReactiveRedisTemplate
* @Autowired 注入 ReactiveRedisTemplate 对象。
ReactiveValueOperations 是 String(或 value)的操作视图,
操作视图还有 ReactiveHashOperations、ReactiveListOperations、ReactiveSetOperations 和 ReactiveZSetOperations 等。
不一样的是,操作视图 set 方法是操作 City 对象,但可以 get 回 Mono 或者 Flux 对象
*/
@Autowired
private ReactiveRedisTemplate reactiveRedisTemplate;
@GetMapping(value = "/{id}")
public Mono<City> findCityById(@PathVariable("id") Long id) {
String key = "city_" + id;
ReactiveValueOperations<String, City> operations = reactiveRedisTemplate.opsForValue();
Mono<City> city = operations.get(key);
return city;
}
@PostMapping
public Mono<City> saveCity(@RequestBody City city) {
String key = "city_" + city.getId();
ReactiveValueOperations<String, City> operations = reactiveRedisTemplate.opsForValue();
return operations.getAndSet(key, city);
}
@DeleteMapping(value = "/{id}")
public Mono<Long> deleteCity(@PathVariable("id") Long id) {
String key = "city_" + id;
return reactiveRedisTemplate.delete(key);
}
}
测试结果和上面的redis测试结果一样,只是具有reactive的特性,是异步非阻塞的。
5,整合redis和mongodb
dao层的代码如下:
package com.jack.webflux_redis.dao;
import com.jack.webflux_redis.domain.City;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
/**
* create by jack 2018/5/12
*/
@Repository
public interface CityRepository extends ReactiveMongoRepository<City, Long> {
}
新增一个handler,代码如下:
package com.jack.webflux_redis.handler;
import com.jack.webflux_redis.dao.CityRepository;
import com.jack.webflux_redis.domain.City;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* create by jack 2018/5/13
*/
@Component
public class CityHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CityHandler.class);
@Autowired
private RedisTemplate redisTemplate;
private final CityRepository cityRepository;
@Autowired
public CityHandler(CityRepository cityRepository) {
this.cityRepository = cityRepository;
}
public Mono<City> save(City city) {
return cityRepository.save(city);
}
/**
* 如果缓存存在,从缓存中获取城市信息;
如果缓存不存在,从 DB 中获取城市信息,然后插入缓存
* @param id
* @return
*/
public Mono<City> findCityById(Long id) {
// 从缓存中获取城市信息
String key = "city_" + id;
ValueOperations<String, City> operations = redisTemplate.opsForValue();
// 缓存存在
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
City city = operations.get(key);
LOGGER.info("CityHandler.findCityById() : 从缓存中获取了城市 >> " + city.toString());
return Mono.create(cityMonoSink -> cityMonoSink.success(city));
}
// 从 MongoDB 中获取城市信息
Mono<City> cityMono = cityRepository.findById(id);
if (cityMono == null)
return cityMono;
// 插入缓存
cityMono.subscribe(cityObj -> {
operations.set(key, cityObj);
LOGGER.info("CityHandler.findCityById() : 城市插入缓存 >> " + cityObj.toString());
});
return cityMono;
}
public Flux<City> findAllCity() {
return cityRepository.findAll().cache();
}
public Mono<City> modifyCity(City city) {
Mono<City> cityMono = cityRepository.save(city);
// 缓存存在,删除缓存
String key = "city_" + city.getId();
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
LOGGER.info("CityHandler.modifyCity() : 从缓存中删除城市 ID >> " + city.getId());
}
return cityMono;
}
/**
* 如果缓存存在,删除;
如果缓存不存在,不操作
* @param id
* @return
*/
public Mono<Long> deleteCity(Long id) {
cityRepository.deleteById(id);
// 缓存存在,删除缓存
String key = "city_" + id;
boolean hasKey = redisTemplate.hasKey(key);
if (hasKey) {
redisTemplate.delete(key);
LOGGER.info("CityHandler.deleteCity() : 从缓存中删除城市 ID >> " + id);
}
return Mono.create(cityMonoSink -> cityMonoSink.success(id));
}
}
新增一个controller,代码如下:
package com.jack.webflux_redis.controller;
import com.jack.webflux_redis.domain.City;
import com.jack.webflux_redis.handler.CityHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* create by jack 2018/5/13
*/
@RestController
@RequestMapping(value = "/city3")
public class CityWebFluxMongodbController {
@Autowired
private CityHandler cityHandler;
@GetMapping(value = "/{id}")
public Mono<City> findCityById(@PathVariable("id") Long id) {
return cityHandler.findCityById(id);
}
@GetMapping()
public Flux<City> findAllCity() {
return cityHandler.findAllCity();
}
@PostMapping()
public Mono<City> saveCity(@RequestBody City city) {
return cityHandler.save(city);
}
@PutMapping()
public Mono<City> modifyCity(@RequestBody City city) {
return cityHandler.modifyCity(city);
}
@DeleteMapping(value = "/{id}")
public Mono<Long> deleteCity(@PathVariable("id") Long id) {
return cityHandler.deleteCity(id);
}
}
运行测试,http://127.0.0.1:8080/city3
新增一个城市
查询一个城市:http://127.0.0.1:8080/city3/3
后台输出
2018-05-13 10:52:40.283 INFO 8444 --- [ntLoopGroup-4-3] c.j.webflux_redis.handler.CityHandler : CityHandler.findCityById() : 城市插入缓存 >> City(id=3, provinceId=6410, cityName=海南, description=海南岛是一个历史不长、但风景优美的沿海岛屿,为中国第二大岛)
进行第二次查询,直接从缓存获取数据:
2018-05-13 10:54:30.253 INFO 8444 --- [ctor-http-nio-2] c.j.webflux_redis.handler.CityHandler : CityHandler.findCityById() : 从缓存中获取了城市 >> City(id=3, provinceId=6410, cityName=海南, description=海南岛是一个历史不长、但风景优美的沿海岛屿,为中国第二大岛)
源码地址:
https://github.com/wj903829182/springcloud5/tree/master/webflux_redis
总结:上面演示了webflux整合redis和mongodb的案例,代码都十分的简单和之前的webflux的CRUD差不多,只是采用了数据存储的方式,引入了nosql数据库mongodb和内存数据库redis。