14

All Spring Configuration is written properly. Non-Multi-Exec Redis operations work perfectly.

@Autowired
@Qualifier("stringRedisTemplate")
StringRedisTemplate template;

void test(){
  template.multi();
  template.boundValueOps("somevkey").increment(1);
  template.boundZSetOps("somezkey").add("zvalue", timestamp);
  template.exec();
}

After running above code through Junit Test, Exceptions are thrown.

    org.springframework.data.redis.RedisSystemException: Unknown exception; nested exception is org.springframework.data.redis.RedisSystemException: Unknown jedis exception; nested exception is java.lang.NullPointerException
        at org.springframework.data.redis.connection.jedis.JedisUtils.convertJedisAccessException(JedisUtils.java:93)
        at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.translateExceptionIfPossible(JedisConnectionFactory.java:155)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:58)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:163)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
        at $Proxy66.appendUserStream(Unknown Source)
        at com.uniu.test.repository.StreamCacheRepositoryTest.testAppendUserStream(StreamCacheRepositoryTest.java:23)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
        at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
        at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
        at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
        at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
        at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
        at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: org.springframework.data.redis.RedisSystemException: Unknown jedis exception; nested exception is java.lang.NullPointerException
        at org.springframework.data.redis.connection.jedis.JedisConnection.convertJedisAccessException(JedisConnection.java:119)
        at org.springframework.data.redis.connection.jedis.JedisConnection.exec(JedisConnection.java:523)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.data.redis.core.CloseSuppressingInvocationHandler.invoke(CloseSuppressingInvocationHandler.java:58)
        at $Proxy70.exec(Unknown Source)
        at org.springframework.data.redis.core.RedisTemplate$1.doInRedis(RedisTemplate.java:416)
        at org.springframework.data.redis.core.RedisTemplate$1.doInRedis(RedisTemplate.java:412)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:162)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:133)
        at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:121)
        at org.springframework.data.redis.core.RedisTemplate.exec(RedisTemplate.java:412)
        at com.uniu.repository.impl.StreamCacheRepositoryRedisImpl.appendUserStream(StreamCacheRepositoryRedisImpl.java:37)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:318)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:155)
        ... 33 more
    Caused by: java.lang.NullPointerException
        at redis.clients.jedis.BinaryTransaction.exec(BinaryTransaction.java:31)
        at org.springframework.data.redis.connection.jedis.JedisConnection.exec(JedisConnection.java:521)
        ... 54 more

I check redis server, the above two commands have been executed and the result is correct. So the problem comes at last line of the code (template.exec()). When underlying JedisClient try to get multibulk response from EXEC, it seems to throw NullPointerException

I use spring-data-redis 1.0.0.RELEASE Thanks for help.

Zhang Jiayu
  • 143
  • 1
  • 2
  • 5

4 Answers4

14

The reason for the exception is probably that the Spring template implementation does not reuse the same connection for .multi() and .exec(). You can try to use execute() via a callback:

private RedisTemplate template = ...;

template.execute(

  new RedisCallback() {

    @Override
    public Object doInRedis(RedisConnection connection)
      throws DataAccessException {

      connection.multi();

      //do whatever you need, like deleting and repopulating some keys

      connection.expire(CHANNEL_KEY.getBytes(), EXPIRE_SECS);
      connection.exec();
      return null;
    }

  }

);
Greg Dubicki
  • 5,983
  • 3
  • 55
  • 68
  • I don't think this is true. As long as the multi and exec are called through the same factory and transaction is enabled, then any call in between will use the same connection. You can see from the RedisConnectionUtils – Olayinka Jun 28 '17 at 16:06
13
template.setEnableTransactionSupport(true);

maybe you can use this to enable transaction

asgs
  • 3,928
  • 6
  • 39
  • 54
Song Yang
  • 131
  • 1
  • 5
6

Piotr is right. You need to put it into SessionCallback.

Here is a quick summary of doing transactions with Spring Data Redis:

https://github.com/eric-wu/dashboard/wiki/3.-Redis-Transactions-via-Spring-Data-Redis

For your case, scroll down and look at "Optimistic locking using WATCH, MULTI, and EXEC".

neurite
  • 2,798
  • 20
  • 32
1

Look at the implementatin of org.springframework.data.redis.support.collections.CollectionUtils.rename(K, K, RedisOperations<K, ?>)

and read about transaction details here

static <K> void rename(final K key, final K newKey, RedisOperations<K, ?> operations) {
    operations.execute(new SessionCallback<Object>() {
        @SuppressWarnings("unchecked")
        public Object execute(RedisOperations operations) throws DataAccessException {
            do {
                operations.watch(key);

                if (operations.hasKey(key)) {
                    operations.multi();
                    operations.rename(key, newKey);
                } else {
                    operations.multi();
                }
            } while (operations.exec().isEmpty());
            return null;
        }
    });
}

Note : While loop is important

Maksim Ploter
  • 431
  • 1
  • 6
  • 13
craftsmannadeem
  • 2,665
  • 26
  • 22