Spring MongoRepository is updating or upserting instead of inserting - java

I'm using a :
org.springframework.data.mongodb.repository.MongoRepository
I start with an empty DB and create an object with _id = 1234 for example, and set some other String field to hello for example, and then do:
repository.save(object);
All is well, it saves the document in MondoDB.
I create a NEW object, set the same _id = 1234 but set the other String field to world and then to another save :
repository.save(newObject);
Results : the save works but updates the original object.
Expected results: This should fail with a DuplicateKeyException as _id is unique and I am using 2 separate objects when doing each save.
Defect in spring or am I doing something wrong ???

Save, by definition, is supposed to update an object in the upsert style, update if present and insert if not.
Read the save operation documentation on the MongoDb website
The insert operation in mongodb has the behavior you expect, but from the MongoRepository documentation it appears that insert is delegated to save so it won't make any difference. But you can give that a try and see if it works for you. Otherwise you can just do a get before to check if the object exists, since it is an index lookup it will be fast.
Edit: Check your repository version, insert was introduced in version 1.7.

the application shall update only when you have #Id annotation for one of the field, after long difficulty had found this
#Document(collection="bus")
public class Bus {
// #Indexed(unique=true, direction=IndexDirection.DESCENDING, dropDups=true)
#Id
private String busTitle;
private int totalNoOfSeats;
private int noOfSeatsAvailable;
private String busType;
}
but somehow I could not use
#Indexed(unique=true, direction=IndexDirection.DESCENDING, dropDups=true)

Related

How can I get the oldObject at any point of time?

Let say I have the following POJO
public class Plan{
private Long id;
private String name;
//other fields,getters, builder..
}
Now let say there is some Plan which already exist in db.
Now I am trying to modify/update various fields of this particular Plan by using the restAPI(api/v1/Plan/123).
Now we use Jackson here,so I will get the updated Plan object.I will be validating this updated Plan object and if all good will persist in the db.
Till here there is no issue.
But we using an internal service to audit this,i.e what all details got modified in this particular Plan and done by whom at what time etc.
For ex:Plan name changed from (Plan1 to Plan2)
Internal service will accept argument as (oldPlan,newPlan).
Now how can I get the oldPlan from the newPlan object?[Since the file which I have now got the method as public Plan update(Plan newPlan),inside this method only we will be calling an internal service method which requires oldPlan]
Constraints:
1.I should not fire the query getting the id from the newPlan,since that will be duplicate query coz it was already queried when creating newPlan object using jackson.
2.I should not change the update method signature and in update method the plan object which we receive via argument is the object which got updated details in it.
What I have in my mind is:
a)Having the Plan object itself in the POJO ,so that when I get newPlan.getPlan() will return the oldInstance?
The above approach can be done but I am not sure abt this.Whats the best approach to achieve this?

org.springframework.orm.jpa.JpaSystemException: identifier of an instance of com.cc.domain.User was altered from 90 to null; [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.

How to use CacheConfiguration.setIndexedTypes for Ignite Cache

I am following the code for the running SQL queries in the Ignite cache, but am able to fully realize the use of the CacheConfiguration.setIndexedTypes API.
I am following the only help that I could find at the ignite site.
The documentation here says to use
CacheConfiguration.setIndexedTypes(MyKey.class, MyValue.class).
Now lets say in the Person class
#QuerySqlField(index = true)
private long id;
#QuerySqlField
private String firstName;
Which are the parameters that I should be passing in the setIndexedType method?
setIndexedTypes takes an even number of parameters. Every odd parameter corresponds to a key type, and every even - to a value type. In your case you should probably use id parameter as a key, so you should call it this way:
cacheConfig.setIndexedTypes(Long.class, Person.class);
Javadoc for setIndexedTypes method contains a pretty good explanation of this method: https://ignite.apache.org/releases/latest/javadoc/org/apache/ignite/configuration/CacheConfiguration.html#setIndexedTypes(java.lang.Class...)
UPD:
There will be registered a table in SQL for each pair of parameters that you provide to setIndexedTypes method.
Your SQL entities will map to cache records and they will have _key and _val columns in addition to the ones that you configured as QuerySqlField-s. So, you should specify types of keys and values that will be used in cache for each table.
You can refer to this page for more information: https://apacheignite.readme.io/docs/dml#basic-configuration
In your case it will be
cacheConfig.setIndexedTypes(KeyType.class, Person.class)
where KeyType is the type you use for keys while calling cache.put(key, person) or insert into Person(_key, ...) ...
Please refer to this documentation section

How to use 'id' when using DocumentDB via MongDB API in Spring DATA?

I have used both the ways of mapping _id as described in the Spring Docs here.
using #Id annotation
having a field with name id without any annotation
in my previous project where we used MongoDB as database and Spring Data for DAO operations. It worked without any problem for both String a well as for BigInteger.
Now we are using DocumentDB with MongoDB API(as Spring Data does not support DocumentDB).
I am able to use all the Spring Data methods, but I am not able to use custom id.
Below is my entity:
public class S{
private String id;
/* other fields here */
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
/* getters and setters for other fields */
}
This is the DAO:
public interface SDao extends MongoRepository<S, String> {
}
Now if anywhere in my code I do:
s = new S();
s.setId("some-id-here");
The record gets successfully persisted in the DB with custom id some-id-here as String (not ObjectId), but after that it throws ClassCastException saying Long cannot be converted to Integer.
Same is the case when using BigInteger for id.
If I am not setting the custom id, i.e. I comment the setting of id as below:
s = new S();
// s.setId("some-id-here");
no exception is being thrown, but the record is being persisted with a random id provided by database itself as ObjectcId.
I want to save the record with custom id, so that I can easily update it when needed.
Currently if I have to update a record, I need to retrieve it using a key which is not mapped to _id and then update it and then delete the old record from the DB and then persist the updated one, which I feel is absolutely inefficient as I am not able to make use of _id.
My question is why am I getting ClassCastException, that too mentioning Conversion of Long to Integer
Is DocumentDB internally doing some conversion which is throwing this exception. If yes, how to tackle it? Is this a bug?
One alternative could be to let DocumentDB/ MongoDB create those IDs for you by default. In your class you can have another field which can serve as natural ID and create a unique index on that field for fetch optimization.
Refer https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/ for indexes.
The id generation rules are explained here link

Return Hibernate envers Audit revision with modified flags

I'm using Hibernate Envers in my app to track changes in all fields of my entities.
I'm using #Audited(withModifiedFlag=true) annotation to do it.
The records are been correcty recorded at database and the _mod fields correctly indicate the changed fields.
I want to get a particular revision from some entity and the information of what fields have been changed. I'm using the follow method to do it:
List<Object[]> results = reader.createQuery()
.forRevisionsOfEntity(this.getDao().getClazz(), false, true)
.add(AuditEntity.id().eq(id))
.getResultList();
This method returns an list of an object array with my entity as first element.
The problem is that the returned entity doesn't have any information about the changed fields. So, my question is: how to get the information about the changed fields?
I know that this question is a bit old now but I was trying to do this and didn't really find any answers.
There doesn't seem to be a nice way to achieve this, but here is how I went about it.
Firstly you need to use projections, which no longer gives you a nice entity model already mapped for you. You'll still get back an array of Objects but each object in the array corresponds to each projection that you added (in order).
final List<Object[]> resultList = reader.createQuery()
.forRevisionsOfEntity(this.getDao().getClazz(), false, true)
// if you want revision properties like revision number/type etc
.addProjection(AuditEntity.revisionNumber())
// for your normal entity properties
.addProjection(AuditEntity.id())
.addProjection(AuditEntity.property("title")) // for each of your entity's properties
// for the modification properties
.addProjection(new AuditProperty<Object>(new ModifiedFlagPropertyName(new EntityPropertyName("title"))))
.add(AuditEntity.id().eq(id))
.getResultList();
You then need to map each result manually. This part is up to you, but I'm use a separate class as a revision model as it contains extra data to the normal entity data. If you wanted you could probably achieve this with #Transient properties on your entity class though.
final List<MyEntityRevision> results = resultList.stream().map(this::transformRevisionResult)
.collect(Collectors.toList());
private MyEntityRevision transformRevisionResult(Object[] revisionObjects) {
final MyEntityRevision rev = new MyEntityRevision();
rev.setRevisionNumber((Integer) revisionObjects[0]);
rev.setId((Long) revisionObjects[1]);
rev.setTitle((String) revisionObjects[2]);
rev.setTitleModified((Boolean) revisionObjects[3]);
return rev;
}

Categories