Spring Data Redis 之使用RedisTemplate 实现自定义查询
Book实体类
原本的接口,再继承我们自定义的接口
自定义查询接口----CustomBookDao
实现类:CustomBookDaoImpl
1、自定义添加hash对象的方法
2、自定义查询价格高于某个点的Book对象
测试:自定义添加hash对象的方法
成功添加hash对象到redis数据库
测试:自定义查询价格高于某个点的Book对象
结果:
数据:价格有100, 200 ,300这三个
结果正确,因为自定义的查询,是价格 > , 不是 >= 。
Spring Data Redis 的样本查询
如图:因为bookDao有继承这个 QueryByExampleExecutor 接口,所以可以进行样本查询
样本中只有 name 作为参数来查询
样本中只有 author 作为参数来查询
完整代码
Book
package cn.ljh.app.domain;import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;
import org.springframework.data.redis.core.index.Indexed;import java.util.concurrent.TimeUnit;//通过@RedisHash注解存储实体Book到redis,就是该Book对象将存储为books(key)对应的hash对象(value) ---> key(books) field value
//相当于 @Entity 实体类映射到数据库,这里的RedisHash就是把这个book存到redis中
@RedisHash("books")
@Data
public class Book
{@Idprivate Integer id; //即使这里类型是Integer,可是存到redis后,也会变成String类型//Indexed 指定对普通类型的属性建立索引,索引化后的属性才可用于查询。@Indexedprivate String name;@Indexedprivate String author;private double price;//该注解修饰一个数值类型的属性,用于指定该对象的过期时长。@TimeToLive(unit = TimeUnit.MINUTES)private long ttl;public Book(){}public Book(Integer id, String name, double price, String author){this.id = id;this.name = name;this.price = price;this.author = author;}public Book(Integer id, String name, double price, String author, long ttl){this.id = id;this.name = name;this.price = price;this.author = author;this.ttl = ttl;}
}
BookDao
package cn.ljh.app.dao;import cn.ljh.app.domain.Book;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;import java.util.List;//DAO接口只需继承CrudRepository,Spring Data Redis 就能为DAO组件提供实现类。
//参数1:要操作的实体的类型 参数2:Id类型
//继承这个 QueryByExampleExecutor 接口,才可以进行样本查询
public interface BookDao extends CrudRepository<Book, Integer>, QueryByExampleExecutor,CustomBookDao
{//方法名关键字查询//根据 name 查询Book对象//Booke实体类中的 name 属性有添加注解 @Indexed,所以可以对这个属性作为key进行查询List<Book> findByName(String name);//根据 author 查询Book对象//这个author在实体类中也有添加 @Indexed 注解List<Book> findByAuthor(String author);}
CustomBookDao
package cn.ljh.app.dao;import cn.ljh.app.domain.Book;import java.util.List;
import java.util.Map;//自定义查询
public interface CustomBookDao
{//自定义添加hash对象的方法void hmset(String key, Map<String,String> value);//自定义查询价格高于某个点的Book对象List<Book> findByPriceGt(double startPrice);}
CustomBookDaoImpl
package cn.ljh.app.dao.impl;import cn.ljh.app.dao.CustomBookDao;
import cn.ljh.app.domain.Book;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;//自定义查询接口实现类
public class CustomBookDaoImpl implements CustomBookDao
{//借助于自动配置的 StringRedisTemplate 来实现数据库的访问private final StringRedisTemplate stringRedisTemplate;//有参构造器进行依赖注入public CustomBookDaoImpl(StringRedisTemplate stringRedisTemplate){this.stringRedisTemplate = stringRedisTemplate;}//自定义添加hash对象的方法@Overridepublic void hmset(String key, Map<String, String> value){//调用 HashOperations 的方法来向数据库中存入 Hash 对象//要操作的value的类型是hash对象,就用.opsForHash() 方法stringRedisTemplate.opsForHash().putAll(key, value);}//自定义查询价格高于某个点的Book对象@Overridepublic List<Book> findByPriceGt(double startPrice){//由于 price 没有用注解@Indexed 建立索引,因此不能直接根据price来查询//首先获取全部的Book对象,redis会自动把我们添加的所有Book对象中的id,都存在一个set集合里面,因此先把id从set集合中都拿出来//要操作的value的类型是 set 集合,就用.opsForSet() 方法,此处的 books 就是 Book对象上 @RedisHash("books") 所指定的keySet<String> idSet = this.stringRedisTemplate.opsForSet().members("books");//把这个 HashOperations 先提取出来HashOperations<String, String, String> hashOps = this.stringRedisTemplate.opsForHash();//返回接收Book对象的集合List<Book> bookList = new ArrayList<>();//2、遍历id集合,根据id获取所有的实体对象Bookfor (String id : idSet){//key 为 "books:<id值>" 对应的hash对象,就保存着一个个的持久化对象的全部信息。String objkey = "books:" + id;//3、判断key是否存在,因为key即使过期了,也还记录者,该objkey对应的value还存在,那么说明该实体还存在if (this.stringRedisTemplate.hasKey(objkey)){//获取 "books:"+id 的key所对应的 Hash 对象中的price属性--就是Book对象的price属性double price = Double.parseDouble(hashOps.get(objkey, "price"));//4、判断价格,符合条件的再添加到list集合中if (price > startPrice){bookList.add(new Book(Integer.parseInt(hashOps.get(objkey, "id")),hashOps.get(objkey, "name"),price,hashOps.get(objkey, "author")));}}}return bookList;}
}
BookDaoTest
//=========================================样本查询=========================================@Autowiredprivate BookDao bookDao;//==========================自定义查询================================//自定义添加hash对象的方法@Testpublic void testHmset(){bookDao.hmset("test", Map.of("k1", "value1", "k2", "value2"));}//自定义查询价格高于某个点的Book对象@ParameterizedTest@ValueSource(doubles = {100, 200, 300})public void testFindByPriceGt(double startPrice){List<Book> books = bookDao.findByPriceGt(startPrice);books.forEach(System.out::println);}//=========================================样本查询=========================================//样本中只有 name 作为参数来查询@ParameterizedTest@ValueSource(strings = {"七龙珠", "火影忍者"})public void testFindByExampleOnlyName(String name){//创建一个样本对象Example ex = Example.of(new Book(null, name, 0, null),ExampleMatcher.matching()//忽略样本对象中的所有null值,即null值不作为样本的比较属性.withIgnoreNullValues()//因为查询的参数只有name,所以price不参与比较,用这个方法把这个price忽略掉//因为price的默认值是0,不是null,需要额外用这个方法.withIgnorePaths("price"));//查询所有对象,把样本作为条件参数传进去比较查询Iterable books = bookDao.findAll(ex);books.forEach(System.err::println);}//样本中只有 author 作为参数来查询@ParameterizedTest@ValueSource(strings = "鸟山明")public void testFindByExampleOnlyAuthor(String author){//先创建一个样本Example<Book> bookExample = Example.of(new Book(null, null, 0, author),ExampleMatcher.matching().withIgnoreNullValues().withIgnorePaths("price"));//查询所有对象,把样本作为条件参数传进去比较查询bookDao.findAll(bookExample).forEach(System.err::println);}
application.properties
# 配置连接redis服务器的相关信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
# 选择连接redis默认16个数据库中的第11个数据库
spring.redis.database=11
# 连接redis超时的时间--30秒
spring.redis.connect-timeout=30s# 配置连接池的相关信息
# 配置这个连接池最大的连接数量
spring.redis.lettuce.pool.max-active=10
# 配置最大的能有多少个活动的、空闲的连接数量 idle-空闲
spring.redis.jedis.pool.max-idle=10
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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.4.5</version></parent><groupId>cn.ljh</groupId><artifactId>redis_boot</artifactId><version>1.0.0</version><name>redis_boot</name><properties><java.version>11</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- 配置连接池需要的依赖 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.9.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></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></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>