Domanda

I'm using spring data repositories and I have a problem I cant find the answer. My repository query is:

@Modifying
@Query("UPDATE User u SET u.firstName = 'blabla' WHERE u.loginName = 'admin'")
public int test();

And the entity User has an javax.persistence.Version annotated field:

 @Version
 private Long version;

When I execute the query the version field doesn't updates, but if instead of the query i do:

User user = this.userRepository.findUserById(1L);
user.setFirstName("blabla");
this.userRepository.save(user);

the version field is updated. Why?

È stato utile?

Soluzione

Section 4.10 of the JPA 2.0 specification explicitly states:

Bulk update maps directly to a database update operation, bypassing optimistic locking checks. Portable applications must manually update the value of the version column, if desired, and/or manually validate the value of the version column.

Generally speaking bulk updates and deletes pretty much bypass a lot of functionality that is applied by the persistence provider that you might be used to when simply saving an entity. Besides optimistic locking this includes the persistence provider managed cascading of persistence operations.

Altri suggerimenti

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:

Bulk update lost update

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:

Bulk update with optimistic locking

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top