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:
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 exceptionsaveAndFlush
- 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.@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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With