Spring mongorepository save throws duplicate key exception - java

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.

Related

Struts2 and Hibernate insert operation error [duplicate]

org.hibernate.HibernateException: identifier of an instance
of org.cometd.hibernate.User altered from 12 to 3
in fact, my user table is really must dynamically change its value, my Java app is multithreaded.
Any ideas how to fix it?
Are you changing the primary key value of a User object somewhere? You shouldn't do that. Check that your mapping for the primary key is correct.
What does your mapping XML file or mapping annotations look like?
You must detach your entity from session before modifying its ID fields
In my case, the PK Field in hbm.xml was of type "integer" but in bean code it was long.
In my case getters and setter names were different from Variable name.
private Long stockId;
public Long getStockID() {
return stockId;
}
public void setStockID(Long stockID) {
this.stockId = stockID;
}
where it should be
public Long getStockId() {
return stockId;
}
public void setStockId(Long stockID) {
this.stockId = stockID;
}
In my case, I solved it changing the #Id field type from long to Long.
In my particular case, this was caused by a method in my service implementation that needed the spring #Transactional(readOnly = true) annotation. Once I added that, the issue was resolved. Unusual though, it was just a select statement.
Make sure you aren't trying to use the same User object more than once while changing the ID. In other words, if you were doing something in a batch type operation:
User user = new User(); // Using the same one over and over, won't work
List<Customer> customers = fetchCustomersFromSomeService();
for(Customer customer : customers) {
// User user = new User(); <-- This would work, you get a new one each time
user.setId(customer.getId());
user.setName(customer.getName());
saveUserToDB(user);
}
In my case, a template had a typo so instead of checking for equivalency (==) it was using an assignment equals (=).
So I changed the template logic from:
if (user1.id = user2.id) ...
to
if (user1.id == user2.id) ...
and now everything is fine. So, check your views as well!
It is a problem in your update method. Just instance new User before you save changes and you will be fine. If you use mapping between DTO and Entity class, than do this before mapping.
I had this error also. I had User Object, trying to change his Location, Location was FK in User table. I solved this problem with
#Transactional
public void update(User input) throws Exception {
User userDB = userRepository.findById(input.getUserId()).orElse(null);
userDB.setLocation(new Location());
userMapper.updateEntityFromDto(input, userDB);
User user= userRepository.save(userDB);
}
Also ran into this error message, but the root cause was of a different flavor from those referenced in the other answers here.
Generic answer:
Make sure that once hibernate loads an entity, no code changes the primary key value in that object in any way. When hibernate flushes all changes back to the database, it throws this exception because the primary key changed. If you don't do it explicitly, look for places where this may happen unintentionally, perhaps on related entities that only have LAZY loading configured.
In my case, I am using a mapping framework (MapStruct) to update an entity. In the process, also other referenced entities were being updates as mapping frameworks tend to do that by default. I was later replacing the original entity with new one (in DB terms, changed the value of the foreign key to reference a different row in the related table), the primary key of the previously-referenced entity was already updated, and hibernate attempted to persist this update on flush.
I was facing this issue, too.
The target table is a relation table, wiring two IDs from different tables. I have a UNIQUE constraint on the value combination, replacing the PK.
When updating one of the values of a tuple, this error occured.
This is how the table looks like (MySQL):
CREATE TABLE my_relation_table (
mrt_left_id BIGINT NOT NULL,
mrt_right_id BIGINT NOT NULL,
UNIQUE KEY uix_my_relation_table (mrt_left_id, mrt_right_id),
FOREIGN KEY (mrt_left_id)
REFERENCES left_table(lef_id),
FOREIGN KEY (mrt_right_id)
REFERENCES right_table(rig_id)
);
The Entity class for the RelationWithUnique entity looks basically like this:
#Entity
#IdClass(RelationWithUnique.class)
#Table(name = "my_relation_table")
public class RelationWithUnique implements Serializable {
...
#Id
#ManyToOne
#JoinColumn(name = "mrt_left_id", referencedColumnName = "left_table.lef_id")
private LeftTableEntity leftId;
#Id
#ManyToOne
#JoinColumn(name = "mrt_right_id", referencedColumnName = "right_table.rig_id")
private RightTableEntity rightId;
...
I fixed it by
// usually, we need to detach the object as we are updating the PK
// (rightId being part of the UNIQUE constraint) => PK
// but this would produce a duplicate entry,
// therefore, we simply delete the old tuple and add the new one
final RelationWithUnique newRelation = new RelationWithUnique();
newRelation.setLeftId(oldRelation.getLeftId());
newRelation.setRightId(rightId); // here, the value is updated actually
entityManager.remove(oldRelation);
entityManager.persist(newRelation);
Thanks a lot for the hint of the PK, I just missed it.
Problem can be also in different types of object's PK ("User" in your case) and type you ask hibernate to get session.get(type, id);.
In my case error was identifier of an instance of <skipped> was altered from 16 to 32.
Object's PK type was Integer, hibernate was asked for Long type.
In my case it was because the property was long on object but int in the mapping xml, this exception should be clearer
If you are using Spring MVC or Spring Boot try to avoid:
#ModelAttribute("user") in one controoler, and in other controller
model.addAttribute("user", userRepository.findOne(someId);
This situation can produce such error.
This is an old question, but I'm going to add the fix for my particular issue (Spring Boot, JPA using Hibernate, SQL Server 2014) since it doesn't exactly match the other answers included here:
I had a foreign key, e.g. my_id = '12345', but the value in the referenced column was my_id = '12345 '. It had an extra space at the end which hibernate didn't like. I removed the space, fixed the part of my code that was allowing this extra space, and everything works fine.
Faced the same Issue.
I had an assosciation between 2 beans. In bean A I had defined the variable type as Integer and in bean B I had defined the same variable as Long.
I changed both of them to Integer. This solved my issue.
I solve this by instancing a new instance of depending Object. For an example
instanceA.setInstanceB(new InstanceB());
instanceA.setInstanceB(YOUR NEW VALUE);
In my case I had a primary key in the database that had an accent, but in other table its foreign key didn't have. For some reason, MySQL allowed this.
It looks like you have changed identifier of an instance
of org.cometd.hibernate.User object menaged by JPA entity context.
In this case create the new User entity object with appropriate id. And set it instead of the original User object.
Did you using multiple Transaction managers from the same service class.
Like, if your project has two or more transaction configurations.
If true,
then at first separate them.
I got the issue when i tried fetching an existing DB entity, modified few fields and executed
session.save(entity)
instead of
session.merge(entity)
Since it is existing in the DB, when we should merge() instead of save()
you may be modified primary key of fetched entity and then trying to save with a same transaction to create new record from existing.

MongoDB Morphia save() generates two objects with the same ID

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.

Is there a simple way to update a Spring-Data based object with Spring MVC & JSON

I'm using Spring-Data JPA and Spring-MVC with a RESTful interface. I'm trying to implement a basic CRUD controller. I'm having some difficulty figuring out the best way to implement the "Update".
My basic controller method is straight forward:
#RequestMapping( method=RequestMethod.POST, value="updateUser", produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public User update( #RequestBody final User user){
userRepository.save(user);
return user;
}
However, this only seems to provide a "create" - not an "update". Everytime the method is called, a new user is created in the DB, even if I specify the User PK in the JSON object.
From a quick look at the SimpleJpaRepository class, it creates a new object whenever the "version" field is missing. However, if I force the "version" field to have a value, I get an exception (not surprisingly):
Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.ia.domain.User#5]
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:303)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:151)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:76)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:914)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:898)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:902)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:889)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
I realize that one option is to first query the DB for the existing User object (based on the submitted PK), then copy all the fields over and then save that object, but that does not seem like the right way. I presume there must be a way for me to "merge" my User object and just update it, but am not entirely sure how.
Is there a simple way of doing this?
Tracking the error to the hibernate code I found there is problem related to the version
else if ( isVersionChanged( entity, source, persister, target ) ) {
299 if ( source.getFactory().getStatistics().isStatisticsEnabled() ) {
300 source.getFactory().getStatisticsImplementor()
301 .optimisticFailure( entityName );
302 }
303 throw new StaleObjectStateException( entityName, id );
304 }
/VersionChanges is
342 boolean changed = ! persister.getVersionType().isSame(
343 persister.getVersion( target ),
344 persister.getVersion( entity )
345 );
The error is coming from different version of the entity,
Consider using to save the standard method of JPARepository.
#Transactional
public <S extends T> S save(S entity)
Also update your question with the code for userRepository, as I think saveUser is your own implementation
After more research and debugging and trial & error, it turns out the solution is very simple. The value of the version field of the entity being persisted must match what is currently in the DB. If the values don't match Hibernate presumes that something else updated the DB and consequently throws the org.hibernate.StaleObjectStateException.
Ensuring that the JSON being submitted has a matching PK and version value as the row in the DB, then the repo.save(user) will update the existing row. If the version field is null, the entity is persisted as a new row.
I don't think you should "force the version field to have value". They're there for the purpose of optimistic locking. Other transaction could be modifying your record and the version you're working at become stale.
As with your entity object not updating, Spring might failed to detect it's a detached entity because the primary key field mapping was wrong / not setup. It could also be due to the entity never been persisted in the first place (hence it's in new state)
I believe you want to use userRepository.merge(user).

MongoDB Morphia - Unique

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

How to return ids on Inserts with mybatis in mysql with annotations

See this related question for Postgres. For some reason, the solution doesn't work for me - the return value of the insert statement is always "1".
See this other question for an XML based solution. I would like to do the same without XML - insert a record and find the new auto-generated id of the record I just insreted.
I didn't find a matching annotation to <selectkey> (see this open issue)
How do I proceed?
Examining mybatis code reveals that INSERT is implemented via UPDATE, and always returns the number of inserted rows! So ... unless I'm completely missing something here, there's no way to do this using the current (3.0.3) implementation.
Actually, it's possible to do it, with the #Options annotation (provided you're using auto_increment or something similar in your database) :
#Insert("insert into table3 (id, name) values(null, #{name})")
#Options(useGeneratedKeys=true, keyProperty="idName")
int insertTable3(SomeBean myBean);
Note that the keyProperty="idName" part is not necessary if the key property in SomeBean is named "id". There's also a keyColumn attribute available, for the rare cases when MyBatis can't find the primary key column by himself. Please also note that by using #Options, you're submitting your method to some default parameters ; it's important to consult the doc (linked below -- page 60 in the current version) !
(Old answer) The (quite recent) #SelectKey annotation can be used for more complex key retrieval (sequences, identity() function...). Here's what the MyBatis 3 User Guide (pdf) offers as examples :
This example shows using the #SelectKey annotation to retrieve a value from a sequence before an
insert:
#Insert("insert into table3 (id, name) values(#{nameId}, #{name})")
#SelectKey(statement="call next value for TestSequence", keyProperty="nameId", before=true, resultType=int.class)
int insertTable3(Name name);
This example shows using the #SelectKey annotation to retrieve an identity value after an insert:
#Insert("insert into table2 (name) values(#{name})")
#SelectKey(statement="call identity()", keyProperty="nameId", before=false, resultType=int.class)
int insertTable2(Name name);
The <insert>, <update>and <delete> statements return the number of affected rows, as is common with database APIs.
If a new ID is generated for the inserted row, it is reflected in the object you passed as a parameter. So for example, if you call mapper.insert(someObject) inside your annotated insert method, after inserting, you can call someObject.getId (or similar) to retrieve it.
Using the options of <insert>, you can tweak how (by providing an SQL statement) and when (before or after the actual insertion) the id is generated or retrieved, and where in the object it is put.
It may be instructive to use the MyBatis generator to generate classes from a database schema and have a look at how inserts and updates are handled. Specifically, the generator produces "example" classes that are used as temporary containers to pass around data.
you can get your generated ids from save methods,
lets say a bean with ID and name properties,
bean.setName("xxx");
mapper.save(bean);
// here is your id
logger.debug(bean.getID);
I didn't like most of the answers I found online for returning generated keys because
All of the solutions I found called a "setter" on the inbound object
None of the solutions returned the generated column from the method
I came up with the following solution which addresses points 1 & 2 above which
Passes two parameters to mybatis "in" & "out" (mybatis does not mutate "in", it calls a setter on "out")
Requires an additional default method on the interface to return the value
public interface MyMapper {
/**
* this method is used by the mybatis mapper
* I don't call this method directly in my application code
*/
#Insert("INSERT INTO MY_TABLE (FOO) VALUES ({#in.foo})")
#Options(useGeneratedKeys=true, keyColumn="ID", keyProperty = "out.value")
void insert(#Param("in") MyTable in, #Param("out") LongReference out);
/**
* this "default method" is called in my application code and returns the generated id.
*/
default long insert(MyTable tableBean) {
LongReference idReference = new LongReference();
insert(tableBean, idReference);
return idReference.getValue();
}
}
This requires an additional class which can be re-used on similar methods in future
public class LongReference {
private Long value;
// getter & setter
}

Categories

Resources