7
votes

I started to work with Spring Data Elasticsearch on Spring Boot 1.3.1 and I want to use the same Entity that use in my database, and it has compound key.

Entity class:

@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;

    ...
}

Key class:

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 repository:

public interface PassengerSearchRepository extends ElasticsearchRepository<Passenger, PassengerPk> {

}

Database: database relationships

If I try to compile this code, I get this error.

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

How I can modify my code?

Thanks

1
anybody can give me a solution?kn4ls
How do you inherit @IdClass dependency? I think that it is a class brought by some JPA dependency, ES with JPA is not so trivial to configure, I don't know how. So of course you cannot use it for ES. By the way would be very useful for me to know how to achieve your goal.andPat
Finally use HibernateSearch and not ES, beacause implementation it's more easy.kn4ls

1 Answers

1
votes

I had read related answers elsewhere and had concluded this was not possible; however, my stubbornness got the best of me and I figured out a solution.

TLDR; Force spring to use a new repository that takes the hashCode of your composite id and uses its String value as its id.

Steps...

Create a new Repository that can handle composite ids:

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());
    }
}

Note that this assumes that you have implemented .hashCode on your composite id class correctly to work.

Next you have to create a new RepositoryFactoryBean that will return this new 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);
            }
        };
    }
}

Finally, when enabling your repositories, specify your new RepositoryFactoryBean class:

@EnableElasticsearchRepositories(basePackages = "xxx.xxx.repository.search", repositoryFactoryBeanClass = CustomElasticsearchRepositoryFactoryBean.class)

This implementation falls back to the default if any of the supported IDs as of this writing are used instead (i.e. String, UUID, Number). I don't know if its a GREAT solution given that there are possibilities of conflict with .hashCode but its working for me right now.

PS I'm using lomboks @Data to auto generate .hashCode for me.

PPS Another solution I've seen others (non java) mention is to base64 encode a serialized version of the id (i.e. JSON). I think this would guarantee no conflicts but you'd have to be sure to strip any excess characters (i.e. whitespace) and guarantee order of properties for it to be effective.