StaleObjectStateException: Row was updated or deleted by another transaction? - spring

I do the following:
def currentUser = springSecurityService.currentUser
currentUser.name = "test"
currentUser.save(flush: true)
// some other code
currentUser.gender = "male"
currentUser.save(flush: true) // Exception occurs
This is the exception I get:
ERROR events.PatchedDefaultFlushEventListener - Could not synchronize database state with session
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)
How can I prevent this error? What is the best solution for that?
I found different approaches:
here that you can use discard()
here that you can use merge()
Which one should I use?

You should use merge - it will update the object to match the current state in the database. If you use discard it will reset the object back to what the database has, discarding any changes. Everything else in the hibernate session you need to manage yourself.
More importantly code should be written in a service so that there is a database transaction, and you should use
save(flush:true)
once only at the end.
def currentUser = springSecurityService.currentUser
currentUser.name = "test"
// currentUser.save(flush: true) // removing this line because if a rollback occurs, then changes before this would be persisted.
// some other code
currentUser.gender = "male"
currentUser.merge() // This will merge persistent object with current state
currentUser.save(flush: true)

Related

Wait for lock release in Grails transaction

I'm trying to write a Groovy/Grails 3 function that looks up a database object, locks it, and then saves it (releasing the lock automatically).
If the function is called multiple times, it should wait until the lock is released, and then run the update. How can I accomplish this?
def updateUser(String name) {
User u = User.get(1)
// if locked, wait until released somehow?
u.lock()
u.name = name
u.save()
}
updateUser('bob')
updateUser('fred') // sees lock from previous call, waits until released, then updates
u.save(flush:true)
Flushing the Hibernate session should complete the transaction and release the lock on the database level.
Generally speaking, pessimistick locking only works in a transactional context.
So make sure to put the updateUser method in a service that is annotated with #Transactional.
Calling get() and then lock() results in 2 sql statements being executed (one for getting the object, another for locking it).
Using User.lock(), a single select ... for udpate query is issued instead.
#Transactional
class UserService {
def updateUser(String name) {
User u = User.lock(1) // blocks until lock is free
u.name = name
u.save()
}
}

Spring data Neo4j Affected row count

Considering a Spring Boot, neo4j environment with Spring-Data-neo4j-4 I want to make a delete and get an error message when it fails to delete.
My problem is since the Repository.delete() returns void I have no ideia if the delete modified anything or not.
First question: is there any way to get the last query affected lines? for example in plsql I could do SQL%ROWCOUNT
So anyway, I tried the following code:
public void deletesomething(Long somethingId) {
somethingRepository.delete(getExistingsomething(somethingId).getId());
}
private something getExistingsomething(Long somethingId, int depth) {
return Optional.ofNullable(somethingRepository.findOne(somethingId, depth))
.orElseThrow(() -> new somethingNotFoundException(somethingId));
}
In the code above I query the database to check if the value exist before I delete it.
Second question: do you recommend any different approach?
So now, just to add some complexity, I have a cluster database and db1 can only Create, Update and Delete, and db2 and db3 can only Read (this is ensured by the cluster sockets). db2 and db3 will receive the data from db1 from the replication process.
For what I seen so far replication can take up to 90s and that means that up to 90s the database will have a different state.
Looking again to the code above:
public void deletesomething(Long somethingId) {
somethingRepository.delete(getExistingsomething(somethingId).getId());
}
in debug that means:
getExistingsomething(somethingId).getId() // will hit db2
somethingRepository.delete(...) // will hit db1
and so if replication has not inserted the value in db2 this code wil throw the exception.
the second question is: without changing those sockets is there any way for me to delete and give the correct response?
This is not currently supported in Spring Data Neo4j, if you wish please open a feature request.
In the meantime, perhaps the easiest work around is to fall down to the OGM level of abstraction.
Create a class that is injected with org.neo4j.ogm.session.Session
Use the following method on Session
Example: (example is in Kotlin, which was on hand)
fun deleteProfilesByColor(color : String)
{
var query = """
MATCH (n:Profile {color: {color}})
DETACH DELETE n;
"""
val params = mutableMapOf(
"color" to color
)
val result = session.query(query, params)
val statistics = result.queryStatistics() //Use these!
}

org.hibernate.HibernateException Message reassociated object has dirty collection

I have an application which performs the following steps:
Places object in session:
def product = Product.get(1)
session["product"] = product
Performs and Ajax call to update a 1-m relationship, and then renders a partial template which displays the new benefits. These should not yet be saved as the user may change their mind, so discard is called:
def product = session["product"]
if ( !product.isAttached() ) {
product.attach()
}
product.addToBenefits( new Benefit( title: "xx" ) )
product.discard()
session["product"] = product
Attempts to save the object in a save action.
def product = session["product"]
if ( !product.isAttached() ) {
product.attach()
}
product.save()
At this point we get the following exception:
org.springframework.orm.hibernate3.HibernateSystemException: reassociated object has dirty collection; nested exception is org.hibernate.HibernateException: reassociated object has dirty collection
Is there anyway to stop this happening, so that I can re-Attach the object, and save it, thus persisting the changes to the products benefits collection?
Don't store the object in the session, store the id, and reload it instead. You're incurring that cost anyway with attach, so you're not saving anything, and causing this problem, plus wasting server memory, which will affect scalability.
Be sure the instance doesn't have unsaved state before calling attach. If you have changed a dettached entity instance and left it in unsaved state, calling attach will throw HibernateSystemException: reassociated object has dirty collection because to reassociate a transient instance with a session Grails uses Session.LockRequest.lock in the attach call chain:
session.buildLockRequest(new LockOptions(lockMode)).lock(entity);
To reassociate a dettached instace with unsaved state call Session.update, Session.merge or Session.saveOrUpdate:
if ( !product.attached ) {
product.withSession { session -> session.saveOrUpdate(product) }
}
Still, storing only the id and reloading is the best option in OP's case as already mentioned. Most of the times that will be the best choice. For instance, you can use load or get:
def product = Product.load(session["productId"])

Plugin Pre Operation Create - Update field error

My plugin fire on Pre Create operation on Entity X. When trying to update a field on the Entity X using the following code I am getting error:
trEntity = (Entity)context.InputParameters["Target"];
trGuid = (Guid)trEntity.Id;
tr = (Entity)service.Retrieve("EntityX", trGuid,
new ColumnSet(new string[] { "field_a", "field_b" }));
tr["field_a"] = null;
service.Update(tr);
The error I am getting is:
Entity X with Id = 11505683-2292-b537-e311-143710e56fb7 Does Not Exist
Since you are in Pre-Create, the entity doesn't exist yet in the database.
You don't need to explicitly call Update in a Pre event. You can just update the Target entity (trEntity in your case) and the changes you make will be saved with the Create operation. The Target entity is the actual entity that is about to be created, so feel free to update fields directly on the Target in the Pre event.
trEntity = (Entity)context.InputParameters["Target"];
trEntity["field_a"] = null;
How are you creating your service?
This also happens when you try to update a record outside of the current transaction i.e. using a manually created OrganizationServiceProxy instead of using the one provided by IOrganizationServiceFactory.CreateOrganizationService.

Using "Any" or "Contains" when context not saved yet

Why isn't the exception triggered? Linq's "Any()" is not considering the new entries?
MyContext db = new MyContext();
foreach (string email in {"asdf#gmail.com", "asdf#gmail.com"})
{
Person person = new Person();
person.Email = email;
if (db.Persons.Any(p => p.Email.Equals(email))
{
throw new Exception("Email already used!");
}
db.Persons.Add(person);
}
db.SaveChanges()
Shouldn't the exception be triggered on the second iteration?
The previous code is adapted for the question, but the real scenario is the following:
I receive an excel of persons and I iterate over it adding every row as a person to db.Persons, checking their emails aren't already used in the db. The problem is when there are repeated emails in the worksheet itself (two rows with the same email)
Yes - queries (by design) are only computed against the data source. If you want to query in-memory items you can also query the Local store:
if (db.Persons.Any(p => p.Email.Equals(email) ||
db.Persons.Local.Any(p => p.Email.Equals(email) )
However - since YOU are in control of what's added to the store wouldn't it make sense to check for duplicates in your code instead of in EF? Or is this just a contrived example?
Also, throwing an exception for an already existing item seems like a poor design as well - exceptions can be expensive, and if the client does not know to catch them (and in this case compare the message of the exception) they can cause the entire program to terminate unexpectedly.
A call to db.Persons will always trigger a database query, but those new Persons are not yet persisted to the database.
I imagine if you look at the data in debug, you'll see that the new person isn't there on the second iteration. If you were to set MyContext db = new MyContext() again, it would be, but you wouldn't do that in a real situation.
What is the actual use case you need to solve? This example doesn't seem like it would happen in a real situation.
If you're comparing against the db, your code should work. If you need to prevent dups being entered, it should happen elsewhere - on the client or checking the C# collection before you start writing it to the db.

Resources