Skip to content

Commit

Permalink
issues/13540: Transaction Propagation support implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
sbespalov committed Jul 30, 2019
1 parent 64094ad commit f3d047a
Show file tree
Hide file tree
Showing 14 changed files with 473 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@
* {@link org.springframework.transaction.PlatformTransactionManager} implementation
* for a single {@link HazelcastInstance}. Binds a Hazelcast {@link TransactionContext}
* from the instance to the thread (as it is already bounded by Hazelcast itself) and makes it available for access.
* <p>
* <i>Note:</i> This transaction manager doesn't supports nested transactions, since Hazelcast doesn't support them either.
*
*
* @author Balint Krivan
* @see #getTransactionContext(HazelcastInstance)
Expand All @@ -59,7 +58,7 @@ public static TransactionContext getTransactionContext(HazelcastInstance hazelca
if (transactionContextHolder == null) {
throw new NoTransactionException("No TransactionContext with actual transaction available for current thread");
}
return transactionContextHolder.getContext();
return transactionContextHolder;
}

/**
Expand All @@ -84,7 +83,7 @@ protected Object doGetTransaction() throws TransactionException {
(TransactionContextHolder) TransactionSynchronizationManager.getResource(hazelcastInstance);
if (transactionContextHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound TransactionContext [" + transactionContextHolder.getContext() + "]");
logger.debug("Found thread-bound TransactionContext [" + transactionContextHolder + "]");
}
txObject.setTransactionContextHolder(transactionContextHolder, false);
}
Expand Down Expand Up @@ -123,7 +122,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) thr

private void closeTransactionContextAfterFailedBegin(HazelcastTransactionObject txObject) {
if (txObject.isNewTransactionContextHolder()) {
TransactionContext context = txObject.getTransactionContextHolder().getContext();
TransactionContext context = txObject.getTransactionContextHolder();
try {
context.rollbackTransaction();
} catch (Throwable ex) {
Expand All @@ -138,11 +137,11 @@ protected void doCommit(DefaultTransactionStatus status) throws TransactionExcep
HazelcastTransactionObject txObject = (HazelcastTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Committing Hazelcast transaction on TransactionContext ["
+ txObject.getTransactionContextHolder().getContext() + "]");
+ txObject.getTransactionContextHolder() + "]");
}

try {
txObject.getTransactionContextHolder().getContext().commitTransaction();
txObject.getTransactionContextHolder().commitTransaction();
} catch (com.hazelcast.transaction.TransactionException ex) {
throw new TransactionSystemException("Could not commit Hazelcast transaction", ex);
}
Expand All @@ -159,22 +158,41 @@ protected void doRollback(DefaultTransactionStatus status) throws TransactionExc
HazelcastTransactionObject txObject = (HazelcastTransactionObject) status.getTransaction();
if (status.isDebug()) {
logger.debug("Rolling back Hazelcast transaction on TransactionContext ["
+ txObject.getTransactionContextHolder().getContext() + "]");
+ txObject.getTransactionContextHolder() + "]");
}

TransactionContext tx = txObject.getTransactionContextHolder().getContext();
TransactionContext tx = txObject.getTransactionContextHolder();
tx.rollbackTransaction();
}

@Override
protected void doCleanupAfterCompletion(Object transaction) {
HazelcastTransactionObject txObject = (HazelcastTransactionObject) transaction;

if (txObject.isNewTransactionContextHolder()) {
TransactionSynchronizationManager.unbindResourceIfPossible(hazelcastInstance);
}
}

@Override
protected Object doSuspend(Object transaction) throws TransactionException {
HazelcastTransactionObject txObject = (HazelcastTransactionObject) transaction;

TransactionContext transactionContext = txObject.getTransactionContextHolder();
transactionContext.suspendTransaction();

txObject.getTransactionContextHolder().clear();
txObject.setTransactionContextHolder(null, false);

return TransactionSynchronizationManager.unbindResourceIfPossible(hazelcastInstance);
}

@Override
protected void doResume(Object transaction, Object suspendedResources) throws TransactionException {
TransactionContextHolder transactionContext = (TransactionContextHolder) suspendedResources;
transactionContext.resumeTransaction();

HazelcastTransactionObject txObject = (HazelcastTransactionObject) transaction;
txObject.setTransactionContextHolder(transactionContext, false);
TransactionSynchronizationManager.bindResource(hazelcastInstance, transactionContext);
}

private static class HazelcastTransactionObject implements SmartTransactionObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,26 @@

package com.hazelcast.spring.transaction;

import javax.transaction.xa.XAResource;
import com.hazelcast.transaction.TransactionContext;
import com.hazelcast.transaction.TransactionException;
import com.hazelcast.transaction.TransactionalList;
import com.hazelcast.transaction.TransactionalMap;
import com.hazelcast.transaction.TransactionalMultiMap;
import com.hazelcast.transaction.TransactionalObject;
import com.hazelcast.transaction.TransactionalQueue;
import com.hazelcast.transaction.TransactionalSet;

/**
* Holder wrapping a Hazelcast TransactionContext.
* <p>
* HazelcastTransactionManager binds instances of this class to the thread, for a given HazelcastInstance.
*
* @author Balint Krivan
* @author Sergey Bespalov
* @see HazelcastTransactionManager
*/
class TransactionContextHolder {
class TransactionContextHolder implements TransactionContext {

private final TransactionContext transactionContext;
private boolean transactionActive;
Expand All @@ -39,19 +48,65 @@ public boolean isTransactionActive() {
return transactionActive;
}

public TransactionContext getContext() {
return transactionContext;
public <K, V> TransactionalMap<K, V> getMap(String name) {
return transactionContext.getMap(name);
}

/**
* @see TransactionContext#beginTransaction()
*/
public void beginTransaction() {
transactionContext.beginTransaction();
transactionActive = true;
}

public void clear() {
transactionActive = false;
public void commitTransaction() throws TransactionException {
try {
transactionContext.commitTransaction();
} finally {
transactionActive = false;
}
}

public <E> TransactionalQueue<E> getQueue(String name) {
return transactionContext.getQueue(name);
}

public void rollbackTransaction() {
try {
transactionContext.rollbackTransaction();
} finally {
transactionActive = false;
}
}

public <K, V> TransactionalMultiMap<K, V> getMultiMap(String name) {
return transactionContext.getMultiMap(name);
}

public void suspendTransaction() {
transactionContext.suspendTransaction();
}

public void resumeTransaction() {
transactionContext.resumeTransaction();
}

public String getTxnId() {
return transactionContext.getTxnId();
}

public XAResource getXaResource() {
return transactionContext.getXaResource();
}

public <E> TransactionalList<E> getList(String name) {
return transactionContext.getList(name);
}

public <E> TransactionalSet<E> getSet(String name) {
return transactionContext.getSet(name);
}

public <T extends TransactionalObject> T getTransactionalObject(String serviceName, String name) {
return transactionContext.getTransactionalObject(serviceName, name);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@

public class DummyObject implements Serializable {

private Long id;
private String string;

public DummyObject() {
}
private final Long id;
private final String string;

public DummyObject(long id, String string) {
this.id = id;
Expand All @@ -35,15 +32,31 @@ public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getString() {
return string;
}

public void setString(String string) {
this.string = string;
@Override
public int hashCode() {
if (id == null) {
return super.hashCode();
}

return id.hashCode();
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof DummyObject)) {
return false;
}

DummyObject other = (DummyObject) obj;
if (id == other.id) {
return true;
}

return id != null && id.equals(other.id);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,23 @@ public void put(DummyObject object) {
transactionalContext.getMap("dummyObjectMap").put(object.getId(), object);
}

@Transactional
public DummyObject get(Long id) {
return (DummyObject) transactionalContext.getMap("dummyObjectMap").get(id);
}

@Transactional
public void putWithException(DummyObject object) {
put(object);
throw new RuntimeException("oops, let's rollback in " + this.getClass().getSimpleName() + "!");
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void putInNewTransaction(DummyObject object) {
put(object);
public void putInNewTransaction(DummyObject object1, DummyObject object2) {
if (get(object1.getId()) != null) {
return;
}
put(object2);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ public void put(DummyObject object) {
transactionalContext.getMap("dummyObjectMap").put(object.getId(), object);
}

public DummyObject get(Long id) {
return (DummyObject) transactionalContext.getMap("dummyObjectMap").get(id);
}

public void putWithException(DummyObject object) {
put(object);
throw new RuntimeException("oops, let's rollback!");
Expand All @@ -50,8 +54,11 @@ public void putUsingOtherBean_sameTransaction_withException(DummyObject object)
otherService.putWithException(object);
}

public void putUsingOtherBean_newTransaction(DummyObject object) {
otherService.putInNewTransaction(object);
public boolean putUsingOtherBean_newTransaction(DummyObject object1, DummyObject object2) {
put(object1);
otherService.putInNewTransaction(object1, object2);

return get(object2.getId()) != null;
}

public void putUsingSameBean_thenOtherBeanThrowingException_sameTransaction(DummyObject object, DummyObject otherObject) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.hazelcast.client.HazelcastClient;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
import com.hazelcast.transaction.TransactionalMap;
import com.hazelcast.spring.CustomSpringJUnit4ClassRunner;
import com.hazelcast.test.annotation.QuickTest;
Expand All @@ -34,11 +35,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.transaction.NoTransactionException;
import org.springframework.transaction.TransactionSuspensionNotSupportedException;
import org.springframework.transaction.annotation.Transactional;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

@RunWith(CustomSpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"transaction-applicationContext-hazelcast.xml"})
Expand Down Expand Up @@ -185,15 +186,25 @@ public void transactionalServiceBeanInvocation_nestedWithPropagationRequired() {

/**
* Tests that if propagation is set to {@link org.springframework.transaction.annotation.Propagation#REQUIRES_NEW REQUIRES_NEW},
* then an exception will be thrown, since Hazelcast doesn't support nested transaction, so {@link HazelcastTransactionManager}
* doesn't support transaction suspension.
* then nested transaction will be created.
*/
@Test
public void transactionalServiceBeanInvocation_nestedWithPropagationRequiresNew() {
// given
expectedException.expect(TransactionSuspensionNotSupportedException.class);
DummyObject dummyObject1 = new DummyObject(1L, "magic1");
DummyObject dummyObject2 = new DummyObject(2L, "magic2");
IMap<Object, Object> dummyObjectMap = instance.getMap("dummyObjectMap");

// when
service.putUsingOtherBean_newTransaction(new DummyObject(1L, "magic"));
boolean result = service.putUsingOtherBean_newTransaction(dummyObject1, dummyObject2);

// then
assertTrue("No data changes within nested transaction .", result);
assertEquals("Both transactions should have data changes.", 2L, dummyObjectMap.size());
assertTrue("No data changes within parent transaction.", dummyObjectMap.containsKey(1L));
assertEquals("Invalid data within parent transaction.", dummyObject1, dummyObjectMap.get(1L));
assertTrue("No data changes within nested transaction.", dummyObjectMap.containsKey(2L));
assertEquals("Invalid data within nested transaction.", dummyObject2, dummyObjectMap.get(2L));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@ public void rollbackTransaction() {
transaction.rollback();
}

@Override
public void suspendTransaction() {
// TODO: implemet
throw new UnsupportedOperationException("Not implemented yet.");
}

@Override
public void resumeTransaction() {
// TODO: implemet
throw new UnsupportedOperationException("Not implemented yet.");
}

@Override
public <K, V> TransactionalMap<K, V> getMap(String name) {
return getTransactionalObject(MapService.SERVICE_NAME, name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ public void rollbackTransaction() {
throw new UnsupportedOperationException("XA Transaction cannot be rolled back manually!");
}

@Override
public void suspendTransaction() {
throw new UnsupportedOperationException("XA Transaction cannot be suspended manually!");
}

@Override
public void resumeTransaction() {
throw new UnsupportedOperationException("XA Transaction cannot be resumed manually!");
}

@Override
public String getTxnId() {
return transaction.getTxnId();
Expand Down
Loading

0 comments on commit f3d047a

Please sign in to comment.