Spring data transaction loop - spring

I use spring boot 2, with spring data jpa and hibernate
In a class I have this code.
...
private final MailContentBuilder mailContentBuilder;
private void sendEmail() {
try {
List<FactoryEmail> factoryEmails = prepareData();
if (factoryEmails != null) {
logger.info(String.valueOf(factoryEmails.size()) + " factories");
}
for (FactoryEmail factoryEmail : factoryEmails) {
String message = mailContentBuilder.build(factoryEmail);
if (factoryEmail.getEmails() != null && !factoryEmail.getEmails().isEmpty()) {
logger.info("prepare to sent email to : " + factoryEmail.getFactoryName());
mailService.sendHtmlMail(factoryEmail.getEmails(), "no conform", message);
setSampleEmailSent(factoryEmail);
Thread.sleep(5000);
}
}
} catch (MessagingException | InterruptedException ex) {
logger.error(ex.getMessage());
}
}
private void setSampleEmailSent(FactoryEmail factoryEmail) {
...
samplesServices.setEmailsSent(testSampleIdEmailSent);
}
In SampleService Class
#Transactional
public void setEmailsSent(Map<String, List<SampleId>> testSampleIdEmailSent) {
...
repository.save(....);
}
Because I loop, If one fail, I don't want to rollback for everybody. Is there a better way to do it?

That sort of approach should work well, albeit in some cases you might need #Transactional(propagation = Propagation.REQUIRES_NEW) instead?
The more programmatic approach used to be TransactionTemplate although I'm not sure if there's a more recent approach than this.

Related

How can i use #autowire in runnable spring boot

I have few MongoTemplate and Repos and i need to call them using #Autowire in my runnable class that is being executed by exceutor class using multi threading, now the problem is that when i run the application my AutoWire for mongoTempelate and Repos returns null pointer exception.
Executor class:
#Component
public class MessageConsumer implements ConsumerSeekAware {
#Autowired
AlarmDataRepository alarmDataRepository;
int assignableCores = ((Runtime.getRuntime().availableProcessors()));
ExecutorService executor = Executors.newFixedThreadPool(
assignableCores > 1 ? assignableCores : 1
);
int counter = 0;
List<String> uniqueRecords = new ArrayList<String>();
#KafkaListener(topics = "teltonikaTest", groupId = "xyz")
public void processMessages(#Payload List<String> payload, #Header(KafkaHeaders.RECEIVED_PARTITION_ID) List<Integer> partitions, #Header(KafkaHeaders.OFFSET) List<Long> offsets) throws UnsupportedEncodingException, DecodeException {
System.out.println("assignable resources are: " + assignableCores);
log.info("Batch Size is: {}", payload.size());
if(counter==0){
log.info("Teletonica Packets Received!");
}
for (int i = 0; i < payload.size(); i++) {
log.info("processing message='{}' with partition off-set='{}'", payload.get(i), partitions.get(i) + " _" + offsets.get(i));
}
uniqueRecords = payload.stream().distinct().collect(Collectors.toList());
Runnable worker = new TeltonikaWorkerThread(uniqueRecords);
executor.execute(worker);
counter++;
}
}
public class TeltonikaWorkerThread implements Runnable{
List<String> records;
List<CurrentDevice> currentDevices = new ArrayList<>();
#Autowired
CurrentDeviceRepository currentDeviceRepository;
#Autowired
MongoTemplate mongoTemplate;
public TeltonikaWorkerThread(List<String> records) {
this.records = records;
}
public void run() {
try {
processMessage();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (DecodeException e) {
e.printStackTrace();
}
}
public void processMessage() throws UnsupportedEncodingException,DecodeException {
for(Object record : records){
if(record!="0"){
try{
int IMEILength = record.toString().indexOf("FF");
String IMEI = record.toString().substring(0,IMEILength);
}
catch (Exception e){
e.printStackTrace();
}
}
}
}
}
If I understand correctly, your problem is about multiple beans and Spring doesn't know which one should be injected. There are several options here.
For example, you can use #Qualifier annotation based on the bean name or #Primary annotation.
If your problem is something else, please add an example to your question.

Race condition when use Kafka and JPA

I have a problem when using microservice and Kafka
for example, I have Service A and Service B they communicate by Kafka and they share the same database inside the database and I have two entities A and B and they share a one-to-many relationship, when I update entity A in service A entity B gets updated/changed as wanted but when I view service B. I can't see the changes that happened in service A.
In my case example code :
here we are in service A:
KafkaService:
public synchronized void getDriverService(Long orderId, Double longitude, Double latitude) {
driverService.getDriver(orderId,longitude,latitude);
driverService.collectionOrder(orderId);
}
driverService:
public void getDriver(Long orderId, Double longitude, Double latitude) {
final Driver [] y={new Driver()};
ascOrderRepository.findById(orderId).ifPresentOrElse(x->{
List<DriverDTO> drivers = findAllCarNearMe(latitude, longitude);
if(drivers.isEmpty())
throwEmptyDriver();
AscOrderDTO orderDto = ascOrderMapper.toDto(x);
int check;
for (DriverDTO dr : drivers) {
check = checkDriver();
if (check < 8) {
log.debug("///////////////////////// driver accept" + dr.getId().toString());
dr.setStatus(UNAVAILABLE);
dr.updateTotalTrip();
Driver driver=driverMapper.toEntity(dr);
driver.addOrders(x);
y[0]=driverRepository.save(driver);
log.debug(dr.toString());
log.debug("/////////////////////////////////////driver accept here /////////////////////////////////////////");
break;
}
}
},this::throwOrder);
}
// find All Car near me
public List<DriverDTO> findAllCarNearMe(Double latitude, Double longitude) {
checkDistance(latitude,longitude);
Point point = createPoint(latitude, longitude);
List<Driver> driver = driverRepository.findNearById(point, 10);
return driverMapper.toDto(driver);
}
public void collectionOrder(Long orderId)
{
ascOrderRepository.findById(orderId).ifPresentOrElse(y->{
if(y.getDriver()!=null) { // here new updated and find this updated into service A
try {
driverProducer.driverCollectionOrder(y.getId());
} catch (Exception e) {
e.printStackTrace();
}
}
else
{
throwDriverNotFind();
}
},this::throwOrder);
}
This is Producer:
#Component public class DriverProducer {
public
DriverProducer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate; }
public void driverCollectionOrder(Long orderId) throws Exception{ ObjectMapper obj=new ObjectMapper();
kafkaTemplate.send("collecting",obj.writeValueAsString(orderId));
}
Service B:
This is Consumer:
#KafkaListener(topics = "collecting",groupId= groupId)
public void doneOrderStatus(String data) throws NumberFormatException, Exception {
try
{
log.debug("i am in done order status order consumer");
OrderEvent event=OrderEvent.TO_BE_COLLECTED;
orderService.changeStatus(event, Long.parseLong(data));
}
catch (Exception e)
{
throw new Exception(e.getMessage());
}
}
This Method Has my Error:
public void changeStatus(OrderEvent event, Long orderId) throws Exception {
try {
Optional<AscOrder> order=ascOrderRepository.findById(orderId);
if (!order.isPresent()) {
throw new BadRequestAlertException("cannot find Order", "Order entity", "Id invalid");
}
if(order.get().getDriver()!=null) { // cant find Change Here
log.debug("===============================================================================================");
log.debug(order.get().getDriver().toString());
log.debug("===============================================================================================");
}
log.debug("i am in changeStatus ");
stateMachineHandler.stateMachine(event, orderId);
stateMachineHandler.handling(orderId);
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
The problem may be about the separate ORM sessions held by the services.
To overcome this you may try to reload the entity. To do that,
1- wire the entity manager
#Autowired
EntityManager entityManager;
2- Decorate changeStatus function with #Transactional annotation, unless there is an active transaction already going on.
3- Refresh the order entity
entityManager.refresh(order)

Usage of exceptionExpression in Spring Retry

According to documentation, I can use something like this in exceptionExpression: #Retryable(exceptionExpression="message.contains('this can be retried')")
But I want to get response body and check message inside it (from RestClientResponseException), something similar to this: exceptionExpression = "getResponseBodyAsString().contains('important message')"
I tried like that but it doesn't work. So, is it possible to do something similar and check info from responseBody?
Edit: Adding whole #Retryable annotation parameters with Gary Russell's suggestion:
#Retryable(value = HttpClientErrorException.class, exceptionExpression = "#{#root instanceof T(org.springframework.web.client.HttpClientErrorException) AND responseBodyAsString.contains('important message')}")
I'm using actual RestClientResponseException subclass that I'm catching but is still not triggering retry.
With the current release, the expression incorrectly requires static template markers; they will not be needed in 1.3.
#Retryable(exceptionExpression = "#{responseBodyAsString.contains('foo')}")
However, you can't use this expression if there are include or exclude properties so the expression should check the type:
#Retryable(exceptionExpression =
"#{#root instanceof T(org.springframework.web.client.RestClientResponseException) "
+ "AND responseBodyAsString.contains('foo')}")
EDIT
#SpringBootApplication
#EnableRetry
public class So61488237Application {
public static void main(String[] args) {
SpringApplication.run(So61488237Application.class, args).close();
}
#Bean
public ApplicationRunner runner(Foo foo) {
return args -> {
try {
foo.test(1, "foo.");
}
catch (Exception e) {
}
};
}
}
#Component
class Foo {
#Retryable(exceptionExpression =
"#{#root instanceof T(org.springframework.web.client.RestClientException) "
+ "AND responseBodyAsString.contains('foo')}")
public void test(int val, String str) {
System.out.println(val + ":" + str);
throw new RestClientResponseException("foo", 500, "bar", new HttpHeaders(), "foo".getBytes(),
StandardCharsets.UTF_8);
}
}
1:foo.
1:foo.
1:foo.
I've implemented the following approach, which in my opinion is much more convenient.
#Retryable(value = WebClientException.class,
exceptionExpression = RetryCheckerService.EXPRESSION,
maxAttempts = 5,
backoff = #Backoff(delay = 500))
public List<ResultDto> getSomeResource () {}
Here the RetryCheckerService encapsulates all needed logic.
#Service
public class RetryCheckerService {
public static final String EXPRESSION = "#retryCheckerService.shouldRetry(#root)";
public boolean shouldRetry(WebClientException ex) {
if (ex instanceof WebClientResponseException responseException) {
return responseException.getStatusCode().is5xxServerError()
|| responseException.getStatusCode().equals(HttpStatus.NOT_FOUND);
}
if (ex instanceof WebClientRequestException requestException) {
String message = requestException.getMessage();
if (message == null) {
return false;
}
return message.contains("HttpConnectionOverHTTP");
}
return false;
}
}

transactional unit testing with ObjectifyService - no rollback happening

We are trying to use google cloud datastore in our project and trying to use objectify as the ORM since google recommends it. I have carefully used and tried everything i could read about and think of but somehow the transactions don't seem to work. Following is my code and setup.
#RunWith(SpringRunner.class)
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ContextConfiguration(classes = { CoreTestConfiguration.class })
public class TestObjectifyTransactionAspect {
private final LocalServiceTestHelper helper = new LocalServiceTestHelper(
// Our tests assume strong consistency
new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy(),
new LocalMemcacheServiceTestConfig(), new LocalTaskQueueTestConfig());
private Closeable closeableSession;
#Autowired
private DummyService dummyService;
#BeforeClass
public static void setUpBeforeClass() {
// Reset the Factory so that all translators work properly.
ObjectifyService.setFactory(new ObjectifyFactory());
}
/**
* #throws java.lang.Exception
*/
#Before
public void setUp() throws Exception {
System.setProperty("DATASTORE_EMULATOR_HOST", "localhost:8081");
ObjectifyService.register(UserEntity.class);
this.closeableSession = ObjectifyService.begin();
this.helper.setUp();
}
/**
* #throws java.lang.Exception
*/
#After
public void tearDown() throws Exception {
AsyncCacheFilter.complete();
this.closeableSession.close();
this.helper.tearDown();
}
#Test
public void testTransactionMutationRollback() {
// save initial list of users
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
ObjectifyService.ofy().save().entities(users).now();
try {
dummyService.mutateDataWithException("username_1", 6L);
} catch (Exception e) {
e.printStackTrace();
}
List<UserEntity> users2 = this.dummyService.findAllUsers();
Assert.assertEquals("Size mismatch on rollback", users2.size(), 10);
boolean foundUserIdSix = false;
for (UserEntity userEntity : users2) {
if (userEntity.getUserId() == 1) {
Assert.assertEquals("Username update failed in transactional context rollback.", "username_1",
userEntity.getUsername());
}
if (userEntity.getUserId() == 6) {
foundUserIdSix = true;
}
}
if (!foundUserIdSix) {
Assert.fail("Deleted user with userId 6 but it is not rolledback.");
}
}
}
Since I am using spring, idea is to use an aspect with a custom annotation to weave objectify.transact around the spring service beans methods that are calling my daos.
But somehow the update due to ObjectifyService.ofy().save().entities(users).now(); is not gettign rollbacked though the exception throws causes Objectify to run its rollback code. I tried printing the ObjectifyImpl instance hashcodes and they are all same but still its not rollbacking.
Can someone help me understand what am i doing wrong? Havent tried the actual web based setup yet...if it cant pass transnational test cases there is no point in actual transaction usage in a web request scenario.
Update: Adding aspect, services, dao as well to make a complete picture. The code uses spring boot.
DAO class. Note i am not using any transactions here because as per code of com.googlecode.objectify.impl.TransactorNo.transactOnce(ObjectifyImpl<O>, Work<R>) a transnational ObjectifyImpl is flushed and committed in this method which i don't want. I want commit to happen once and rest all to join in on that transaction. Basically this is the wrong code in com.googlecode.objectify.impl.TransactorNo ..... i will try to explain my understanding a later in the question.
#Component
public class DummyDaoImpl implements DummyDao {
#Override
public List<UserEntity> loadAll() {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
return query.list();
}
#Override
public List<UserEntity> findByUserId(Long userId) {
Query<UserEntity> query = ObjectifyService.ofy().transactionless().load().type(UserEntity.class);
//query = query.filterKey(Key.create(UserEntity.class, userId));
return query.list();
}
#Override
public List<UserEntity> findByUsername(String username) {
return ObjectifyService.ofy().transactionless().load().type(UserEntity.class).filter("username", username).list();
}
#Override
public void update(UserEntity userEntity) {
ObjectifyService.ofy().save().entity(userEntity);
}
#Override
public void update(Iterable<UserEntity> userEntities) {
ObjectifyService.ofy().save().entities(userEntities);
}
#Override
public void delete(Long userId) {
ObjectifyService.ofy().delete().key(Key.create(UserEntity.class, userId));
}
}
Below is the Service class
#Service
public class DummyServiceImpl implements DummyService {
private static final Logger LOGGER = LoggerFactory.getLogger(DummyServiceImpl.class);
#Autowired
private DummyDao dummyDao;
public void saveDummydata() {
List<UserEntity> users = new ArrayList<UserEntity>();
for (int i = 1; i <= 10; i++) {
UserEntity user = new UserEntity();
user.setAge(i);
user.setUsername("username_" + i);
users.add(user);
}
this.dummyDao.update(users);
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#mutateDataWithException(java.lang.String, java.lang.Long)
*/
#Override
#ObjectifyTransactional
public void mutateDataWithException(String usernameToMutate, Long userIdToDelete) throws Exception {
//update one
LOGGER.info("Attempting to update UserEntity with username={}", "username_1");
List<UserEntity> mutatedUsersList = new ArrayList<UserEntity>();
List<UserEntity> users = dummyDao.findByUsername(usernameToMutate);
for (UserEntity userEntity : users) {
userEntity.setUsername(userEntity.getUsername() + "_updated");
mutatedUsersList.add(userEntity);
}
dummyDao.update(mutatedUsersList);
//delete another
UserEntity user = dummyDao.findByUserId(userIdToDelete).get(0);
LOGGER.info("Attempting to delete UserEntity with userId={}", user.getUserId());
dummyDao.delete(user.getUserId());
throw new RuntimeException("Dummy Exception");
}
/* (non-Javadoc)
* #see com.bbb.core.objectify.test.services.DummyService#findAllUsers()
*/
#Override
public List<UserEntity> findAllUsers() {
return dummyDao.loadAll();
}
Aspect which wraps the method annoted with ObjectifyTransactional as a transact work.
#Aspect
#Component
public class ObjectifyTransactionAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectifyTransactionAspect.class);
#Around(value = "execution(* *(..)) && #annotation(objectifyTransactional)")
public Object objectifyTransactAdvise(final ProceedingJoinPoint pjp, ObjectifyTransactional objectifyTransactional) throws Throwable {
try {
Object result = null;
Work<Object> work = new Work<Object>() {
#Override
public Object run() {
try {
return pjp.proceed();
} catch (Throwable throwable) {
throw new ObjectifyTransactionExceptionWrapper(throwable);
}
}
};
switch (objectifyTransactional.propagation()) {
case REQUIRES_NEW:
int limitTries = objectifyTransactional.limitTries();
if(limitTries <= 0) {
Exception illegalStateException = new IllegalStateException("limitTries must be more than 0.");
throw new ObjectifyTransactionExceptionWrapper(illegalStateException);
} else {
if(limitTries == Integer.MAX_VALUE) {
result = ObjectifyService.ofy().transactNew(work);
} else {
result = ObjectifyService.ofy().transactNew(limitTries, work);
}
}
break;
case NOT_SUPPORTED :
case NEVER :
case MANDATORY :
result = ObjectifyService.ofy().execute(objectifyTransactional.propagation(), work);
break;
case REQUIRED :
case SUPPORTS :
ObjectifyService.ofy().transact(work);
break;
default:
break;
}
return result;
} catch (ObjectifyTransactionExceptionWrapper e) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
LOGGER.error("An exception occured while executing [{}.{}] in a transactional context."
, packageName, methodName, e);
throw e.getCause();
} catch (Throwable ex) {
String packageName = pjp.getSignature().getDeclaringTypeName();
String methodName = pjp.getSignature().getName();
String fullyQualifiedmethodName = packageName + "." + methodName;
throw new RuntimeException("Unexpected exception while executing ["
+ fullyQualifiedmethodName + "] in a transactional context.", ex);
}
}
}
Now the problem code part that i see is as follows in com.googlecode.objectify.impl.TransactorNo:
#Override
public <R> R transact(ObjectifyImpl<O> parent, Work<R> work) {
return this.transactNew(parent, Integer.MAX_VALUE, work);
}
#Override
public <R> R transactNew(ObjectifyImpl<O> parent, int limitTries, Work<R> work) {
Preconditions.checkArgument(limitTries >= 1);
while (true) {
try {
return transactOnce(parent, work);
} catch (ConcurrentModificationException ex) {
if (--limitTries > 0) {
if (log.isLoggable(Level.WARNING))
log.warning("Optimistic concurrency failure for " + work + " (retrying): " + ex);
if (log.isLoggable(Level.FINEST))
log.log(Level.FINEST, "Details of optimistic concurrency failure", ex);
} else {
throw ex;
}
}
}
}
private <R> R transactOnce(ObjectifyImpl<O> parent, Work<R> work) {
ObjectifyImpl<O> txnOfy = startTransaction(parent);
ObjectifyService.push(txnOfy);
boolean committedSuccessfully = false;
try {
R result = work.run();
txnOfy.flush();
txnOfy.getTransaction().commit();
committedSuccessfully = true;
return result;
}
finally
{
if (txnOfy.getTransaction().isActive()) {
try {
txnOfy.getTransaction().rollback();
} catch (RuntimeException ex) {
log.log(Level.SEVERE, "Rollback failed, suppressing error", ex);
}
}
ObjectifyService.pop();
if (committedSuccessfully) {
txnOfy.getTransaction().runCommitListeners();
}
}
}
transactOnce is by code / design always using a single transaction to do things. It will either commit or rollback the transaction. there is no provision to chain transactions like a normal enterprise app would want.... service -> calls multiple dao methods in a single transaction and commits or rollbacks depending on how things look.
keeping this in mind, i removed all annotations and transact method calls in my dao methods so that they don't start an explicit transaction and the aspect in service wraps the service method in transact and ultimately in transactOnce...so basically the service method is running in a transaction and no new transaction is getting fired again. This is a very basic scenario, in actual production apps services can call other service methods and they might have the annotation on them and we could still end up in a chained transaction..but anyway...that is a different problem to solve....
I know NoSQLs dont support write consistency at table or inter table levels so am I asking too much from google cloud datastore?

dynamically register transaction listener with spring?

I have a springframework application in which I would like to add a transaction listener to a transaction which is currently in progress. The motivation is to trigger a post commit action which notifies downstream systems. I am using #Transactional to wrap a transaction around some service method -- which is where I want to create/register the post transaction listener. I want to do something "like" the following.
public class MyService {
#Transaction
public void doIt() {
modifyObjects();
// something like this
getTransactionManager().registerPostCommitAction(new
TransactionSynchronizationAdapter() {
public void afterCommit() {
notifyDownstream();
}
});
}
}
Spring has a TransactionSynchronization interface and adapter class which seems exactly what I want; however it is not immediately clear how to register one dynamically with either the current transaction, or the transaction manager. I would rather not subclass JtaTransactionManager if I can avoid it.
Q: Has anyone done this before.
Q: what is the simplest way to register my adapter?
Actually it was not as hard as I thought; spring has a static helper class that puts the 'right' stuff into the thread context.
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronizationAdapter() {
#Override
public void afterCommit() {
s_logger.info("TRANSACTION COMPLETE!!!");
}
}
);
you could use an aspect to match transactional methods aspect in your service to accomplish this:
#Aspect
public class AfterReturningExample {
#AfterReturning("execution(* com.mypackage.MyService.*(..))")
public void afterReturning() {
// ...
}
}
Here is a more complete solution I did for a similar problem that with wanting my messages sent after transactions are committed (I could have used RabbitMQ TX but they are rather slow).
public class MessageBusUtils {
public static Optional<MessageBusResourceHolder> getTransactionalResourceHolder(TxMessageBus messageBus) {
if ( ! TransactionSynchronizationManager.isActualTransactionActive()) {
return Optional.absent();
}
MessageBusResourceHolder o = (MessageBusResourceHolder) TransactionSynchronizationManager.getResource(messageBus);
if (o != null) return Optional.of(o);
o = new MessageBusResourceHolder();
TransactionSynchronizationManager.bindResource(messageBus, o);
o.setSynchronizedWithTransaction(true);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new MessageBusResourceSynchronization(o, messageBus));
}
return Optional.of(o);
}
private static class MessageBusResourceSynchronization extends ResourceHolderSynchronization<MessageBusResourceHolder, TxMessageBus> {
private final TxMessageBus messageBus;
private final MessageBusResourceHolder holder;
public MessageBusResourceSynchronization(MessageBusResourceHolder resourceHolder, TxMessageBus resourceKey) {
super(resourceHolder, resourceKey);
this.messageBus = resourceKey;
this.holder = resourceHolder;
}
#Override
protected void cleanupResource(MessageBusResourceHolder resourceHolder, TxMessageBus resourceKey,
boolean committed) {
resourceHolder.getPendingMessages().clear();
}
#Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_COMMITTED) {
for (Object o : holder.getPendingMessages()) {
messageBus.post(o, false);
}
}
else {
holder.getPendingMessages().clear();
}
super.afterCompletion(status);
}
}
}
public class MessageBusResourceHolder extends ResourceHolderSupport {
private List<Object> pendingMessages = Lists.newArrayList();
public void addMessage(Object message) {
pendingMessages.add(message);
}
protected List<Object> getPendingMessages() {
return pendingMessages;
}
}
Now in your class where you actually send the message you will do
#Override
public void postAfterCommit(Object o) {
Optional<MessageBusResourceHolder> holder = MessageBusTxUtils.getTransactionalResourceHolder(this);
if (holder.isPresent()) {
holder.get().addMessage(o);
}
else {
post(o, false);
}
}
Sorry for the long winded coding samples but hopefully that will show someone how to do something after a commit.
Does it make sense to override the transaction manager on the commit and rollback methods, calling super.commit() right at the beginning.

Resources