Basically, I have a JMS queue and an MDB to collects the messages from the JMS queue, does some processing on them, and then persists the messages into the database via JPA. I marked the method, which is responsible for persisting the message into DB, to be started in a new transaction:
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void create(T entity)
{
try
{
getEntityManager().persist(entity);
}
catch(Exception e)
{
throw new RuntimeException("DB Exception");
}
}
If a transaction is rolled back, is it going to be retired automatically until the transaction completes? If not, how to enable that?
If the exception propagates to the MDB, the transaction will not commit, the message will not be acknowledged as received and will be retried. From the EJB 3.1 specs:
Message acknowledgment is automatically handled by the container. If
the message-driven bean uses container-managed transaction
demarcation, message acknowledgment is handled automatically as a part
of the transaction commit.
I'm not familiar with Weblogic but there should be a JMS queue parameter that set the number of retries, retry interval, etc. until the message is dropped or put on an undelivered queue.
But usually is better to catch the exception in the MDB because a RuntimeException thrown from the MDB results in the bean to discarded by the container. From the EJB 3.1 specs:
Message-driven beans should not, in general, throw RuntimeExceptions.
A RuntimeException that is not an application exception thrown from
any method of the message-driven bean class (including a message
listener method and the callbacks invoked by the container) results in
the transition to the “does not exist” state.
For example, is better to have:
public class MyMDB implements MessageListener {
#Resource
private MessageDrivenContext context;
public void onMessage() {
try {
//some other processing
someService.create(entity);
}
catch(Exception e) {
//mark the message as undelivered
context.setRollbackOnly();
}
}
}
Related
Let's say I have a #KafkaListener class with a #KafkaHandler method inside that processes any received messages and does some DB operations.
I want to have fine-grained control over how and when to commit (or rollback) the database changes (i.e. manually manage the DB transaction) in this class. The consumed message offset can be committed regardless of the DB transaction result.
Here is a simplified version of what I have:
#Service
#RequiredArgsConstructor
#KafkaListener(
topics = "${kafka.topic.foo}",
groupId = "${spring.kafka.consumer.group-id-foo}",
containerFactory = "kafkaListenerContainerFactoryFoo")
public class FooMessageConsumer {
// ...
private final EntityManager entityManager;
#KafkaHandler
public void handleMessage(FooMessage msg) {
// ...
handleDBOperations(msg);
// ...
}
void handleDBOperations(msg) {
try {
entityManager.getTransaction().begin();
// ...
entityManager.getTransaction().commit();
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
entityManager.getTransaction().rollback();
}
}
}
When a message is received and entityManager.getTransaction().begin(); is invoked, this results in an exception:
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
Why am I not allowed to create a transaction here?
And if I remove EntityManager and add #Transactional annotations to methods with DB operations (though this is not exactly what I want), then it results in another exception:
TransactionRequiredException Executing an update/delete query
It seems like it completely ignores the annotation. Is this somehow related to Kafka consumer having its own transaction management?
In short, what am I doing wrong here and how can I manage DB transactions in a #KafkaHandler method?
Any help is appreciated.
Thanks in advance.
Try using Springs TransactionTemplate:
https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch10s06.html
If your use case is (that) simple, Springs declarative transaction management should also let you achieve the behavior you ask for:
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s05.html
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?
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
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);
}
}
I want to rollback my transaction on an exception which works absolutely fine. But now I do not want to rollback all actions I made.
For example, a request to my application gets processed and several database actions on multiple databases are done. If the exception is thrown I want to rollback all actions on one of my databases and only 2 actions on the second database. How can I do that? I always end up rolling bback the whole transaction...
what I tried so far:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public <Param> GenericResponseMsg executeRequest (Param myParam) {
entityManager1.persist(someEntity); // rollback
...
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void addFailedRequestToDatabase() {
entityManager2.persist(otherEntity); // do not rollback
...
}
I also tried to annotate the class with
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
but this results in the following exception:
Internal Exception: java.sql.SQLException: Connection can not be used while enlisted in another transaction
Error Code: 0
Any ideas? I am somehow stuck and don't know what to do anymore...
EDIT:
Here is the workflow you asked for:
#Stateless
#Path("my/path")
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyRessource {
#EJB
private MyEJB myEjb;
#POST
#Path("method/Path")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public GenericResponseMsg doSomeStuff(Param param) throws Exception {
try {
return myEjb.executeRequest(param);
} catch(Throwable throwable) {
myEjb.addFailedRequestToDatabase();
throw throwable;
}
}
}
#TransactionAttribute is support for EJB as per the specification. #Transactional support is coming in tomee 7 (currently under vote #asf).
Side note: tomee and openejb standalone code is 100% the same for JTA support.
I wonder how to use JMS transactions correctly inside an EJB container. I found this code, that sends messages using JMS in a stateless bean:
#Stateless
public class SendEventsBean {
private static final Logger log = Logger.getLogger(SendEventsBean.class);
#Resource(mappedName = "jms/MyConnectionFactory")
private ConnectionFactory jmsConnectionFactory;
#Resource(mappedName = "jms/myApp/MyQueue")
private Queue queue;
public void sendEvent() {
Connection jmsConnection = null;
try {
connection = jmsConnectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
MyObj obj = new MyObj(1, "Foo");
ObjectMessage myObjMsg = session.createObjectMessage(obj);
producer.send(myObjMsg);
} catch (JMSException jmxEx) {
log.error("Couldn't send JMS message: ", jmsEx);
}finally{
if (jmsConnection != null) {
try {
jmsConnection.close();
}catch(JMSException ex) {
log.warn("Couldn't close JMSConnection: ", ex);
}
}
}
}
(from When should I close a JMS connection that was created in a stateless session bean? )
At default the transactions are container managed with transaction attribute 'required'. Suppose a client calls sendEvent() directly, so that the transaction starts at the beginning and ends at the end of sendEvent() (-> a commit is executed at the very end of the method).
Isn't it wrong to close the connection (jmsConnection.close()) BEFORE the commit occurs at the very end of the method?
Furthermore I'm wondering how setting the transactional attribute and setting true/false at createSession() interacts.
Does it make sense setting createSession(true,...) if there is already a transaction started by the container (using container managed transactions)? Does this create a new transaction just for JMS messages (and not for DB also) inside the JTA transaction?
And with createSession(false, ...) am I right, that messages are nevertheless transactional because of the transaction started by the container?
Isn't it wrong to close the connection (jmsConnection.close()) BEFORE the commit occurs at the very end of the method?
No. Closing connections has got nothing to do with commit within a JTA transaction (which is the case here, it being an ejb with CMT). It is just proper and essential cleanup. Note these are connections returned by the container and the underlying transaction manager knows how to work with the resource to commit the transactions. Same goes for JDBC connections as well.
Does it make sense setting createSession(true,...) if there is already a transaction started by the container (using container managed transactions)?
For Weblogic, you should definitely be using non transacted sessions. But what is important to use XA connection factories for your JMS connections.
http://docs.oracle.com/cd/E11035_01/wls100/jms/trans.html#wp1031645
http://www.informit.com/articles/article.aspx?p=26137&seqNum=8
However articles related to JBOSS suggests setting the createSession(true...) as a good practise even within a CMT ejb
https://developer.jboss.org/thread/213629?tstart=0&_sscc=t
http://www.coderanch.com/t/606786/EJB-JEE/java/EJB-CMT-sending-JMS-message
Irrespective of the setting, JCA/XA based connection factories have to be used compulsorily.
And with createSession(false, ...) am I right, that messages are
nevertheless transactional because of the transaction started by the
container?
No. As mentioned above, you will have to use XA connection factories.
#Transactional (noRollbackFor=RuntimeException.class)
public void methodA (Entity e){
service.methodB(e);
}
---service method below---
#Transactional (propagation=Propagation.REQUIRES_NEW, noRollbackFor=RuntimeException.class)
public void methodB (Entity e){
dao.insert(e);
}
When dao.insert(e) in methodB() causes a primary key violation and throws a ConstraintViolationException, which is a subclass of RuntimeException, I would expect the transaction to still commit because of the noRollbackFor property I used. But I observed that the outer transaction (on methodA) is still being rolled back by the HibernateTransactionManager with the message
org.springframework.transaction.UnexpectedRollback Exception:
Transaction rolled back because it has been marked as rollback-only
I've found similar questions reported but not exactly this one.
Once an exception is caught, the Hibernate Session should be discarded and the transaction should be rolled back:
If the Session throws an exception, the transaction must be rolled
back and the session discarded. The internal state of the Session
might not be consistent with the database after the exception occurs.
So, noRollbackFor applies to your Service and DAO layer that might throw an exception. Let's say you have a gatewayService that write to a Database through a Hibernate DAO and also sends an email through an emailService. If the emailService throws a SendMailFailureException you can instruct the gatewayService not to roll back when it will catch this exception:
#Transactional(noRollbackFor=SendMailFailureException.class)
public void saveAndSend(Entity e){
dao.save(e);
emailService.send(new Email(e));
}