1:说明
本项目主要使用redis模拟数据库基本增、删、改、查、分页查询功能。如果涉及复杂SQL语句查询,请直接使用主流关系型数据库。
本项目基于Springboot工程,使用了ReidTemplate。
2:项目构建
Web工程和Redis以及Lombok相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
Redis相关配置,使用了yml文件进行配置,其中配置根据需求自行修改即可。
spring:
redis:
database: 2 #redis提供了16个database 编号为0-15
host: 127.0.0.1
port: 6379 #端口号
password: #没有密码
jedis:
pool:
max-active: 8 #最大连接数
max-idle: 8 #最大空闲连接数
max-wait: -1 #最大阻塞等待时间
min-idle: 0 #最小空闲连接数
RedisTemplate相关配置,主要修改了Key,Value的序列化方式,便于直接查看。如果不配置则默认使用JDK进行序列化。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> testTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
template.setValueSerializer(jacksonSeial);
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(jacksonSeial);
template.setHashValueSerializer(jacksonSeial);
template.setDefaultSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
}
RedisUtil工具类,主要使用了Hash和ZSet
@Component
@Slf4j
public class RedisUtil {
@Autowired
private RedisTemplate<Object,Object> testTemplate;
/**
* 存放单个hash缓存
*
* @param key 键
* @param hkey 键
* @param value 值
* @return
*/
public boolean hput(String key, Object hkey, Object value) {
try {
testTemplate.opsForHash().put(key, hkey, value);
log.debug("hput {} = {}", key + hkey, value);
return true;
} catch (Exception e) {
log.warn("hput {} = {}", key + hkey, value, e);
}
return false;
}
/**
* 获取hash中所有数据
* @param key hash存取的key
* @return
*/
public List<Object> getValues(String key){
List<Object> values=null;
try {
values = testTemplate.opsForHash().values(key);
} catch (Exception e) {
e.printStackTrace();
}
return values;
}
/**
* 在hash集合中根据hashKey获取单个对象数据
* @param key
* @param hashKey
* @return
*/
public Object get(String key,Object hashKey){
Object o=null;
try {
o= testTemplate.opsForHash().get(key,hashKey);
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
/**
* 分页存取数据
*
* @param key hash存取的key
* @param hkey hash存取的hkey
* @param score 指定字段排序
* @param value
* @return
*/
public boolean setPage(String key, Object hkey, double score, Object value) {
boolean result = false;
try {
testTemplate.opsForZSet().add(key + ":page", hkey, score);
result = hput(key, hkey, value);
log.debug("setPage {}", key);
} catch (Exception e) {
log.warn("setPage {}", key, e);
}
return result;
}
/**
* 根据页码和条数取出数据
* @param key
* @param page
* @param limit
* @return
*/
public List<Object> getResultByPage(String key, int page, int limit){
List<Object> result=new ArrayList<>();
try {
//1 100000代表score的排序氛围值,即从1-100000的范围
Set<Object> pageResult=testTemplate.opsForZSet().rangeByScore(key + ":page", 1, 100000, (page - 1) * limit, limit);
for (Object o : pageResult) {
Object o1 = this.get(key, o);
result.add(o1);
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 计算key值对应的数量
*
* @param key
* @return
*/
public Integer getSize(String key) {
Integer num = 0;
try {
Long size = testTemplate.opsForZSet().zCard(key + ":page");
log.debug("getSize {}", key);
return size.intValue();
} catch (Exception e) {
log.warn("getSize {}", key, e);
}
return num;
}
public boolean removeHashKey(String key,Object hashKey){
boolean result=false;
try {
testTemplate.opsForZSet().remove(key+":page",hashKey);
testTemplate.opsForHash().delete(key,hashKey);
result=true;
log.info("删除数据成功!");
} catch (Exception e) {
log.info("出现异常:{}",e.getMessage());
}
return result;
}
/**
* 获取最大值
* @param key
* @return
*/
public Integer getMaxValue(String key){
Integer result=null;
try {
Set<Object> set = testTemplate.opsForZSet().reverseRange(key+":page", 0, 0);
for (Object o : set) {
result= (Integer) o;
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 获取最大分数
* @param key
* @param value
* @return
*/
public Double getMaxScore(String key,Object value){
Double result=null;
try {
result = testTemplate.opsForZSet().score(key+":page", value);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Book implements Serializable {
private Integer id;
private String name;
private String author;
}
测试Controller方法
@RestController
@Slf4j
public class RedisController {
@Autowired
private RedisUtil redisUtil;
private static final String REDIS_KEY = "books";
@GetMapping("/books")
public ConcurrentMap<String, Object> fetchList(@RequestParam("page") Integer page, @RequestParam("limit") Integer limit) {
ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<>(16);
List<Object> resultByPage = redisUtil.getResultByPage(REDIS_KEY, page, limit);
if (!CollectionUtils.isEmpty(resultByPage)) {
concurrentMap.put("message", "success");
concurrentMap.put("data", resultByPage);
concurrentMap.put("code", 1000);
return concurrentMap;
}
concurrentMap.put("message", "暂无数据!");
concurrentMap.put("code", 1001);
return concurrentMap;
}
@PostMapping("/book")
public ConcurrentMap<String, Object> saveBook(@RequestBody Book book) {
ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<>(16);
Integer maxValue = redisUtil.getMaxValue(REDIS_KEY);
if (maxValue == null) {
book.setId(1);
redisUtil.setPage(REDIS_KEY, book.getId(), 1, book);
} else {
Double maxScore = redisUtil.getMaxScore(REDIS_KEY, maxValue);
book.setId(maxValue + 1);
redisUtil.setPage(REDIS_KEY, book.getId(), maxScore + 1, book);
}
concurrentMap.put("message", "新增成功");
concurrentMap.put("data", book);
return concurrentMap;
}
@GetMapping("/book/{id}")
public ConcurrentMap<String, Object> getBook(@PathVariable("id") Integer id) {
ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<>(16);
Object o = redisUtil.get(REDIS_KEY, id);
if (o == null) {
concurrentMap.put("message", "查询的数据不存在");
} else {
concurrentMap.put("message", "success");
concurrentMap.put("data", o);
}
return concurrentMap;
}
@PutMapping("/book")
public ConcurrentMap<String, Object> modifyBook(@RequestBody Book book) {
ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<>(16);
Object o = redisUtil.get(REDIS_KEY, book.getId());
if (o == null) {
concurrentMap.put("message", "书籍不存在,修改失败");
} else {
if (redisUtil.hput(REDIS_KEY, book.getId(), book)) {
concurrentMap.put("message", "修改成功");
concurrentMap.put("data", book);
} else {
concurrentMap.put("message", "修改失败");
}
}
return concurrentMap;
}
@DeleteMapping("/book/{id}")
public ConcurrentMap<String,Object> removeBook(@PathVariable("id") Integer id){
ConcurrentMap<String, Object> concurrentMap = new ConcurrentHashMap<>(16);
Object o = redisUtil.get(REDIS_KEY, id);
if (o == null) {
concurrentMap.put("message", "书籍不存在,删除失败");
return concurrentMap;
}
if (redisUtil.removeHashKey(REDIS_KEY,id)){
concurrentMap.put("message", "删除成功");
}else {
concurrentMap.put("message", "删除失败");
}
return concurrentMap;
}
}
3.测试
基于Postman测试
为了方便测试我先插入了10条记录
@Test
public void insertData(){
for (int i=1;i<=10;i++){
redisUtil.setPage("books",i,i,new Book(i,"书籍"+i,"作者"+i));
}
}
zset集合数据
hash集合数据
-
测试查询 按一页5条记录进行测试
第一页数据第二页数据
查询结果一切正常!
-
新增数据
新增数据测试
传入数据时并为给定id,为后台自增。
查询测试,由于一页五条,此时第11条记录为第三页 所有只有这一条记录。
第三页数据
-
根据id查询测试
根据编号查询不存在的数据 -
修改测试
修改数据测试
再次查询
查询修改之后的数据
-
删除测试
删除数据测试
再次查询
查询被删除的数据
4.结语
至此,redis模拟数据操作全部完成。可以根据自身需求调整RedisUtil类中的方法。此demo只为一个简单实例,Redis的用途也并非仅此而已。我只不过是用来模拟一下数据库使用而已。