Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring -Data MongoDB issue with field which is an interface

I'm using Spring-Data for MongoDB:

Version information - org.mongodb.mongo-java-driver version 2.10.1, org.springframework.data.spring-data-mongodb version 1.2.1.RELEASE.

I have a case that's similar to the one defined in here, which is (sorry for the formatting...):

I just started developing some app in Java with spring-data-mongodb and came across some issue that I haven't been able to solve:

I have a couple of document beans like this:

@Document(collection="myBeanBar")
public class BarImpl implements Bar {
    String id;
    Foo foo;
    // More fields and methods ... 
}

@Document
public class FooImpl implements Foo {
    String id;
    String someField;
    // some more fields and methods ...
} 

And I have a repository class with a method that simply invokes a find similar to this:

public List<? extends Bar> findByFooField(final String fieldValue) {
    Query query = Query.query(Criteria.where("foo.someField").is(fieldValue));
    return getMongoOperations().find(query, BarImpl.class);
} 

Saving a Bar works just fine, it would save it in mongo along with the "_class" attribute for both Foo and Bar. However, finding by some attribute in Foo would throw an exception like this:

Exception in thread "main" java.lang.IllegalArgumentException: No
property someField found on test.Foo!
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:225)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getPath(QueryMapper.java:202)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getTargetProperty(QueryMapper.java:190)
    at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:86)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1336)
    at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1322)
    at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:495)
    at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:486)

The solution that was given was to use the @TypeAlias annotation on the abstract class, which told the framework to use a specific implementation (in this case FooImpl).

In my case, I have interface members, instead of abstract members:

@Document(collection="myBeanBar")
public class BarImpl implements Bar {
    String id;
    IFoo foo;
    // More fields and methods ...
}

I'm very reluctant to put an annotation on the interface IFoo that will give a default implementation, instead I'd like to tell the framework what this field's default implementation in the context of the implementing BarImpl class, similar to @JsonTypeInfo:

@Document(collection="myBeanBar") 
public class BarImpl implements Bar {
    String id;    

    @JsonTypeInfo(use = Id.CLASS, defaultImpl = FooImpl.class)
    IFoo foo; 

    // More fields and methods ... 
}

I found this answer, which more or less says to avoid using interfaces. but I'd be happy to know if there's no better option.

Any ideas?

Thanks!

like image 408
Ido Cohn Avatar asked Jul 28 '13 16:07

Ido Cohn


2 Answers

My problem is similar to the question, but the exception thrown is a bit different:

Could not instantiate bean class [class name]: Specified class is an interface

This happens when one of the fields of my DB class is declared as an interface. Saving this field is fine but exception thrown when reading it from MongoDB. Finally I found the solution that makes use of org.springframework.core.convert.converter.Converter.

TWO steps to do, 1. construct a class that implements Converter; 2. register the converter in the servlet context. And YES, you don't have to modify any existing code, such as adding annotation.

Below is my model class, where the field Data is an interface:

@Document(collection="record")
public class Record {
    @Id
    private String id;

    // Data is an interface
    private Data data;

    // And some other fields and setter/getter methods of them
}

The converter:

@ReadingConverter
public class DataReadConverter implements Converter<DBObject, Data> {
    @Override
    public Data convert(DBObject source) {
        // Your implementation to parse the DBObject,
        // this object can be BasicDBObject or BasicDBList,
        // and return an object instance that implements Data.

        return null;
    }
}

The last thing to do is to register the converter, my configuration is in xml:

<mongo:mongo id="mongo" />

<mongo:db-factory mongo-ref="mongo" dbname="example" />

<mongo:mapping-converter>
    <mongo:custom-converters>
        <mongo:converter>
            <beans:bean class="com.example.DataReadConverter" />
        </mongo:converter>
    </mongo:custom-converters>
</mongo:mapping-converter>

<beans:bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <beans:constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
    <beans:constructor-arg name="mongoConverter" ref="mappingConverter" />
</beans:bean>

Deploy the application and try again. It should properly parse the DBObject from MongoDB on the interface field.

The version of my Spring MongoDB application is : spring-*-4.1.0 and spring-data-mongodb-1.6.0.

like image 103
Victor Wong Avatar answered Nov 01 '22 17:11

Victor Wong


I had the same error message as @victor-wong

Could not instantiate bean class [class name]: Specified class is an interface

The following code solves the problem with Spring Boot 2.3.2 and spring-data-mongodb 3.0.2

The converter:

import org.bson.Document;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;

@ReadingConverter
public class DataReadConverter implements Converter<Document, Data> {

    @Override
    public Data convert(Document source) {

        return new DataImpl(source.get("key"));
    }
}

The last thing to do is to register the converter

    @Bean
    public MongoCustomConversions customConversions() {
        return new MongoCustomConversions(
                List.of(
                        new DataReadConverter()
                )
        );
    }

Additional information may be found here: https://jira.spring.io/browse/DATAMONGO-2391

like image 1
RoBeaToZ Avatar answered Nov 01 '22 17:11

RoBeaToZ