handling lost update problem happening with one thread updating a resource at a time
so i got this quite an interesting problem where i got a piece of code that edits a entity
there is one class and a bunch of other classes that inherit it
the superclass has a version field for optimistic locking
all subclasses are stored in the same jpa repository
the lost update/stale data problem comes when sending an update command
example scenario
employee gets updated salary to 3000 but the request will sent later
employee gets updated salary to 5000 but the request is sent immediately
so the employee gets 3000 salary even though he was supposed to have 5000
i was thinking of comparing the version of the entity and the version thats sent in the command but that generates race condition
example code
123 Replies
⌛
This post has been reserved for your question.
Hey @ayylmao123xdd! Please useTIP: Narrow down your issue to simple and precise questions to maximize the chance that others will reply in here./close
or theClose Post
button above when your problem is solved. Please remember to follow the help guidelines. This post will be automatically marked as dormant after 300 minutes of inactivity.
Did you try logging the SQL statements?
yea the version gets increased every time properly
i need some way to check if the version in the command and the entity equal and if not throw an exception without a race condition
and also cant write a custom query for updating
so that narrows down most solutions
and also cant add a parameter for fetching the subclass in database because then it wont throw the optimistic lock exception when 2 threads try to edit the subclass at once
What is your service doing?
edits a subclass
so for example
if there is salary you can edit it
to 5000 or whatever
Can you show it?
and the update method just has a bunch of setters
1 sec
its just a mapstruct edit method
I was asking about the service method
that sets new values to the entity
service.update()
oh
it just calls the edit method in the mapper
let me show
strategies is just a map for all the mappers
depending on the subclass type
iedited the classname of this
was trying with student class but didnt work
Can you show the relevant
UpdateStrategy
?sure
also that
1 second
yes i tried sql statements but it wont work in my case
since all the subclasses are stored in one repository
and i havent heard of a way to dynamically update the edit method
and i cant change the repository file
so i would need a way to inject an update method into the repository
its just this
it calls the mapper i sent
also has a bunch of irrelevant methods for this so i didnt include them
So nothing in there is doing any DB update?
Then why should it change anything?
this update does the update
and the mapper
because if you set the values
of the entity
then it automatically gets saved
because there is transactional
so you dont need to call
but yes the entity gets updated and saved to database i checked with a bunch of tests
no...
yes
that isn't how
@Transactional
worksi got a bunch of tests and it works well
and i dont use in the code
then it doesn't save it to the DB
if your tests use the same object, then the tests are not checking the DB
ok so in the test it
creates an entity
saves it to db and saves the id of the saved entity
sends request update
update method does its magic
db grabs the entity again based on id
compares if stuff has changed
so yes it does save and update properly
im just stuck with this problem with versioning
i have to somehow compare version without using if
if you don't interact with any repository,
@Transactional
does nothingyes but the entity still is saved to database
after the transaction ends
with the changes from the command
Then where are you saving it?
in the repository
it saves itself automatically
Then show the code where you are saving it
wait let me grab the name of why it happens
@Transactional
doesn't do it automatically
except you have extra code doing stuff like that, e.g. using AOPso the entity is tracked in the persistence context and at the end of the transaction hibernate just saves the entity back to db again
i use jpa by the way
maybe that explains why it works in my case
not automatically unless you did something for that
no i never call a save method
and that's why it doesn't save anything
But I think I might know why the test seems like it is saved
So in the test, I think the following happens:
- You create the entity
- You save it to the DB
- Update changes the fields on the object but it's never saved in the DB
- You retrieve the entity again but it gives you the same object
- Since it's the same object, it has the changes even though these changes are not in the DB
You can enable query logging to see that
or check the DB after making the request
yea i got sql logging enabled
but my question is how do i add version checking
like in here
just without race condition
i need some way to compare if the version in command
using
spring.jpa.show-sql=true
and/or spring.jpa.hibernate.show-sql=true
?is the same one as the one in the database
yes both
if yes then show the logs of just calling that edit method
sure but let me rephrase maybe
lost update happens because another request overried changes of the other request
because there is no comparing versions
between the command and the entity from the database
now let me show logs
yeah because it isn't even in the DB
the logs of only the
edit()
methodo
logs aside
check this out
i added statistics to measure the amount of requests
sent to database
and in each test it shows 2
which means it grabs the entity from db
then saves it
let me paste logs
as you can see it calls the update
Which logs are that?
and when are these logs printed?
after calling the edit
method
Are these logs printed when only doing a request calling
edit
?in my integration test
yea
only then
outside of an integration test
it doesnt call the update query
when saving
in for example the save method
so yes it does save to database
i need to figure out how to do this comparing versions
I can show you an example:
this runs
no UPDATE
yea
because it saves
you are calling the save method
in my case it just updates
ill send you the
example code i use
once i figure out this versioning problem
otherwise it wouldn't insert it
yes
it wouldnt insert without the repo save method
when saving
but when editing it saves automatically
The result of
f.setName
is never stored in the DB in my exampleill send you the project that shows how it works in my case
with some changed values
so i dont leak some stuff
actually you can just write a method that grabs the entity from database and updates some field and then dont call any save method
and see if it gets saved
in an integration test
ill send the project anyway
probably tomorrow as today i want to finish this versioning problem
If I do it like that
then it prints "b" and not "new name"
Because the
doesn't save anything
if you want it to be saved, you need a save()
If you don't believe it, make an actual HTTP request outside of an integration test that only triggers this method and check the result in the DB
or give me a minimal example
1 second then
@dan1st | Daniel done
sorry for the long wait
as you can see it perfectly edits the entity
without calling a save method to save it back to db
excuse my class names i was writing it quite fast
i still havent found a solution on how to compare version without race condition
weirdest task i got tbh
cant write a custom query to check versioning
cant fetch entity based on version
and cant compare version with command using a if statement
oh you are receiving in the transactional method?
yes thats how it was
in both the tests
and normal code
I didn't notice that, sry
no problem
do you have any idea how to do this part though
Are you specifying optimistic locking in the transaction?
yes it uses optimistic locking
with @version annotation on entity
but optimistic locking wont prevent a lost update when its only 1 thread modifying data
so i need to compare version between the command and the entity fetched from db
somehow without causing race condition
Where are you specifying that?
in the fetch method
as you can see
ah ok
yea
i was thinking of using a second request to check the version
in the edit method
but thats so unoptimized
Can you show the SQL logs of both updates where the second doesn't get through?
the problem is that it does go through
here
if i dont add that if statement
the update will always go through
even if the data that i send in the request is outdated
so i need to somehow
do the if statement
without race condition
or a solution i havent thought of yet
because natively the versioning just increases version of the entity without comparing it to the editcommand
yeah but I want to see the SQL logs to see what exactly happens
you said it would happen with the methods being executed one after each other
ok let me launch the app
Can you log the contents (both version and the relevant fields) after the
service.update
as well as SQL logging?
and then show the logs of both requests as well as the result
there
i added a print that says the current version
after fetching it from database
it was the code without the version checking
Can you show that?
Can you check whether the fields are actually modified?
using a debugger
just here
yes they are modified
version is raised
but how could i check
the version from command
and the version from db
oh you had set fields
What are the values here?
yes its about this edit command version
the values of the relevant fields
id is 1
type is irrelevant i guess
and version in first update is 0
and in second update is 1
I meant the field you want to change but isn't actually changed
no no no
?
the fields get changed
just this
commented out code
i need to check
whether the get version from command
is the same as subclass command
otherwise throw an exception
all the other stuff works as intended
Doesn't JPA do that automatically if optimistic locking fails?
it does that when two threads modify an entity at once
but in this case we assume the data in editcommand is outdated
hence why it has its own version field
Actually why do you have a command and a subclass version?
to be compared with the entity field
for this
because we assume the data in edit command might come outdated
so it needs to compare the version with db entity
to make sure its not outdated
The
if
here doesn't generate a race condition assuming you are throwing an exception here. If the version changes in the meantime, JPA will throw an exception when the method exitsif its outdated just an exception
the
should work
but only if the exception is a
RuntimeException
actually no it should work in any way
it doesn't need to rollbackyea it throws a runtime exception
but a senior dev told me to not do that if statement since its a race condition
but didnt want to tell what to use instead lol
hence my question here
If the
command.getVersion() != subclass.getVersion()
check decides they are equal and it's edited in the meantime, Spring will abort it due to its optimistic locking
so no there shouldn't be a race condition
but this is not at all obvious
so it needs to be documented properly WHY this is safe
and you should maybe also add a test checking that there is really no race condition
so if something changes in the future, you hopefully get alertedok wait let me pull up the notes i got
"lots update may occur when there is just one request
if the entity version is 8 and you send a request with version 7 then an exception should be thrown
it should be handled with versioning
not if statement
if statements generate race condition"
thats what i got so i assumed using an if statement just cant be used in this case
maybe it doesnt generate race condition in that case ill have to somehow show the senior how hes wrong
or just look for another solution
thanks for your help @dan1st | Daniel
If you are finished with your post, please close it.
If you are not, please ignore this message.
Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.
then you would need to argue why there is no race condition, yes
I think it wasn't clear that you are using both an optimistic lock and the
if
and include that in the form of a comment in the method
showing how the combination of the two versions prevent the race conditionyes maybe he just didnt see that i use optimistic locking
unsure though
o
thanks for the description
If you are finished with your post, please close it.
If you are not, please ignore this message.
Note that you will not be able to send further messages here after this post have been closed but you will be able to create new posts.
as again thanks for the time spent on this
something along the lines of that, you might want to change it in some form or the other
np
i guess it all came down to just uncommenting the if statement
lol
too much thinking on the problem
Also you could consider the if as some form of input validation
it checks whether the user has the most recent value
while optimistic locking checks for other issues
Oh and I recommend also including something that the version check MUST be after obtaining the obtimistic lock and it should compare to the optimistic lock
oh i use java validations for that
it checks whether the command has proper fields
even before reaching the service
I meant it being some other type of validation
but make sure to properly handle the exception from the user btw
yes i got the
advice controller
and all that fancy stuff
for handling errors
and you might also write a test that ensures the optimistic locking actually works
i got those too
now i just gotta add another one
where race condition happens
and to actually make sure it doesnt go through
yeah that's what I mean
when i send 2 threads at once it for sure throws an error so thats a positive thing
Something like
but you'd need to make sure your testing environment actually allows multithreaded testing
alternatively you can try doing it in a singlethreaded with with
Propagation.REQUIRES_NEW
the idea of that test would be that it enforces a race conditionyea i did almost the same thing
I also thought about using a single
CyclicBarrier
instead of the two CountDownLatch
es xdactually i just created 2 threads
started them
and joined
and
i added a catch for both of them
atomic boolean
and if one of them throws an error then it means the code worked
since it stopped one of the threads from editing data
the idea here is that it enforces the exception from optimistic locking
yes in my case too
not the other one
2 threads trying to edit data at once
while having the same version
in its command
one has to fail
yeah but can't it happen that they run after each other resulting it to fail with your if?
it can happen
because
if both requests send version 1
and entity in db is 1
then they both pass through the if statement
well it might happen that they go into the if
the test doesn't verify that JPA optimistic locking is the thing that fails
yes but its quite unlikely
well do you not want to test that unlikely case where JPA does that?
actually I forgot a mock spec
oh i also made another test
where it sends a wrong version
it throws an error correctly
💤
Post marked as dormant
This post has been inactive for over 300 minutes, thus, it has been archived.
If your question was not answered yet, feel free to re-open this post or create a new one.
In case your post is not getting any attention, you can try to use /help ping
.
Warning: abusing this will result in moderative actions taken against you.