diff --git a/redisson/pom.xml b/redisson/pom.xml index de469ca6f..e63618e65 100644 --- a/redisson/pom.xml +++ b/redisson/pom.xml @@ -151,6 +151,12 @@ [3.1,5.0) test + + org.springframework + spring-test + [3.1,5.0) + test + net.jpountz.lz4 diff --git a/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionHolder.java b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionHolder.java new file mode 100644 index 000000000..c4ebc849a --- /dev/null +++ b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionHolder.java @@ -0,0 +1,22 @@ +package org.redisson.spring.transaction; + +import org.redisson.api.RTransaction; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonTransactionHolder { + + private RTransaction transaction; + + public RTransaction getTransaction() { + return transaction; + } + + public void setTransaction(RTransaction transaction) { + this.transaction = transaction; + } + +} diff --git a/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionManager.java b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionManager.java index 6d2bc8bd5..37d95ab50 100644 --- a/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionManager.java +++ b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionManager.java @@ -15,58 +15,112 @@ */ package org.redisson.spring.transaction; -import org.redisson.api.RBatch; +import java.util.concurrent.TimeUnit; + +import org.redisson.api.RTransaction; import org.redisson.api.RedissonClient; +import org.redisson.api.TransactionOptions; +import org.springframework.transaction.NoTransactionException; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; +import org.springframework.transaction.TransactionSystemException; import org.springframework.transaction.support.AbstractPlatformTransactionManager; import org.springframework.transaction.support.DefaultTransactionStatus; import org.springframework.transaction.support.ResourceTransactionManager; import org.springframework.transaction.support.TransactionSynchronizationManager; +/** + * + * @author Nikita Koksharov + * + */ public class RedissonTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager { + private static final long serialVersionUID = -6151310954082124041L; + private RedissonClient redisson; + public RedissonTransactionManager(RedissonClient redisson) { + this.redisson = redisson; + } + + public RTransaction getCurrentTransaction() { + RedissonTransactionHolder to = (RedissonTransactionHolder) TransactionSynchronizationManager.getResource(redisson); + if (to == null) { + throw new NoTransactionException("No transaction is available for the current thread"); + } + return to.getTransaction(); + } + @Override protected Object doGetTransaction() throws TransactionException { - RedissonTransactionObject tObject = new RedissonTransactionObject(); + RedissonTransactionObject transactionObject = new RedissonTransactionObject(); - TransactionSynchronizationManager.getResource(redisson); - // TODO Auto-generated method stub - return null; + RedissonTransactionHolder holder = (RedissonTransactionHolder) TransactionSynchronizationManager.getResource(redisson); + if (holder != null) { + transactionObject.setTransactionHolder(holder); + } + return transactionObject; } @Override protected boolean isExistingTransaction(Object transaction) throws TransactionException { - // TODO Auto-generated method stub - return super.isExistingTransaction(transaction); + RedissonTransactionObject transactionObject = (RedissonTransactionObject) transaction; + return transactionObject.getTransactionHolder() != null; } @Override protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException { RedissonTransactionObject tObject = (RedissonTransactionObject) transaction; - if (tObject.getBatch() == null) { - RBatch batch = redisson.createBatch(); - batch.atomic(); - tObject.setBatch(batch); + if (tObject.getTransactionHolder() == null) { + int timeout = determineTimeout(definition); + TransactionOptions options = TransactionOptions.defaults(); + if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { + options.timeout(timeout, TimeUnit.SECONDS); + } + + RTransaction trans = redisson.createTransaction(options); + RedissonTransactionHolder holder = new RedissonTransactionHolder(); + holder.setTransaction(trans); + tObject.setTransactionHolder(holder); + TransactionSynchronizationManager.bindResource(redisson, holder); } - } @Override protected void doCommit(DefaultTransactionStatus status) throws TransactionException { - // TODO Auto-generated method stub - + RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction(); + try { + to.getTransactionHolder().getTransaction().commit(); + } catch (TransactionException e) { + throw new TransactionSystemException("Unable to commit transaction", e); + } } @Override protected void doRollback(DefaultTransactionStatus status) throws TransactionException { - // TODO Auto-generated method stub - + RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction(); + try { + to.getTransactionHolder().getTransaction().rollback(); + } catch (TransactionException e) { + throw new TransactionSystemException("Unable to commit transaction", e); + } } + @Override + protected void doSetRollbackOnly(DefaultTransactionStatus status) throws TransactionException { + RedissonTransactionObject to = (RedissonTransactionObject) status.getTransaction(); + to.setRollbackOnly(true); + } + + @Override + protected void doCleanupAfterCompletion(Object transaction) { + TransactionSynchronizationManager.unbindResourceIfPossible(redisson); + RedissonTransactionObject to = (RedissonTransactionObject) transaction; + to.getTransactionHolder().setTransaction(null); + } + @Override public Object getResourceFactory() { return redisson; diff --git a/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionObject.java b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionObject.java index d55d0150a..eabf56d51 100644 --- a/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionObject.java +++ b/redisson/src/main/java/org/redisson/spring/transaction/RedissonTransactionObject.java @@ -15,25 +15,33 @@ */ package org.redisson.spring.transaction; -import org.redisson.api.RBatch; import org.springframework.transaction.support.SmartTransactionObject; +/** + * + * @author Nikita Koksharov + * + */ public class RedissonTransactionObject implements SmartTransactionObject { - private RBatch batch; - - public RBatch getBatch() { - return batch; + private boolean isRollbackOnly; + private RedissonTransactionHolder transactionHolder; + + public RedissonTransactionHolder getTransactionHolder() { + return transactionHolder; } - public void setBatch(RBatch batch) { - this.batch = batch; + public void setTransactionHolder(RedissonTransactionHolder transaction) { + this.transactionHolder = transaction; } + public void setRollbackOnly(boolean isRollbackOnly) { + this.isRollbackOnly = isRollbackOnly; + } + @Override public boolean isRollbackOnly() { - // TODO Auto-generated method stub - return false; + return isRollbackOnly; } @Override diff --git a/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionContextConfig.java b/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionContextConfig.java new file mode 100644 index 000000000..511fd08b4 --- /dev/null +++ b/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionContextConfig.java @@ -0,0 +1,39 @@ +package org.redisson.spring.transaction; + +import javax.annotation.PreDestroy; + +import org.redisson.BaseTest; +import org.redisson.api.RedissonClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableTransactionManagement +public class RedissonTransactionContextConfig { + + @Bean + public TransactionalBean2 transactionBean2() { + return new TransactionalBean2(); + } + + @Bean + public TransactionalBean transactionBean() { + return new TransactionalBean(); + } + + @Bean + public RedissonTransactionManager transactionManager(RedissonClient redisson) { + return new RedissonTransactionManager(redisson); + } + + @Bean + public RedissonClient redisson() { + return BaseTest.createInstance(); + } + + @PreDestroy + public void destroy() { + redisson().shutdown(); + } +} diff --git a/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionManagerTest.java b/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionManagerTest.java new file mode 100644 index 000000000..b7f2c4ca0 --- /dev/null +++ b/redisson/src/test/java/org/redisson/spring/transaction/RedissonTransactionManagerTest.java @@ -0,0 +1,90 @@ +package org.redisson.spring.transaction; + +import static org.assertj.core.api.Assertions.*; + +import java.io.IOException; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.redisson.RedisRunner; +import org.redisson.api.RMap; +import org.redisson.api.RedissonClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.transaction.TransactionSuspensionNotSupportedException; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = RedissonTransactionContextConfig.class) +public class RedissonTransactionManagerTest { + + @Autowired + private RedissonClient redisson; + + @Autowired + private TransactionalBean transactionalBean; + + @BeforeClass + public static void beforeClass() throws IOException, InterruptedException { + RedisRunner.startDefaultRedisServerInstance(); + } + + @AfterClass + public static void afterClass() throws IOException, InterruptedException { + RedisRunner.shutDownDefaultRedisServerInstance(); + } + + @Test + public void test() { + transactionalBean.testTransactionIsNotNull(); + transactionalBean.testNoTransaction(); + + transactionalBean.testCommit(); + RMap map1 = redisson.getMap("test1"); + assertThat(map1.get("1")).isEqualTo("2"); + + try { + transactionalBean.testRollback(); + Assert.fail(); + } catch (IllegalStateException e) { + // skip + } + RMap map2 = redisson.getMap("test2"); + assertThat(map2.get("1")).isNull(); + + transactionalBean.testCommitAfterRollback(); + assertThat(map2.get("1")).isEqualTo("2"); + + try { + transactionalBean.testNestedNewTransaction(); + Assert.fail(); + } catch (TransactionSuspensionNotSupportedException e) { + // skip + } + RMap mapTr1 = redisson.getMap("tr1"); + assertThat(mapTr1.get("1")).isNull(); + RMap mapTr2 = redisson.getMap("tr2"); + assertThat(mapTr2.get("2")).isNull(); + + transactionalBean.testPropagationRequired(); + RMap mapTr3 = redisson.getMap("tr3"); + assertThat(mapTr3.get("2")).isEqualTo("4"); + + try { + transactionalBean.testPropagationRequiredWithException(); + Assert.fail(); + } catch (IllegalStateException e) { + // skip + } + RMap mapTr4 = redisson.getMap("tr4"); + assertThat(mapTr4.get("1")).isNull(); + RMap mapTr5 = redisson.getMap("tr5"); + assertThat(mapTr5.get("2")).isNull(); + + } + + +} diff --git a/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean.java b/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean.java new file mode 100644 index 000000000..8478f0e6e --- /dev/null +++ b/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean.java @@ -0,0 +1,74 @@ +package org.redisson.spring.transaction; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Assert; +import org.redisson.api.RTransaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.NoTransactionException; +import org.springframework.transaction.annotation.Transactional; + +public class TransactionalBean { + + @Autowired + private RedissonTransactionManager transactionManager; + + @Autowired + private TransactionalBean2 transactionalBean2; + + @Transactional + public void testTransactionIsNotNull() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + assertThat(transaction).isNotNull(); + } + + public void testNoTransaction() { + try { + RTransaction transaction = transactionManager.getCurrentTransaction(); + Assert.fail(); + } catch (NoTransactionException e) { + // skip + } + } + + @Transactional + public void testCommit() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("test1").put("1", "2"); + } + + @Transactional + public void testRollback() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("test2").put("1", "2"); + throw new IllegalStateException(); + } + + @Transactional + public void testCommitAfterRollback() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("test2").put("1", "2"); + } + + @Transactional + public void testNestedNewTransaction() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("tr1").put("1", "0"); + + transactionalBean2.testInNewTransaction(); + } + + @Transactional + public void testPropagationRequired() { + transactionalBean2.testPropagationRequired(); + } + + @Transactional + public void testPropagationRequiredWithException() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("tr4").put("1", "0"); + + transactionalBean2.testPropagationRequiredWithException(); + } + +} diff --git a/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean2.java b/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean2.java new file mode 100644 index 000000000..e13875020 --- /dev/null +++ b/redisson/src/test/java/org/redisson/spring/transaction/TransactionalBean2.java @@ -0,0 +1,32 @@ +package org.redisson.spring.transaction; + +import org.redisson.api.RTransaction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +public class TransactionalBean2 { + + @Autowired + private RedissonTransactionManager transactionManager; + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void testInNewTransaction() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("tr2").put("2", "4"); + } + + @Transactional + public void testPropagationRequired() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("tr3").put("2", "4"); + } + + @Transactional + public void testPropagationRequiredWithException() { + RTransaction transaction = transactionManager.getCurrentTransaction(); + transaction.getMap("tr5").put("2", "4"); + throw new IllegalStateException(); + } + +}