6

I have a very simple domain model, one entity which has a version field in order to use the optimistic locking capabilities provided from JPA (api v2.2). The implementation i use is Hibernate v5.3.10.Final.

@Entity @Data public class Activity { @Id @SequenceGenerator(name = "gen", sequenceName = "gen_seq", allocationSize = 1) @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "gen") private Long id; @Column private String state; @Version private int version; } 

Then there are simple operations with that entity, like for example temporarily changing its stage:

@Transactional public Activity startProgress(Long id) { var entity = activityRepository.findById(id).orElseThrow(RuntimeException::new); if (entity.getState() == "this") { // or that, or else, etc // throw some exceptions in some cases } entity.setState("IN_PROGRESS"); return activityRepository.saveAndFlush(entity); } 

The outcome of this, what i'd like to achieve, is to have a way to update the entity, and that update should also increment the version. If there is a mismatch, i'd expect a ObjectOptimisticLockingFailureException or OptimisticLockingException to be thrown. I'd also like to have the updated value of the version field in the object, as i'm returning it to the client.

Several options which i've tried:

  • simply call save - the version field gets updated, but the new value is not returned and the client gets the old one, which makes the next request hit the locking exception
  • call saveAndFlush - in this case i get the update statement on the client executed twice, which weirdly returns version X which in the database the version is X+1. Then again the next client request hits the locking exception.
  • Create a @Modifying query, mark it as clear automatically (to auto flush the change) and use the hql create versioned syntax. Then i get the following query executed: update activities set version=version+1, state=? where id=?, but this doesn't seem to do the optimistic lock check (where version = :version_from_entity). I also don't think it will raise the appropriate exception.

So, in the end what i'd like to achieve is quite simple and i don't think i have to write it on my own - have a way to update one or more fields on a versioned entity, rely on JPA for the optimistic locking and get the latest version so the client can do further operations with the entity. I read quite a lot of similar issues but most use the entity manager directly, which is not what i'm striving for.

3
  • The version should get updated automatically. Are you using the correct @Versionannotation from javax.persistence? Commented Aug 27, 2019 at 13:48
  • Yes, the javax.persistence and not the one from spring data commons. Commented Aug 27, 2019 at 14:42
  • I try saveAndFlush() in my local and cannot reproduce your mentioned problem. It works as expected and no more double update happen......So in your case, what are these two UPDATE SQL look like ? Are they the same ? Commented Aug 31, 2019 at 0:07

1 Answer 1

3
+100

Your code looks pretty correct so it's difficult to say where is the problem...

I've created a simple working example with Spring Data JPA and Optimistic locking. I hope it will be helpful for you to solve the issue.

Create an entity:

@Transactional public Model create(Model model) { return modelRepo.save(model); } 

Update the entity:

@Transactional public Optional<Model> update(int id, Model source) { return modelRepo .findById(id) .map(model -> modelMapper.apply(model, source)); } 

Get entities:

@Transactional(readOnly = true) public List<Model> getAll() { return modelRepo.findAll(); } 

Just clone the project, run it (for example, with mvn spring-boot:run), and check the log:

15:44:35.905 INFO 2800 --- [ main] i.g.c.d.Application : [i] Creating... 15:44:35.925 INFO 2800 --- [ main] jdbc.sqltiming : batching 1 statements: 1: insert into model (name, version, id) values ('model', 0, 1); {executed in 1 msec}
15:44:35.930 INFO 2800 --- [ main] i.g.c.d.Application : [i] Updating... 15:44:35.934 INFO 2800 --- [ main] jdbc.sqltiming : select model0_.id as id1_0_0_, model0_.name as name2_0_0_, model0_.version as version3_0_0_ from model model0_ where model0_.id=1; {executed in 0 msec} 15:44:35.939 INFO 2800 --- [ main] jdbc.resultsettable : |---------|------|--------| |id |name |version | |---------|------|--------| |[unread] |model |0 | |---------|------|--------| 15:44:35.944 INFO 2800 --- [ main] jdbc.sqltiming : batching 1 statements: 1: update model set name='model_updated', version=1 where id=1 and version=0; {executed in 1 msec}
15:44:36.010 INFO 2800 --- [ main] i.g.c.d.Application : [i] Getting... 15:44:36.015 INFO 2800 --- [ main] jdbc.sqltiming : select model0_.id as id1_0_, model0_.name as name2_0_, model0_.version as version3_0_ from model model0_; {executed in 0 msec} 15:44:36.017 INFO 2800 --- [ main] jdbc.resultsettable : |---|--------------|--------| |id |name |version | |---|--------------|--------| |1 |model_updated |1 | |---|--------------|--------|

A few recomendations, if you don't mind )

Use an object instead of a simple type for version property (eg Integer, Long, etc). It's useful, for example, when you create entity identifiers yourself. In this case, Spring Data/Hibernate check that version is null and won't do an extra select query to the database.

When you update an entity you don't have to explicitly call save methods of the repo - since you are in a transaction the Hibernate update the changed entity itself.

Sign up to request clarification or add additional context in comments.

8 Comments

Okay, but in your example you update the entity and then read it back in separate transactions. Do you properly get the version to be 1 at the end of the public Optional<Model> update(int id, Model source) method?
@MilanMilanov Check the log again - I've updated the answer a little...
Okay, i'm officially out of ideas. I changed the method to not call saveAndFlush and left Hibernate to take care of the update. Tests are still green, but amazingly it doesn't work in the real setup, an Oracle DB. Here's what the log says:
SQL - select ... from activities act0_ where act0_.id=? BasicBinder - binding [1] as [BIGINT] - [117] SQL - update activities set state=?, version=? where id=? and version=? BasicBinder - binding [1] as [BIGINT] - [117] BasicBinder - binding [2] as [VARCHAR] - ["IN_PROGRESS"] BasicBinder - binding [3] as [INTEGER] - [113] BasicBinder - binding [4] as [BIGINT] - [117] BasicBinder - binding [5] as [INTEGER] - [112] Clearly the version is updated from 112 to 113, but still 112 is returned at the end of the method, then to the client and again the exception happens.
The only Oracle specific thing used is the creation of the datasource, done using PoolDataSourceFactory#getPoolDataSource. Other than that the setup is identical with the one in the tests. Any ideas would be welcome as to why the behaviour might differ.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.