可以使用复合键创建Spring Data Elasticsearch @Document吗?

7

我开始在Spring Boot 1.3.1上使用Spring Data Elasticsearch,并且想要使用与我的数据库中相同的实体,它具有复合键。

实体类:

@IdClass(PassengerPk.class)
@Table(name = "passenger")
@Document(indexName="passenger")
public class Passenger implements Serializable {

    @Id
    @ManyToOne
    @JoinColumn(columnDefinition="long", name="user_id", referencedColumnName="id")
    private User user;

    @Id
    @ManyToOne
    @JoinColumn(columnDefinition="long", name="scheduler_id", referencedColumnName="id")
    private Scheduler scheduler;

    @Column(name = "is_active")
    private Boolean isActive;

    ...
}

密钥类:

public class PassengerPk implements Serializable {

    private Long user;
    private Long scheduler;

    public PassengerPk() {
    }

    public PassengerPk(Long user, Long scheduler) {
        this.user = user;
        this.scheduler = scheduler;
    }
    ...
}

JPA Elasticsearch 存储库:
public interface PassengerSearchRepository extends ElasticsearchRepository<Passenger, PassengerPk> {

}

数据库: 数据库关系

如果我尝试编译此代码,我会收到此错误。

Caused by: java.lang.IllegalArgumentException: Unsuppored ID type class com.dualion.test.domain.PassengerPk
    at org.springframework.data.elasticsearch.repository.support.ElasticsearchRepositoryFactory.getRepositoryBaseClass(ElasticsearchRepositoryFactory.java:79) ~[spring-data-elasticsearch-1.3.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepositoryInformation(RepositoryFactorySupport.java:238) ~[spring-data-commons-1.11.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:181) ~[spring-data-commons-1.11.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:251) ~[spring-data-commons-1.11.1.RELEASE.jar:na]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:237) ~[spring-data-commons-1.11.1.RELEASE.jar:na]
    at org.springframework.data.elasticsearch.repository.support.ElasticsearchRepositoryFactoryBean.afterPropertiesSet(ElasticsearchRepositoryFactoryBean.java:55) ~[spring-data-elasticsearch-1.3.1.RELEASE.jar:na]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
    ... 71 common frames omitted

我该如何修改我的代码?

谢谢。


有人能给我一个解决方案吗? - kn4ls
1
你如何继承@IdClass依赖项?我认为它是由某些JPA依赖项引入的类,使用JPA进行ES配置并不那么容易,我不知道该怎么做。所以当然你不能在ES中使用它。 顺便说一句,了解如何实现你的目标对我来说非常有用。 - andPat
最终使用HibernateSearch而不是ES,因为它的实现更加容易。 - kn4ls
1个回答

1
我曾在其他地方阅读相关答案,并得出结论这是不可能的;然而,我的顽固使我找到了解决方案。
TLDR; 强制使用一个新的仓库,它获取你的复合 ID 的 hashCode 并使用其 String 值作为 ID。
步骤...
创建一个新的 Repository,可以处理复合ID:
public class HashKeyedRepository<T, ID extends Serializable> extends AbstractElasticsearchRepository<T, ID> {

    public HashKeyedRepository() {
        super();
    }

    public HashKeyedRepository(ElasticsearchEntityInformation<T, ID> metadata,
                                 ElasticsearchOperations elasticsearchOperations) {
        super(metadata, elasticsearchOperations);
    }

    public HashKeyedRepository(ElasticsearchOperations elasticsearchOperations) {
        super(elasticsearchOperations);
    }

    @Override
    protected String stringIdRepresentation(ID id) {
        return String.valueOf(id.hashCode());
    }
}

请注意,这假定您已经正确实现了复合 ID 类上的 .hashCode 方法。
接下来,您需要创建一个新的 RepositoryFactoryBean,它将返回这个新的 Repository
public class CustomElasticsearchRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable> extends ElasticsearchRepositoryFactoryBean<T, S, ID> {

    private ElasticsearchOperations operations;

    public CustomElasticsearchRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    public void setElasticsearchOperations(ElasticsearchOperations operations) {
        super.setElasticsearchOperations(operations);
        Assert.notNull(operations);     
        this.operations = operations;
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory() {
        return new ElasticsearchRepositoryFactory(operations) {
            @Override
            protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
                if (!Integer.class.isAssignableFrom(metadata.getIdType()) && !Long.class.isAssignableFrom(metadata.getIdType()) && !Double.class.isAssignableFrom(metadata.getIdType()) && metadata.getIdType() != String.class && metadata.getIdType() != UUID.class) {
                    return HashKeyedRepository.class;
                }
                return super.getRepositoryBaseClass(metadata);
            }
        };
    }
}

最后,当启用你的存储库时,请指定新的 RepositoryFactoryBean 类:
@EnableElasticsearchRepositories(basePackages = "xxx.xxx.repository.search", repositoryFactoryBeanClass = CustomElasticsearchRepositoryFactoryBean.class)

此实现将回退到默认值,如果使用了本文撰写时支持的任何 ID(即 String、UUID、Number)中的任何一个。我不知道这是否是一个很好的解决方案,因为存在与 .hashCode 的冲突可能性,但它目前对我有效。
PS:我正在使用 lombok 的 @Data 自动生成 .hashCode。
PPS:我看到其他人(非 Java)提到的另一种解决方案是对 ID(即 JSON)的序列化进行 base64 编码。我认为这可以保证没有冲突,但您必须确保剥离任何多余字符(即空格)并保证属性的顺序才能使其有效。

哈希不是唯一的,你最终会得到重复的ID。 - Konstantin Kulagin
我已经注意到了。但是,如果您的复合ID由两个唯一的整数组成,并且您使用了一个正确利用质数的哈希码实现,那么冲突的可能性应该是最小的。当然,您的数据集越大,冲突的可能性就越大,但即使在这种情况下,您也可以决定冲突的机会是否影响您的最终解决方案(例如,在DB上实现全局搜索,如果它们的复合ID冲突,您能容忍缺少1/x的结果吗?)。 - Andrew Wynham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接