2

Using Spring Data with MongoDB.

Problem is in instantiating a bean retrieved from Mongo because the constructor has some argument which actually is inside a nested object. Persistance is not an issue. Retrieval Is.

Class structure is as below where B is nested inside A. A sets B instantiating it using one of its constructor Params.

Class A

Class A{
 int prop1;
 B b; 

 @JsonCreator
 public A(  @JsonProperty int prop1, @JsonProperty  int prop2){
  this.prop1 = prop1;
  this.b = new B(prop2);
 }
}

Class B which is nested inside A

Class B(){
 int prop2;

 @JsonCreator
 public B(@JsonProperty int prop2){
  this.prop2 = prop2;
 }
}

When Object is received by REST API in the below Json form:

 {"prop1":"Hello1","prop2":"Hello2"}

Spring Controller receives it and maps it correctly to Object A. Since Spring uses no-arg constructor by default, I used jsoncreator annotation on arg-constructor and it is added to MongoDB without any fuss.

The data is stored in below format as per bean structure which is right.

      {"prop1":"Hello1","b":{"prop2":"Hello2"}}

Coming to the problem: Error comes when I try to retrieve from Mongo. It says:

 org.springframework.data.mapping.model.MappingException: No property prop2 
 found on entity class A to bind constructor parameter to

How Can I tell SpringData to use object B which contains prop2 when getting it from SpringData? (Perhaps thats not possible)

I thought perhaps adding one more constructor will do the trick, where I use object B as one of the constructor arguments like below:

   public A(int prop1, B b){
       ......
       ......
   }

Adding one more constructor works with ObjectMapper but again not with SpringData.

This time a new exception is thrown :

      org.springframework.data.mapping.model.MappingInstantiationException: 
      Failed to instantiate A using constructor NO_CONSTRUCTOR with 
      arguments. 

I checked the above error where List is being used/not used , but I tried both ways and didnt resolve.

Please note: I am using object B as nested since it contains common properties which will be used by lot of other beans ( could be abstract class , but I will need to try that later, but somehow abstract class seems limiting)

How do have SpringData create object A ?

DragonZoned
  • 201
  • 3
  • 14

1 Answers1

4

Looks like a faux pas.

Should have used better terms in searching. Would have found the correct answer. Just in case somebody faces this problem ...

Some searching led me to this below question:

How exactly does spring-data-mongodb handle constructors when rehydrating objects?

The above question mentions few things which gave me the solution. The answer lies in using another constructor for deserialization from SpringData to bean. This constructor needs to be annotated with:

    @PersistenceConstructor

In the example in the question, if I use the second constructor with this annotation, retrieval works.

        @PersistenceConstructor
        public A(int prop1, B b){
         ......
         ......
        }

So now , I have 2 constructors , one with @jsoncreator and another with @PersistenceConstructor. But to add , this other constructor can be private also if you have no other use for it other than for retrieval from SpringData and it will not be used in code by anybody thus leaving your Business Logic intact. Hence the below will also work:

        @PersistenceConstructor
        private A(int prop1, B b){
         ......
         ......
        }
DragonZoned
  • 201
  • 3
  • 14