54R1N
54R1N

Reputation: 11

Spring Data 3.0.5 MongoDB and ElasticSearch Domain Class mixed Annotation

I'm migrating our application from Spring Boot 1.5.9 to version 2.0.0.

In version 1.5.9 we have successfully used mixed Annotations on several Domain Classes e.g:

...
@org.springframework.data.mongodb.core.mapping.Document(collection = "folder")
@org.springframework.data.elasticsearch.annotations.Document(indexName = "folder")
public class Folder {
    ...
}

The same approach causes probems in Spring Boot 2.0.0. When MongoDB annotatnion @DBRef is used, Spring throws exception while ElasticsearchRepository creation:

java.lang.IllegalStateException: No association found!

Here comes classes and confs

pom.xml

   ...
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <parent>
        <groupId>org.springfrsamework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>
    ...
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>
    ...

Application.java

...
@EnableMongoRepositories("com.hydra.sbmr.repoMongo")
@EnableElasticsearchRepositories("com.hydra.sbmr.repoElastic")
@SpringBootApplication
public class Application extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

Folder.java (Note this @DBRef couses exception)

package com.hydra.sbmr.model;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.DBRef;

@org.springframework.data.mongodb.core.mapping.Document(collection = "folder")
@org.springframework.data.elasticsearch.annotations.Document(indexName = "folder")
public class Folder {

    @Id
    @Getter @Setter private String id;

    // Why MongoDB core mapping @DBRef causes java.lang.IllegalStateException: No association found! exception
    // while ElasticsearchRepository creation???
    @DBRef
    @Getter @Setter private Profile profile;

    @Getter @Setter private String something;

}

Profile.java

package com.hydra.sbmr.model;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;

@org.springframework.data.mongodb.core.mapping.Document(collection = "profile")
public class Profile {

    @Id
    @Getter @Setter private String id;

    @Getter @Setter String blah;

}

FolderElasticRepository.java

package com.hydra.sbmr.repoElastic;

import com.hydra.sbmr.model.Folder;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface FolderElasticRepository extends ElasticsearchRepository<Folder, String> {

}

You can find whole mini project on GitHub: https://github.com/hydraesb/sbmr

My question:

Upvotes: 1

Views: 645

Answers (2)

navid_gh
navid_gh

Reputation: 1883

I have faced this problem also and I fixed with solution of @ybouraze

@Bean
fun elasticsearchTemplate(client: JestClient, converter: ElasticsearchConverter, builder: Jackson2ObjectMapperBuilder): ElasticsearchOperations {
    val entityMapper = CustomEntityMapper(builder.createXmlMapper(false).build())
    val mapper = DefaultJestResultsMapper(converter.mappingContext, entityMapper)
    return JestElasticsearchTemplate(client, converter, mapper)
}

@Bean
@Primary
fun mappingContext(): SimpleElasticsearchMappingContext {
    return MappingContext()
}

@Bean
fun elasticsearchConverter(): ElasticsearchConverter {
    return MappingElasticsearchConverter(mappingContext())
}

inner class CustomEntityMapper(private val objectMapper: ObjectMapper) : EntityMapper {

    init {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
    }

    @Throws(IOException::class)
    override fun mapToString(`object`: Any): String {
        return objectMapper.writeValueAsString(`object`)
    }

    @Throws(IOException::class)
    override fun <T> mapToObject(source: String, clazz: Class<T>): T {
        return objectMapper.readValue(source, clazz)
    }
}

inner class MappingContext : SimpleElasticsearchMappingContext() {
    override fun createPersistentProperty(property: Property, owner: SimpleElasticsearchPersistentEntity<*>, simpleTypeHolder: SimpleTypeHolder): ElasticsearchPersistentProperty {
        return PersistentProperty(property, owner, simpleTypeHolder)
    }
}

inner class PersistentProperty(property: Property, owner: SimpleElasticsearchPersistentEntity<*>, simpleTypeHolder: SimpleTypeHolder) : SimpleElasticsearchPersistentProperty(property, owner, simpleTypeHolder) {
    override fun isAssociation(): Boolean {
        return false
    }
}

Upvotes: 0

ybouraze
ybouraze

Reputation: 31

I have the same issue and the solution that i found is to extends SimpleElasticsearchMappingContext like this :

    package com.mypackage;

    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.elasticsearch.client.Client;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
    import org.springframework.data.elasticsearch.core.EntityMapper;
    import org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter;
    import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

    import java.io.IOException;

    @Configuration
    public class ElasticsearchConfiguration {

        @Bean
        public ElasticsearchTemplate elasticsearchTemplate(Client client, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
            return new ElasticsearchTemplate(client, new MappingElasticsearchConverter(new CustomElasticsearchMappingContext()),
                new CustomEntityMapper(jackson2ObjectMapperBuilder.createXmlMapper(false).build()));
        }

        public class CustomEntityMapper implements EntityMapper {

            private ObjectMapper objectMapper;

            public CustomEntityMapper(ObjectMapper objectMapper) {
                this.objectMapper = objectMapper;
                objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
                objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
            }

            @Override
            public String mapToString(Object object) throws IOException {
                return objectMapper.writeValueAsString(object);
            }

            @Override
            public  T mapToObject(String source, Class clazz) throws IOException {
                return objectMapper.readValue(source, clazz);
            }
        }
    }

    package com.mypackage;

    import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
    import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchMappingContext;
    import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentEntity;
    import org.springframework.data.mapping.model.Property;
    import org.springframework.data.mapping.model.SimpleTypeHolder;

    public class CustomElasticsearchMappingContext extends SimpleElasticsearchMappingContext {
        @Override
        protected ElasticsearchPersistentProperty createPersistentProperty(Property property, SimpleElasticsearchPersistentEntity owner, SimpleTypeHolder simpleTypeHolder) {
            return new CustomElasticsearchPersistentProperty(property, owner, simpleTypeHolder);
        }
    }

    package com.mypackage;

    import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
    import org.springframework.data.elasticsearch.core.mapping.SimpleElasticsearchPersistentProperty;
    import org.springframework.data.mapping.PersistentEntity;
    import org.springframework.data.mapping.model.Property;
    import org.springframework.data.mapping.model.SimpleTypeHolder;

    public class CustomElasticsearchPersistentProperty extends SimpleElasticsearchPersistentProperty {

        public CustomElasticsearchPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) {
            super(property, owner, simpleTypeHolder);
        }

        @Override
        public boolean isAssociation() {
            return false;
        }
    }

Upvotes: 3

Related Questions