如何以干净的方式在 Spring Data Redis 中实现事务?

Posted

技术标签:

【中文标题】如何以干净的方式在 Spring Data Redis 中实现事务?【英文标题】:How to implement transaction in Spring Data Redis in a clean way? 【发布时间】:2014-03-07 00:48:12 【问题描述】:

我正在关注 here 上的 RetwisJ 教程。在这我不认为 Redis 事务被实现。例如,在下面的函数中,如果中间发生一些异常,数据将处于不一致的状态。 我想知道如何在 Spring Data Redis 中将如下功能实现为单个事务:

public String addUser(String name, String password) 
        String uid = String.valueOf(userIdCounter.incrementAndGet());

        // save user as hash
        // uid -> user
        BoundHashOperations<String, String, String> userOps = template.boundHashOps(KeyUtils.uid(uid));
        userOps.put("name", name);
        userOps.put("pass", password);
        valueOps.set(KeyUtils.user(name), uid);

        users.addFirst(name);
        return addAuth(name);
    

这里userIdCountervalueOpsusers在构造函数中被初始化。我在文档中遇到过this(第 4.8 节),但我不知道如何将它放入这个函数中,其中一些变量在函数外部初始化(请不要告诉我必须在我需要交易的每一个功能!)。

PS:还有@Transaction注解或事务管理器可用于Spring Data Redis吗?

更新:我尝试过使用MULTIEXEC。我写的代码是为另一个项目编写的,但是当它应用于这个问题时,它将如下:

public String addMyUser(String name, String password) 
        String uid = String.valueOf(userIdCounter.incrementAndGet());
        template.execute(new SessionCallback<Object>() 
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations)
                    throws DataAccessException 
                operations.multi();
                getUserOps(operations, KeyUtils.uid(uid)).put("name", name);
                getUserOps(operations, KeyUtils.uid(uid)).put("pass", password);
                getValueOps(operations).set(KeyUtils.user(name), uid);
                getUserList(operations, KeyUtils.users()).leftPush(name);
                operations.exec();
                return null;
            
        );
        return addAuth(name);
    
    private ValueOperations<String, String> getValueOps(RedisOperations operations) 
        return operations.opsForValue();
    
    private BoundHashOperations<String, String, String> getUserOps(RedisOperations operations, String key) 
        return operations.boundHashOps(key);
    
    private BoundListOperations<String, String> getUserList(RedisOperations operations, String key) 
        return operations.boundListOps(key);
    

请告知是否推荐这种使用MULTIEXEC的方式。

【问题讨论】:

【参考方案1】:

在 SD Redis 1.2 之前,您必须使用 TransactionSynchronisationManager 自行处理事务

上面的代码片段可能看起来像这样:

public String addUser(String name, String password) 

    String uid = String.valueOf(userIdCounter.incrementAndGet());

    // start the transaction
    template.multi(); 

    // register synchronisation
    if(TransactionSynchronisationManager.isActualTransactionActive()) 
        TransactionSynchronisationManager.registerSynchronisation(new TransactionSynchronizationAdapter()) 

            @Override
            public void afterCompletion(int status) 
                switch(status) 
                    case STATUS_COMMITTED : template.exec(); break;
                    case STATUS_ROLLED_BACK : template.discard(); break;
                    default : template.discard(); 
                
            
        
    

    BoundHashOperations<String, String, String> userOps = template.boundHashOps(KeyUtils.uid(uid));
    userOps.put("name", name);
    userOps.put("pass", password);
    valueOps.set(KeyUtils.user(name), uid);

    users.addFirst(name);

    return addAuth(name);

请注意,一旦在 multi 中,读取操作也将成为事务的一部分,这意味着您可能无法从 redis 服务器读取数据。 该设置可能与上述设置不同,因为您可能需要另外调用 WATCH。此外,您还必须处理多个回调,不要多次发送MULTI 和/或EXEC

即将发布的 Spring Data Redis 1.3 RELEASE 将支持 Spring 托管事务,以处理 MULTi|EXEC|DISCARD 并在事务同步处于活动状态时允许读取操作(对现有键)。您已经可以试一试 BUILD-SNAPSHOT 并通过设置 template.setEnableTransactionSupport(true) 将其打开。

【讨论】:

您好,很久没有人问这个问题了。由于没有人回答,我想出了如何去做(在问题中更新)。但我不知道这是否是推荐的方法。 TransactionSynchronisationManager 是要走的路吗?它是否需要除此之外的任何其他配置?我在编写的代码中发现的优势是,如果我必须从 Redis 读取数据,我可以使用“模板”,因此它不会成为事务的一部分 使用SessionCallback 非常好(请参阅reference),因为这会在命令执行和之后释放它的时间绑定使用的连接。 TransactionSynchronisationManager 或在下一个版本 1.3 中的 template.setEnableTransactionSupport(true) 是用于处理对 redis 的多次调用,这些调用可能不在附近,而正在进行的外部事务处于活动状态。通常不必直接与 TSM 交互。 @ChristophStrobl:我有类似的情况。我已经搜索了文档,但我没有看到我们如何保证交易的任何参考。一旦抛出任何异常,正确配置的 RestTemplate 是否应该回滚?假设我们在 Spring Service 中有方法:@Transactional public void doSomth() redisTemplate.delete(...); redisTemplate.delete(...); 它是否应该正常回滚第一次删除第二次会失败?

以上是关于如何以干净的方式在 Spring Data Redis 中实现事务?的主要内容,如果未能解决你的问题,请参考以下文章

如何以干净有效的方式在pytorch中获得小批量?

使用spring jdbc时将长(+20行sql)外部化的干净方法? [关闭]

如何以干净的方式分叉现有的 Meteorite 包?

如何以干净且可维护的方式编写非常复杂的 SQL? [关闭]

如何以干净的方式创建外键与 Room DB 相关的行和子项?

在 Spring Data JPA 中,如何在运行时添加实体?