在Spring Data Mongodb事务期间如何使用文档锁来防止对记录进行外部修改

时间:2019-05-24 18:30:57

标签: mongodb spring-data-mongodb spring-transactions

我对Spring Data Mongo和Mongo Transactions有疑问。

我已经成功实现了事务,并已通过Spring @Transactional注释验证了提交和回滚工作是否如预期。

但是,我很难让事务按我在Spring Data环境中期望的方式工作。

Spring数据执行Mongo-> Java对象映射。因此,更新内容的典型模式是从数据库中获取内容,然后进行修改,然后将其保存回数据库。在实施事务之前,我们一直在使用Spring的Optimistic Locking来解决获取和更新之间记录发生更新的可能性。

我希望一旦我们能够使用事务,就不会对我的所有更新都包括开放式锁定基础结构。因此,我希望在事务的上下文中,访存会创建一个锁,这样我就可以进行更新并保存,并且我将与外界隔离,以便没人能像以前那样大胆地进行更改。

但是,根据我所看到的,访存不会创建任何类型的锁,因此没有什么可以阻止任何其他连接更新记录,这意味着,尽管有以下操作,但我似乎仍必须维护所有乐观锁代码本地mongodb事务支持。

我知道我可以使用mongodb findAndUpdate方法进行更新,并且不允许进行临时修改,但这与Spring Data的标准模式相反,Spring Data将数据加载到Java Object中。因此,我不仅要操纵Java对象,还必须在应用程序中散布mongo特定的代码,或者为我要进行的每种特定类型的更新创建Repository方法。

在维护仅使用Java对象的Spring Data范例的同时,有人对如何干净地处理这种情况有任何建议吗?

谢谢!

1 个答案:

答案 0 :(得分:0)

我找不到任何在Spring / MongoDB事务中进行“读取”锁定的方法。

但是,为了能够继续使用以下模式:

  1. 获取记录
  2. 进行更改
  3. 保存记录

我最终创建了一个执行findAndModify的方法,以便在获取过程中“锁定”记录,然后我可以进行更改并进行保存,所有这些都发生在同一事务中。如果另一个进程/线程在交易过程中尝试更新“锁定”记录,则会阻止该记录,直到我的交易完成为止。

对于lockForUpdate方法,我利用了Spring已经用于乐观锁定的version字段,仅仅是因为它很方便并且可以为简单的锁定操作轻松修改。

我还将我的实现添加到基本存储库实现中,以在所有存储库上启用“ lockForUpdate”。

这是我的解决方案的要点,其中删除了一些特定于域的复杂性:

public class BaseRepositoryImpl<T, ID extends Serializable> extends SimpleMongoRepository<T, ID>
    implements BaseRepository<T, ID> {

    private final MongoEntityInformation<T, ID> entityInformation;
    private final MongoOperations mongoOperations;

    public BaseRepositoryImpl(MongoEntityInformation<T, ID> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);

        this.entityInformation = metadata;
        this.mongoOperations = mongoOperations;
    }

    public T lockForUpdate(ID id) {
        // Verify the class has a version before trying to increment the version in order to lock a record
        try {
            getEntityClass().getMethod("getVersion");
        } catch (NoSuchMethodException e) {
            throw new InvalidConfigurationException("Unable to lock record without a version field", e);
        }

        return mongoOperations.findAndModify(query(where("_id").is(id)),
                new Update().inc("version", 1L), new FindAndModifyOptions().returnNew(true), getEntityClass());
    }

    private Class<T> getEntityClass() {
        return entityInformation.getJavaType();
    }
}

然后,在事务上下文中,您可以按照以下方式进行调用:

Record record = recordRepository.lockForUpdate(recordId);
...make changes to record...
recordRepository.save();