OptimisticLockException not thrown when version ha

2020-02-14 18:37发布

Model structure:

@MappedSuperclass
public class BaseModel<K extends Comparable> implements Serializable, Comparable<Object> {

    private static final long serialVersionUID = 1L;

    @Id
    private K id;

    @Version
    private Integer version;

    // getter/setter
}

@Entity
public class MyEntity extends BaseModel<String> {
    // some fields and it's getter/setter
}

Record in my database for my_entity:

id: 1 version: 1 ...

Below is my update method:

void update(String id, Integer currentVersion, ....) {
    MyEntity myEntity = myRepository.findOne(id);
    myEntity.setVersion(currentVersion);
    // other assignments

    myRepository.save(myEntity);
}

Below is the query being fired when this method is invoked.

update my_entity set version=?, x=?, y=?, ...
where id=? and version=?

I am expecting OptimisticLockException when currentVersion passed in above method is other than 1.

Can any body help me why I am not getting OptimisticLockException? I am using spring-boot for my webmvc project.

2条回答
家丑人穷心不美
2楼-- · 2020-02-14 18:38

Section 11.1.54 of the JPA specification notes that:

In general, fields or properties that are specified with the Version annotation should not be updated by the application.

From experience, I can advise that some JPA providers (OpenJPA being one) actually throw an exception should you try to manually update the version field.

While not strictly an answer to your question, you can re-factor as below to ensure both portability between JPA providers and strict compliance with the JPA specification:

public void update(String id, Integer currentVersion) throws MyWrappedException {
    MyEntity myEntity = myRepository.findOne(id);

    if(currentVersion != myEntity.getVersion()){
        throw new MyWrappedException();
    }

    myRepository.save(myEntity);

   //still an issue here however: see below
}

Assuming your update(...) method is running in a transaction however you still have an issue with the above as section 3.4.5 of the JPA specification notes:

3.4.5 OptimisticLockException Provider implementations may defer writing to the database until the end of the transaction, when consistent with the lock mode and flush mode settings in effect. In this case, an optimistic lock check may not occur until commit time, and the OptimisticLockException may be thrown in the "before completion" phase of the commit. If the OptimisticLockException must be caught or handled by the application, the flush method should be used by the application to force the database writes to occur. This will allow the application to catch and handle optimistic lock exceptions.

Essentially then, 2 users can submit concurrent modifications for the same Entity. Both threads can pass the initial check however one will fail when the updates are flushed to the database which may be on transaction commit i.e. after your method has completed.

In order that you can catch and handle the OptimisticLock exception, your code should then look something like the below:

public void update(String id, Integer currentVersion) throws MyWrappedException {
    MyEntity myEntity = myRepository.findOne(id);

    if(currentVersion != myEntity.getVersion()){
        throw new MyWrappedException();
    }

    myRepository.save(myEntity);

    try{
       myRepository.flush()
    }
    catch(OptimisticLockingFailureException  ex){
       throw new MyWrappedException();
    }
}
查看更多
我命由我不由天
3楼-- · 2020-02-14 19:03

Use EVICT before updating when using JPA. I did not get the @Version to work either. The property was increased but no exception was thrown when updating an object that had the wrong version-property.

The only thing I have got to work is to first EVICT the object and then save it. Then the HibernateOptimisticLockingException is thrown if the Version properties does not match.

Set the hibernates ShowSQL to 'true' to verify that the actual update sql ends with "where id=? and version=?". If the object is not evicted first, the update statement only has "where id=?", and that will (for obvious reasons) not work.

查看更多
登录 后发表回答