I have a java application that connects to a MongoDB Database through the Morphia library. My POJO that I store in the database has String field named _id and annotated with the #Id annotation (com.google.code.morphia.annotations.Id;).
I'm generating a new object ( it has null _id).
I call save(object) on the datastore provided by morphia.
The object gets updated after being stored and now has an _id value.
I call save(object) again and a new entry is created in the database with the same _id.
All consecutive save() operations on the object overwrite the old one and do not produce any new entries in the database.
So for example, after 10 save() calls on the same object my database ends up looking like this:
{ "_id" : { "$oid" : "539ade7ee4b0451f28ba0e2e"} , "className" : "blabla" , blabla ...}
{ "_id" : "539ade7ee4b0451f28ba0e2e" , "className" : "blabla" , blabla ...}
As seen those two entries have the same _id but with different representation. One has it as an object the other as a string. Normally I should have only one entry shouldn't I ?
Do not use a string for the _id. This will fix your problem:
#Id
protected ObjectId id;
While you could use protected String id (this shouldn't create duplicates IMHO), you'll have problems if you use #Reference and might run into weird edge cases elsewhere, so avoid it if possible.
Related
I have an object that has a parent class that may or may not set a _id field.
In this case, the _id field is not set and I pass an object through to
collection.insertOne(object)
Normally mongo generates a ObjectId() for a _id that isn't specified, but for some reason whenever _id is specified by a parent class and is not set, it is generating an id, bug is saving the id as a string in the database rather than an ObjectId.
What I'm passing through
{
"name" : "name"
}
Expected:
{
"_id" : ObjectId("5cb89a7cf5e722a3d493ce8b"),
"name" : "name"
}
Actual:
{
"_id" : "5cb89a7cf5e722a3d493ce8b",
"name" : "name"
}
What I think is happening is that it sees that the parent class has a _id field, but can't find it, causing somthing like this to be passed through.
{
"_id" : null,
"name" : "name"
}
and as a result mongo doesn't generate an ObjectId but a string.
Is this a bug?
Thanks in advance!
I think this is the normal behavior of mongoDB. I only use mongo with javascript and this is the behavior it always shows. I‘m wondering if this actually makes any difference for your usecase, as the id is still unique
I am using the java and Spring. As a test, I query an object by id, then try to save the same object without updating anything. I get a duplicate key exception when I do this. According to what I've read, MongoRepository.save() should do an insert if the _id is null and an update otherwise. Clearly, I should get an update.
A bit of code:
// Succeeds
Datatype sut = mongoRepository.findOne("569eac0dd4c623dc65508679");
// Fails with duplicate key.
mongoRepository.save(sut);
Why? Repeat the above with object of other classes and they work. How can I trouble shoot this? I don't see how to break it down and isolate the problem.
Thanks
The error:
27906 [http-bio-8080-exec-3] 2016-05-02 13:00:26,304 DEBUG org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver -
Resolving exception from handler
[
public gov.nist.healthcare.tools.hl7.v2.igamt.lite.web.DatatypeSaveResponse
gov.nist.healthcare.tools.hl7.v2.igamt.lite.web.controller.DatatypeController.save(
gov.nist.healthcare.tools.hl7.v2.igamt.lite.domain.Datatype)
throws gov.nist.healthcare.tools.hl7.v2.igamt.lite.web.exception.DatatypeSaveException
]:
org.springframework.dao.DuplicateKeyException: {
"serverUsed" : "127.0.0.1:27017" ,
"ok" : 1 ,
"n" : 0 ,
"err" : "E11000 duplicate key error index: igl.datatype.$_id_ dup key: { : ObjectId('569eac0dd4c623dc65508679') }" ,
"code" : 11000};
nested exception is com.mongodb.MongoException$DuplicateKey: {
"serverUsed" : "127.0.0.1:27017" ,
"ok" : 1 ,
"n" : 0 ,
"err" : "E11000 duplicate key error index: igl.datatype.$_id_ dup key: { : ObjectId('569eac0dd4c623dc65508679') }" ,
"code" : 11000}
...repeats
I just made a discovery. When saving as shown above, spring attempts an insert, this even though _id is populated.
When saving other objects ( not shown, but similar), spring performs, an update, and yes _id is again populated.
Why the difference? The documentation says spring should update when _id is populated and insert when it is not.
Is there anything else that can be causing this? Something in my object? perhaps my read converter?
Update:
I just met with the team. Upon scrutiny we determined we no longer need read converters. Problem solved by another means.
In my case the issue was that I added the version for my data model class.
#Version
private Long version;
The old documents did not have one, it resolved to null, and MongoTemplate decided that this is a new document.
In this case just initialize the version with the default value (private Long version = 0;).
When using read converters or write converters you can solve the issue by ensuring the object to be saved contains a non-null id field.
The SimpleMongoRepository checks if the entity is new before performing a conversion. In our instance, we had an object that did not have an id field and the write converter would add it.
Adding a populated id field to the object informs the SimpleMongoRepository to call save instead of insert.
The decision happens here. Your code may vary by Spring version. I hope this helps.
#Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
return mongoOperations.insert(entity, entityInformation.getCollectionName());
}
return mongoOperations.save(entity, entityInformation.getCollectionName());
}
In the database side, You may have created Unique indexes. please look at "https://docs.mongodb.com/manual/core/index-unique/" for more information.
Implement equals and hashcode methods in your Datatype entity, and make sure that mongoRepository extends CrudRepositor
(as it is described in https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#repositories) to ensure that if the objects are equals save method should merge them instead of save a new one.
I have an entity with composite id , I'm using hibernate's Multiple id properties without identifier type , like so :
#Entity
class MyEntity implements Serializable {
#Id
private Long id1;
#Id
private Long id2;
//... Getters , setters , hashcode , equals ...
}
The problem is that in my Database: id1 = 1 , id2 = 2
And if I want to add a row with : id1 = 2 , id2 = 2
I get an error ConstraintViolationException: Duplicate entry '2' for key 'id2'
I'm using hibernate 4.1.7,
The documentation link : http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#mapping-declaration-id
Update
I'm talking about a Hibernate-specific solution: Map multiple properties as #Id properties without declaring an external class to be the identifier type
Use EmbeddedId. Please refer this.
It's very possible the problem is not your code, but your db schema. Without knowing what DBMS you're using and the constraints/indexes on the table for MyEntity, it's impossible to say for sure. However my guess is that you have something like this:
CREATE UNIQUE INDEX ON my_entity (id1);
CREATE UNIQUE INDEX ON my_entity (id2);
which requires that each column independently contains only unique values, when you really want something like this:
CREATE UNIQUE INDEX ON my_entity (id1, id2);
which allows each column to contain duplicates of the same value, as long as the combination of both columns is unique.
I'm using MongoDB, latest version, in Java & Spring.
I want to be able to fill a class of type NotA from a collection which stores class type A.
Class A and Class NotA are exactly the same, with one difference: class NotA's name is, well, not A :)
for the sake of argument, class A looks like so:
public class A {
String name;
String domain;
}
And the A collection has objects which look like so:
{ "_id" : "b7990a90-7d95-4879-bb4a-5ec2fd13e262", "_class" : "com.someservice.A", "name" : "Dan", "domain":"global"}
For reasons unrelated to this question I can't read into A and then copy to NotA, I have to read directly to NotA (or some other object which is NOT A in between, and then to NotA, if there's no other choice).
I suppose I can read a DBObject and then manually copy all the fields, but would rather let the default reflection mechanism do its thing.
I've set the #Document annotation of NotA to #Document(collection = "A") but that's not enough, I need the rest of the way.
I'm guessing there's a simple solution to this problem, I just can't figure it out. Help?
If it's possible on your side - you can update your documents directly in Mongo collection to adjust _class value:
{ "_id" : "b7990a90-7d95-4879-bb4a-5ec2fd13e262", "_class" : "com.someservice.NotA", "name" : "Dan", "domain":"global"}
I am trying to have a consistent db where the username and email are unique.
http://www.mongodb.org/display/DOCS/Indexes#Indexes-unique%3Atrue
http://code.google.com/p/morphia/wiki/EntityAnnotation
My user class looks like this:
public class User {
#Indexed(unique = true)
#Required
#MinLength(4)
public String username;
#Indexed(unique = true)
#Required
#Email
public String email;
#Required
#MinLength(6)
public String password;
#Valid
public Profile profile;
public User() {
...
I used the #Indexed(unique=true) annotation but it does not work. There are still duplicates in my db.
Any ideas how I can fix this?
Edit:
I read about ensureIndexes but this seems like a wrong approach, I don't want to upload duplicate data, just to see that its really a duplicate.
I want to block it right away.
somthing like
try{
ds.save(user);
}
catch(UniqueException e){
...
}
A unique index cannot be created if there are already duplicates in the column you are trying to index.
I would try running your ensureIndex commands from the mongo shell:
db.user.ensureIndex({'username':1},{unique:true})
db.user.ensureIndex({'email':1},{unique:true})
.. and also check that the indexes are set:
db.user.getIndexes()
Morphia should have WriteConcern.SAFE set by default, which will throw an exception when trying to insert documents that would violate a unique index.
There is good explanation about unique constraint right here Unique constraint with JPA and Bean Validation , does this help you at all? So what I would do is just to validate your data at controller level (or bean validate()) when checking other errors as well. That will do the job, but its not as cool than it would be with annotation.
Edit
Alternatively see this post Inserting data to MongoDB - no error, no insert which clearly describes that mongodb doesn't raise error by default of unique indexes if you don't tell it so, try configuring your mongodb to throw those errors too and see if you can work on with solution :(
Edit 2
It also crossed my mind that play 2 has a start up global class where you could try to access your database and run your indexed column commands like this db.things.ensureIndex({email:1},{unique:true}); ?? see more at http://www.playframework.org/documentation/2.0/JavaGlobal
I had the same issue, with play framework 1.2.6 and morphia 1.2.12.
The solution for the #Indexed(unique = true) annotation, is to let morpha to re create the collection.
So if I already had the "Account" collection in mongo, and annotated the email column, and re started the play app, nothing changed in the Account indexes.
If I dropped the Account ollection, morphia re crated it, and now the email column is unique:
> db.Account.drop()
true
After play restart: (I have a job to create initial accounts...)
> db.Account.getIndexes()
[
{
"v" : 1,
"key" : {
"_id" : 1
},
"ns" : "something.Account",
"name" : "_id_"
},
{
"v" : 1,
"key" : {
"email" : 1
},
"unique" : true,
"ns" : "something.Account",
"name" : "email_1"
}
]
Now, after an insert with an already existing email, I get a MongoException.DuplicateKey exception.
To create indexes, the Datastore.ensureIndexes() method needs to be called to apply the indexes to MongoDB. The method should be called after you have registered your entities with Morphia. It will then synchronously create your indexes. This should probably be done each time you start your application.
Morphia m = ...
Datastore ds = ...
m.map(Product.class);
ds.ensureIndexes(); //creates all defined with #Indexed
Morphia will create indexes for the collection with either the class name or with the #Entity annotation value.
For example if your class name is Author:
Please make sure you have #Indexed annotation in you Entity class and you have done these two steps:
m.map(Author.class);
ds.ensureIndexes();
Check indexes on mongo db
b.Author.getIndexes()
I am adding this answer, to emphasize that you can not create indexes with a custom collection name(Entity class is Author, but your collection name is different)
This scenario is obvious in many cases, where you want to reuse the Entity class if the schema is same