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.