使用 Spring Data 在 Redis 中查询嵌套对象

Posted

技术标签:

【中文标题】使用 Spring Data 在 Redis 中查询嵌套对象【英文标题】:Query Nested Objects in Redis using Spring Data 【发布时间】:2018-01-07 05:18:27 【问题描述】:

我有一个如下的 java 对象,我存储在 redis 存储中。

@RedisHash("UserProfile")
public class UserProfile implements Serializable 
    @Id String id;
    @Reference PersonalInfo personalInfo = new PersonalInfo();
    @Reference BusinessInfo businessInfo = new BusinessInfo();
    ...

现在,PersonalInfo 对象的结构如下:

public class PersonalInfo 
    private String firstName;
    private String lastName;
    @Indexed private String userId;
    private ContactInfo contactInfo = new ContactInfo();

请注意,PersonalInfo 对象中的 userId 已编入索引。

我的目标是使用 spring 数据通过用户 ID 查询 redis 中的 UserProfile 对象。

CrudRepository 接口将无法直接执行findByUserId(),除非我在 UserProfile 对象中有 userId。 如果有更好的方法来编写查询以通过 userid 查找用户,我不想将 userId 直接包含在 UserProfile 对象中。

对我如何做这件事有什么建议吗?

更新: 我将如下方法添加到存储库:

public interface UserProfileRepository extends CrudRepository<UserProfile, String> 
    List<UserProfile> findByPersonalInfo_UserId(String userId);

但是在代码中调用此方法时,我收到以下错误。不确定我正在使用的 redis/spring 库是否存在问题。我正在使用

springboot-starter-parent 版本:1.5.2.RELEASE spring-data-redis 版本:1.8.6.RELEASE

例外:

java.lang.NoSuchMethodError: org.springframework.data.keyvalue.core.query.KeyValueQuery.getCriteria()Ljava/lang/Object;
    at org.springframework.data.redis.repository.query.RedisQueryCreator.complete(RedisQueryCreator.java:101) ~[spring-data-redis-1.8.6.RELEASE.jar:na]
    at org.springframework.data.redis.repository.query.RedisQueryCreator.complete(RedisQueryCreator.java:41) ~[spring-data-redis-1.8.6.RELEASE.jar:na]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery.createQuery(KeyValuePartTreeQuery.java:184) ~[spring-data-keyvalue-1.2.1.RELEASE.jar:na]
    at org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery.prepareQuery(KeyValuePartTreeQuery.java:128) ~[spring-data-keyvalue-1.2.1.RELEASE.jar:na]
    at org.springframework.data.keyvalue.repository.query.KeyValuePartTreeQuery.execute(KeyValuePartTreeQuery.java:87) ~[spring-data-keyvalue-1.2.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:483) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:61) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57) ~[spring-data-commons-1.13.1.RELEASE.jar:na]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.7.RELEASE.jar:4.3.7.RELEASE]
    at com.sun.proxy.$Proxy66.findByPersonalInfo_UserId(Unknown Source) ~[na:na]

更新 2: 我可以通过将 springboot 版本更改为 1.5.6.RELEASE 来修复上述异常。

但是,现在我面临的问题是,当我通过 userid 查询时,即使有匹配项,它也没有给我 UserProfiles。

例如redis-cli显示:

KEYS *
UserProfile:d0876c1f-5684-4ee4-acbb-7a92b7fa25ae
UserProfile:3591e476-29d7-4c3c-a7e5-6272231f96e0
UserProfile:3591e476-29d7-4c3c-a7e5-6272231f96e0:idx
UserProfile:d0876c1f-5684-4ee4-acbb-7a92b7fa25ae:idx
UserProfile:51814a77-bf40-4912-b700-cfa50d1c4b25
UserProfile:personalInfo.userId:adrian.tremblay
UserProfile:66ba8276-1bb0-47a0-a54d-4c9d99b8bf80
UserProfile:66ba8276-1bb0-47a0-a54d-4c9d99b8bf80:idx
UserProfile:personalInfo.userId:anthony.turner
UserProfile:personalInfo.userId:ashleigh.hayes
UserProfile:a81356b0-27ef-4a34-92a3-629be5114f0e
UserProfile

当我执行 findAll、findById 时,它会返回结果。当我执行 findByPersonalInfo_UserId() 并传递诸如 anthony.turner 或 ashleigh.hayes 之类的值时,什么也没有出现。我是否以某种不正确的格式提供了 userId?我尝试了一些不同的方法,但没有奏效。

更新 3:我还尝试删除 UserProfile 对象中的 @Reference(以便 redis 对象变平,而不是指向引用)。但这也无济于事。

【问题讨论】:

你得到正确的unsver 【参考方案1】:

尝试命名您的方法,不要使用任何下划线,例如:

List<UserProfile> findByPersonalInfoUserId(String userId);

有类似的情况,它对我有用。也不需要@Reference 注释。

使用spring-boot-starter-parent 2.0.3.RELEASE

【讨论】:

【参考方案2】:

我遇到了同样的情况,

基本上,如果你有一个引用,那么 Redis 会在内部存储引用的 Id,它不能直接查询,因为它需要获取整个对象来匹配,是的,它是 Redis(或任何基于哈希/非关系的数据库)的缺点)。

您可能会问为什么不可能,从技术上讲是可以做到的,但这将是处理器和内存密集型操作,最终会影响性能,而 Redis 以其性能而闻名。

@Reference 仅应仅在您希望在多个对象(此处为 UserInfo)之间共享单个对象(此处为 PersonalInfo)时使用。

如果您想使用 @Reference 可能的解决方案(假设 UserProfile 的 'id' 不等于 PersonalInfo 的 userId ):

我在这个答案结束时的个人解决方案:

    (不推荐 - 性能慢) - 首先是 findAllUserProfile(),然后遍历 UserProfile 中的每个 PersonalInfo 并匹配所需的字段(此处为 userId

    (推荐)- 重构您的架构,问我为什么,因为如果您希望保留的引用不仅仅是阅读(我的意思也是查询),那么在这种情况下非关系数据库不适合您.如果可能,重组以使其适合非关系结构。

注意: 上面@Aurimas 的解决方案很好,最适合您的情况,如果您不添加引用,那么它将创建一个新对象,该对象将存储在UserProfile 对象中,但任何PersonalInfo 中的更改将特定于此 UserProfile 并且不会反映在其他 UserProfile 对象中。但它的优点是它可以查询:-)

因此,根据您的情况,我建议放弃 @ReferencePersonalInfo,因为逻辑上它不应该被共享(即个人 ;-)),并继续使用 @Aurimas 解决方案。

【讨论】:

以上是关于使用 Spring Data 在 Redis 中查询嵌套对象的主要内容,如果未能解决你的问题,请参考以下文章

使用 Spring Data 在 Redis 中查询嵌套对象

Spring之Redis访问(Spring-data-redis)

指定与 spring-data-redis 一起使用的逻辑数据库

spring-data-redis 使用过程中踩过的坑

Spring-data-redis + Lettuce 如何使用 Pipeline

spring data redis