In order to use optimistic locking, you need a version
column first.
Now, if the bulk update statement does not increment the version
column, a Lost Update anomaly can occur:
Now, to avoid the Lost Update, you just need to increment the version
column.
You can do it with any type of query.
JPQL
int updateCount = entityManager
.createQuery(
"update Post " +
"set " +
" status = :newStatus," +
" version = version + 1 " +
"where " +
" status = :oldStatus and " +
" lower(title) like :pattern")
.setParameter("oldStatus", PostStatus.PENDING)
.setParameter("newStatus", PostStatus.SPAM)
.setParameter("pattern", "%spam%")
.executeUpdate();
Criteria API
CriteriaBuilder builder = entityManager
.getCriteriaBuilder();
CriteriaUpdate<Post> update = builder
.createCriteriaUpdate(Post.class);
Root<Post> root = update.from(Post.class);
Expression<Boolean> wherePredicate = builder
.and(
builder.equal(
root.get("status"),
PostStatus.PENDING
),
builder.like(
builder.lower(root.get("title")),
"%spam%"
)
);
Path<Short> versionPath = root.get("version");
Expression<Short> incrementVersion = builder
.sum((short) 1, versionPath);
update
.set(root.get("status"), PostStatus.SPAM)
.set(versionPath, incrementVersion)
.where(wherePredicate);
int updateCount = entityManager
.createQuery(update)
.executeUpdate();
Native SQL
UPDATE post
SET
status = 2,
version = version + 1
WHERE
status = 0 AND
lower(title) LIKE '%spam%'
Once you increment the version
column, the Lost Update will be prevented in the concurrent transactions that loaded the previous versions of the entities updated by the bulk statement: