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