Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement transaction in Spring Data Redis in a clean way?

I am following RetwisJ tutorial available here. In this I don't think Redis transactions are implemented. For example, in the following function, if some exception occurs in between, the data will be left in an inconsistent state. I want to know how a function like the following can be implemented in Spring Data Redis as a single transaction:

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);
    }

Here userIdCounter, valueOps and users are initialized in the constructor. I have come across this in the documentation(section 4.8), but I can't figure out how to fit that into this function where some variables are initialized outside the function(please don't tell I have to initialize these variables in each and every function where I need transactions!).

PS: Also is there any @Transaction annotation or transaction manager available for Spring Data Redis?

UPDATE: I have tried using MULTI, EXEC. The code which I have written is for another project, but when its applied to this problem it'll be as follows:

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);
    }

Please tell whether this way of using MULTI, EXEC is recommended or not.

like image 774
sinujohn Avatar asked Feb 09 '14 20:02

sinujohn


People also ask

How do you handle transactions in Spring boot?

Transactions and Proxies. At a high level, Spring creates proxies for all the classes annotated with @Transactional, either on the class or on any of the methods. The proxy allows the framework to inject transactional logic before and after the running method, mainly for starting and committing the transaction.

How would you enable transactions in Spring and what are their benefits?

The Spring Framework provides a consistent abstraction for transaction management that delivers the following benefits: Provides a consistent programming model across different transaction APIs such as JTA, JDBC, Hibernate, JPA, and JDO. Supports declarative transaction management.

How does Redis integrate with Spring boot?

Spring Boot will automatically configure a Redis-cache Manager but with default properties. We can modify this configuration and change it as per our requirement. Modifying the configurations gives us more control over the basic cache configuration.


1 Answers

Up to SD Redis 1.2 you will have to take care of tansaction handling yourself using TransactionSynchronisationManager

The snipplet above could then look something like this:

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);
}

Please note that once in multi, read operations will also be part of the transaction which means you'll likely not be able to read data from redis server. The setup might differ form the above one as you could want to additionally call WATCH. Further on you'll also have to take care of multiple callbacks do not sending MULTI and/or EXEC more than once.

The upcoming 1.3 RELEASE of Spring Data Redis will ship with support for spring managed transactions in a way of taking care of MULTi|EXEC|DISCARD plus allowing read operations (on already existing keys) while transaction synchronization is active. You could already give the BUILD-SNAPSHOT a spin and turn this on by setting template.setEnableTransactionSupport(true).

like image 174
Christoph Strobl Avatar answered Sep 22 '22 17:09

Christoph Strobl