2
votes

I make a POC with spring-boot-starter-data-jpa and spring-boot-starter-activemq. I would like to push the jms message on the broker (activeMQ) when the jpa transaction was commited.

My code : UtilsateurService with have the "main" transaction:

@Service
public class UtilisateurService {

    @Autowired
    private UtilisateurRepository utilisateurRepository;

    @Autowired
    private SendMessage sendMessage;

    @Transactional(rollbackOn = java.lang.Exception.class)
    public Utilisateur create(Utilisateur utilisateur) throws Exception {
        final Utilisateur result = utilisateurRepository.save(utilisateur);
        sendMessage.send("creation utilisateur : " + result.getId());
        throw new Exception("rollback");
        //return result;
    }
}

The SendMessage class witch "manage" Jms message:

@Component
public class SendMessage {

    @Autowired
    private JmsMessagingTemplate jmsMessagingTemplate;

    @Value("${jms.queue.destination}")
    private String destinationQueue;

    public void send(String msg) {
        this.jmsMessagingTemplate.convertAndSend(destinationQueue, msg);
    }

}

My main class :

@SpringBootApplication
@EnableJms
@EnableTransactionManagement
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

The JMS message was push on the activeMq broker before exception was throw. So I don't have "rollback" on the broker.

How can I configure to have xa transaction running?

2

2 Answers

5
votes

is your jmsTemplate Transacted ?

jmsTemplate.setSessionTransacted(true); 

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jms/support/JmsAccessor.html#setSessionTransacted-boolean-

public void setSessionTransacted(boolean sessionTransacted)

Set the transaction mode that is used when creating a JMS Session. Default is "false". Note that within a JTA transaction, the parameters passed to create(Queue/Topic)Session(boolean transacted, int acknowledgeMode) method are not taken into account. Depending on the Java EE transaction context, the container makes its own decisions on these values. Analogously, these parameters are not taken into account within a locally managed transaction either, since the accessor operates on an existing JMS Session in this case.

Setting this flag to "true" will use a short local JMS transaction when running outside of a managed transaction, and a synchronized local JMS transaction in case of a managed transaction (other than an XA transaction) being present. This has the effect of a local JMS transaction being managed alongside the main transaction (which might be a native JDBC transaction), with the JMS transaction committing right after the main transaction.

http://www.javaworld.com/article/2077963/open-source-tools/distributed-transactions-in-spring--with-and-without-xa.html

30.2.5 Transaction management

Spring provides a JmsTransactionManager that manages transactions for a single JMS ConnectionFactory. This allows JMS applications to leverage the managed transaction features of Spring as described in Chapter 17, Transaction Management. The JmsTransactionManager performs local resource transactions, binding a JMS Connection/Session pair from the specified ConnectionFactory to the thread. JmsTemplate automatically detects such transactional resources and operates on them accordingly.

In a Java EE environment, the ConnectionFactory will pool Connections and Sessions, so those resources are efficiently reused across transactions. In a standalone environment, using Spring’s SingleConnectionFactory will result in a shared JMS Connection, with each transaction having its own independent Session. Alternatively, consider the use of a provider-specific pooling adapter such as ActiveMQ’s PooledConnectionFactory class.

JmsTemplate can also be used with the JtaTransactionManager and an XA-capable JMS ConnectionFactory for performing distributed transactions. Note that this requires the use of a JTA transaction manager as well as a properly XA-configured ConnectionFactory! (Check your Java EE server’s / JMS provider’s documentation.)

Reusing code across a managed and unmanaged transactional environment can be confusing when using the JMS API to create a Session from a Connection. This is because the JMS API has only one factory method to create a Session and it requires values for the transaction and acknowledgment modes. In a managed environment, setting these values is the responsibility of the environment’s transactional infrastructure, so these values are ignored by the vendor’s wrapper to the JMS Connection. When using the JmsTemplate in an unmanaged environment you can specify these values through the use of the properties sessionTransacted and sessionAcknowledgeMode. When using a PlatformTransactionManager with JmsTemplate, the template will always be given a transactional JMS Session.

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/jms.html#jms-tx

1
votes

Hassen give the solution. So I change the SendMessage class to :

@Component
public class SendMessage {

    private final JmsMessagingTemplate jmsMessagingTemplate;

    @Value("${jms.queue.destination}")
    private String destinationQueue;

    @Autowired
    public SendMessage(JmsMessagingTemplate jmsMessagingTemplate) {
        this.jmsMessagingTemplate = jmsMessagingTemplate;
        this.jmsMessagingTemplate.getJmsTemplate().setSessionTransacted(true);
    }

    public void send(String msg) {
        this.jmsMessagingTemplate.convertAndSend(destinationQueue, msg);
    }

}