Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PersistenceConstructor argument variable name doesn't match instance variable name

I'm trying to persist the following object with spring-data-mongodb version 1.1.1.RELEASE:

@Document
public static class TestObject {

    private final int m_property;

    @PersistenceConstructor
    public TestObject(int a_property) {
        m_property = a_property;
    }

    public int property() {
        return m_property;
    }

}

I get a MappingException when I try to read the object back from the database (see full stacktrace below)

The naming convention my group uses requires argument variable names to be prefaced by a_ and instance variable names to be prefaced by m_. It seems like spring-data-mongodb is making the assumption that the constructor argument variable names must match the object instance variable names.

  • Why doesn't spring-data-mongodb use the constructor argument to instance variable mapping that I define within the constructor?
  • Is there another way to define this mapping such that spring-data-mongodb will properly construct my object, or is my only option to break the naming convention?

.

Exception in thread "main" org.springframework.data.mapping.model.MappingException: No property a_property found on entity class com.recorder.TestRecorder$TestObject to bind constructor parameter to!
    at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:90)
    at org.springframework.data.convert.ReflectionEntityInstantiator.createInstance(ReflectionEntityInstantiator.java:70)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:229)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:209)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:173)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:169)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:72)
    at org.springframework.data.mongodb.core.MongoTemplate$ReadDbObjectCallback.doWith(MongoTemplate.java:1820)
    at org.springframework.data.mongodb.core.MongoTemplate.executeFindMultiInternal(MongoTemplate.java:1542)
    at org.springframework.data.mongodb.core.MongoTemplate.findAll(MongoTemplate.java:1064)
    at com.recorder.TestRecorder.main(TestRecorder.java:43)
like image 772
Ben Baumgold Avatar asked Dec 12 '12 03:12

Ben Baumgold


People also ask

Can parameters and instance variables have the same name?

There's no problem with giving parameter names and instance variable names the same name.

Can constructor parameters have the same name as instance variables?

Parameter Names This name is used within the method body to refer to the passed-in argument. The name of a parameter must be unique in its scope. It cannot be the same as the name of another parameter for the same method or constructor, and it cannot be the name of a local variable within the method or constructor.


1 Answers

tl;dr

We need to rely on constructor argument names to match field names to find out which field of the document to pull in. If you want to customize this use @Value("#root.field_name") on the constructor argument.

Long story

If you're using a constructor with arguments to let Spring Data instantiate the given class using this constructor we have to hand parameters to the constructor upon invocation. To find out which document field we have to hand in, we need to inspect the matching property for potential field name customization. See the following example:

@Document
class MyEntity {

  @Field("foo")
  private String myField;

  public MyEntity(String myField) {
    this.myField = myField;
  }
}

In this case we need to pipe the field foo into the constructor and there's no way to find out about this if we don't somehow can obtain a reference to the property. If the constructor parameter name was something different, how should we reliably find out which field value should actually be used as argument? The example you've shown in your question can never work out of the box, as your document would contain a m_property field and there's absolutely no way to find out you actually want that to be injected, except adding more explicit configuration.

To customize this behavior you can use Spring's @Value annotation and inject a custom document field into the constructor. The document itself is available through the #root variable. So you could easily alter my sample above to:

@Document
class MyEntity {

  @Field("foo")
  private String myField;

  public MyEntity(@Value("#root.foo") String somethingDifferent) {
    this.myField = somethingDifferent;
  }
}

I'd strongly recommend that you add custom field names to your properties as well as you don't want to expose your property naming conventions to the database. The usage pf @Value is briefly mentioned in the reference docs but I've created a ticket to improve the docs and make this more obvious.

like image 146
Oliver Drotbohm Avatar answered Sep 20 '22 23:09

Oliver Drotbohm