Announcement Announcement Module
Collapse
No announcement yet.
Property bug in Spring Data MongoDB Support 1.0.0.M1 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Property bug in Spring Data MongoDB Support 1.0.0.M1

    I'm working on porting my tutorial
    Spring MVC 3: Using a Document-Oriented Database - MongoDB to use Spring Data Document - MongoDB Support 1.0.0.M1. However I think I've found a bug.

    The bug happens when your document (or your domain class) has an "id" property.

    Code:
    public class Person implements Serializable {
    
    	private String id;
    	private String firstName;
    	private String lastName;
    	private Double money;
    ...
    }
    Using plain MongoDB (no Spring Data support), to populate the Mongo with sample data, I call the init() method:

    Code:
    private void init() {
    		// Populate our MongoDB database
    
    		logger.debug("Init MongoDB users");
    		
    		// Drop existing collection
    		MongoDBFactory.getCollection("mydb","mycollection").drop();
    		// Retrieve collection. If not existing, create a new one
    		DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
    		
    		// Create new object
    		BasicDBObject doc = new BasicDBObject();
    		// Generate random id using UUID type 4
    		// See http://en.wikipedia.org/wiki/Universally_unique_identifier
            doc.put("id", UUID.randomUUID().toString());
            doc.put("firstName", "John");
            doc.put("lastName", "Smith");
            doc.put("money", 1000);
            coll.insert(doc);
    		
            // Create new object
            doc = new BasicDBObject();
    		// Generate random id using UUID type 4
            doc.put("id", UUID.randomUUID().toString());
            doc.put("firstName", "Jane");
            doc.put("lastName", "Adams");
            doc.put("money", 2000);
            coll.insert(doc);
            
            // Create new object
            doc = new BasicDBObject();
    		// Generate random id using UUID type 4
            doc.put("id", UUID.randomUUID().toString());
            doc.put("firstName", "Jeff");
            doc.put("lastName", "Mayer");
            doc.put("money", 3000);
            coll.insert(doc);
    		
    	}
    Calling db.mycollection.find() on the Mongo console:
    Code:
    { "_id" : ObjectId("4d5e7b3bfdece02281f253a4"), "id" : "f1e6b8b2-9d68-495c-9d6e-b81cc35cf9bc", "firstName" : "John", "lastName" : "Smith", "money" : 1000 }
    { "_id" : ObjectId("4d5e7b3bfdece02282f253a4"), "id" : "39f6ebfb-10cf-4702-a5b8-8bc2489cebcc", "firstName" : "Jane", "lastName" : "Adams", "money" : 2000 }
    { "_id" : ObjectId("4d5e7b3bfdece02283f253a4"), "id" : "f4932004-c320-47e4-b4b3-7d07f3aaabb9", "firstName" : "Jeff", "lastName" : "Mayer", "money" : 3000 }
    Notice that all properties, id, firstName, lastName, and money are present. We also have another property "_id". This works fine.

    Now, I ported the code to use Spring Data MongoDb. To populate Mongo with data, I call the following init() method:
    Code:
    private void init() {
    		// Populate our MongoDB database
    
    		logger.debug("Init MongoDB users");
    		
    		// Drop existing collection
    		mongoTemplate.dropCollection("mycollection");
    		
    		// Create new object
    		Person p = new Person ();
    		p.setId(UUID.randomUUID().toString());
    		p.setFirstName("John");
    		p.setLastName("Smith");
    		p.setMoney(1000.0);
    		
    		// Insert to db
    	    mongoTemplate.insert("mycollection", p);
    
    	    // Create new object
    		p = new Person ();
    		p.setId(UUID.randomUUID().toString());
    		p.setFirstName("Jane");
    		p.setLastName("Adams");
    		p.setMoney(2000.0);
    		
    		// Insert to db
    	    mongoTemplate.insert("mycollection", p);
            
    	    // Create new object
    		p = new Person ();
    		p.setId(UUID.randomUUID().toString());
    		p.setFirstName("Jeff");
    		p.setLastName("Mayer");
    		p.setMoney(3000.0);
    		
    		// Insert to db
    	    mongoTemplate.insert("mycollection", p);
    	}
    Notice I used the template:
    Code:
    mongoTemplate.insert("mycollection", p);
    Calling db.mycollection.find() on the Mongo console:
    Code:
    { "_id" : "69133244-fcc0-42b2-aa59-308f00c75860", "firstName" : "John", "money" : 1000, "lastName" : "Smith" }
    { "_id" : "45d236ff-6a38-4767-a973-6f3d70ff2448", "firstName" : "Jane", "money" : 2000, "lastName" : "Adams" }
    { "_id" : "9dcba269-9ed5-45c6-887e-a2de8fec334d", "firstName" : "Jeff", "money" : 3000, "lastName" : "Mayer" }
    Notice the "id" property is gone. Actually it has been merged with the property "_id". In the original implementation, these are two separate properties.

    The real problem starts when you try to update and remove the item by "id". But the fundamental question is why the discrepancy between the two implementations?

    As a workaround, I had to name the property id to something different like "pid" and everything works as expected with Spring Data MongoDb.
    Last edited by skram; Feb 18th, 2011, 08:25 AM.

  • #2
    By the way here's the new tutorial that uses the Spring Data MongoDB 1.0.0.M1

    Spring Data - MongoDB Tutorial at http://krams915.blogspot.com/2011/02...-tutorial.html

    Comment


    • #3
      Thank you, skram. Regarding the id property, as far as I understand it, spring-data-document implementation hardcodes the "id" field to be the database id (_id in json). This is not good, we should add an annotation to specify which field is the database id field(e.g., @Id), so this problem will not appear. I think this would be covered in the JIRA ticket I filed: DATADOC-40 Add support for specifying attributes to entitties, e.g., collection name, field names, etc..

      Comment


      • #4
        @oshankerShutter, thanks for verifying this problem. I thought I'm just having hallucinations I've checked the Spring Data for MongoDB source and you're right. Somehow the id is hard coded. I wouldn't call it bug or wrong behavior if the native MongoDB behaves exactly the same, but it behaves differently.

        Comment


        • #5
          You are right that the current bean property based SimpleMongoConverter maps any property named 'id' or '_id' to the Mongo _id field. We are considering other solutions for mapping and we could also add configuration for the SimpleMongoConverter to change the _id mapping behavior. I'll take a look at adding the latter for our M2 release.

          -Thomas

          Comment


          • #6
            @trisberg, thanks for the confirmation. I can't wait for the M2 release. I'm sure it's gonna be a great release as well

            Comment


            • #7
              I'm just curious why it's important to maintain a separate id and _id property. I'd assume that if you have a unique id property you could use that for the _id field and skip the _id property of type ObjectId. Am I missing something?

              -Thomas

              Comment


              • #8
                We need only one id - the issue is that it must not be hardcoded to be named "id" - the user must be able to use an annotation to say that a field named "x" is actually the id field (a la @id in JPA). This is important for projects which want to port existing code (say, currently using morphia) to spring-data-document whithout having to rewrite all their entities and supporting code.

                Comment


                • #9
                  I agree and we are working on a more advanced mapping solution for the M2 release. The current SimpleMongoConverter only handles basic JavaBeans and relies on either an _id or id property. We'll try to add some configurability to the SimpleMongoConverter but annotation support is planned for the more advanced mapping solution that we are just starting to work on.

                  Comment


                  • #10
                    The M2 mapping support will allow you to put an @Id annotation on any field (or it will look for an ObjectId or String named either "id" or "_id") but that won't put a property into the document named by that field name.

                    For example, given:

                    Code:
                    class Person {
                      @Id
                      private String empId;
                    }
                    I won't have a document that looks like:

                    Code:
                    {
                      "_id": ObjectId("blahblahblah"),
                      "empId": "blahblahblah",
                      ...other properties...
                    }
                    It will transliterate the "empId" property to "_id". Is this okay? Or are you wanting both an "_id" and an "empId" property to appear on the document?

                    Comment


                    • #11
                      If i remove my id property and add a custim field for my application(UUID) i get an Exception when i use the Repositories. IllegalArgumentException: property _id or id not found.

                      Comment


                      • #12
                        Hi Matthias, could you please open a bug for that inside our JIRA and give some more context (stack trace, entity fields, query method name)?

                        Thanks,
                        Ollie

                        Comment


                        • #13
                          Well, i don't have an account for JIRA and i don't know if it overlaps with the existing Ticket

                          https://jira.springsource.org/browse/DATADOC-44

                          My original problem is described in this topic:

                          http://forum.springsource.org/showthread.php?t=106587

                          Maybe i misunderstood how to handle Ids (PKs in RDMBM) with spring-data / mongodb

                          As a workaround i tried to remove the field

                          Code:
                          ObjectId id;
                          and replaced it with a field called

                          String uuid;

                          (created by java.util.UUID).

                          so that there is no conflict between my id property and the one created by MongoDB.

                          If i unsertood correctly, the field _id is for MongoDB only (?).

                          So i have to add a logical id for my Entity which i did with the uuid property.

                          Thats what i found out here:

                          http://krams915.blogspot.com/2011/02...-tutorial.html

                          I didn't try the MongoTemplate but i tried the Repositories:

                          Code:
                          @Repository
                          public interface IMyResourceRepository extends MongoRepository<MyResource, String> {
                          
                              MyResource findByUuid(String uuid);
                          
                              // ...
                          
                          }
                          Exception:

                          Code:
                          12:57:20,053 ERROR TestContextManager:324 - Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@2929e5e9] to prepare test instance [com....@7db5391b]
                          java.lang.IllegalStateException: Failed to load ApplicationContext
                          	at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:308)
                          	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
                          	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
                          	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
                          	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:220)
                          	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:301)
                          	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
                          	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:303)
                          	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
                          	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
                          	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
                          	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
                          	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
                          	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
                          	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
                          	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
                          	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
                          	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
                          	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
                          	at org.junit.runners.Suite.runChild(Suite.java:128)
                          	at org.junit.runners.Suite.runChild(Suite.java:24)
                          	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
                          	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
                          	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
                          	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
                          	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
                          	at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
                          	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
                          	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
                          	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
                          	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
                          	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
                          	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
                          Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'myResourceService': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private IMyResourceRepository com.....MyResourceService.resourceRepository; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'iMyResourceRepository': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Given domain class com.....domain.MyResource does not contain an id property!
                          	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:285)
                          	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1074)
                          	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
                          	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
                          	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
                          	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
                          	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
                          	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
                          	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
                          	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
                          	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
                          	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:84)
                          	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
                          	at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:280)
                          	at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:304)
                          	... 32 more

                          Comment


                          • #14
                            Hi, there's a few things to consider here. The repository support is based on the MongoTemplate which in turn uses SimpleMongoConverter by default. This converter insists on having a property name of "id" or "_id" of type ObjectId, String or BigInteger. If you want to use a different name for the id you can use an @Id annotation plus the <mongo:mapping-converter /> namespace element (which hides a MongoMappingConverter plus a MongoMappingContext). These advanced mapping features are available in the snapshots only for now and will be included into the upcoming M2 release.

                            Cheers,
                            Ollie

                            Comment


                            • #15
                              The name id is fine for me. My original problem was that the delete(Entity) method of my repository doesn't work. I thought it might be because of this id issue.

                              But it looks like it's a bug then

                              Comment

                              Working...
                              X