I am trying to develop a small Springboot application (using JHipster) that listens to a RabbitMQ queue, acknowledges the messages and then runs some operations on a hibernate repository. For now I can consume messages with no problem. But when I try to use the hibernate repository when receiving a message, it appears the TransactionManagers get mixed up and I get the Exception:
Caused by: org.hibernate.TransactionException: Unable to commit against JDBC Connection
at org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor.commit(AbstractLogicalConnectionImplementor.java:92)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:282)
...
Caused by: org.postgresql.util.PSQLException: Cannot commit when autoCommit is enabled.
at org.postgresql.jdbc.PgConnection.commit(PgConnection.java:872)
My Rabbit configuration is simplistic:
#Configuration
public class RabbitExchangeConfiguration {
#Bean
Queue queue() {
return new Queue("myQueue", false);
}
}
Then there is my TestService, with a RabbitListener (listen) and a method that uses my database transaction manager to access the repository (test)
#Service
public class TestService {
public TestService(
MyRepository myRepository
) {
this.myRepository= myRepository;
}
// My Listener to receive the messages
#RabbitListener(queues = "datafactory")
public void listen(Message in) {
String messageBody = new String(in.getBody());
test(messageBody);
}
// the method that should do stuff with the repository
#Transactional(transactionManager = "myDatabaseTransactionManager")
public void test(String content) {
myRepository.findById(content).orElse(null);
..
}
}
Right now I don't care what the message contains, nor is it important if the entity can be found.
When I comment out the call to test(content) then the message is received (I can see it in the logs). But when I try to call the function, then I get the Exception.
Is there any way to end the RabbitMQ-transaction before entering the hibernate transaction context? Why is the listener trying to use the hibernate transaction?
solved
Thanks to the answer of p3consulting I found that jpa.properties.hibernate.connection.provider_disables_autocommit=true was set by default. It was configuered automatically by JHipster for me. I just had to make sure that autocommit was disabled for good:
spring.datasource.hikari.auto-commit=false // disables auto commit
spring.jpa.properties.hibernate.connection.provider_disables_autocommit=false // makes sure it stays that way
So nothing to do with RabbitMQ...
The message is clear: you left autocommit to TRUE then you can't commit explicitly because it's done for you after each DML statement, you could turn it off with hibernate.connection.autocommit=false in your properties or calling connection.setAutoCommit(false);
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
It's not exactly as the title says, but close to. Consider these Spring beans:
#Bean
class BeanA {
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = EvilException.class)
public void methodA() {
/* ... some actions */
if (condition) {
throw new EvilException();
}
}
}
#Bean
class BeanB {
#Autowired private BeanA beanA;
final int MAX_TRIES = 3;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
// prepare to call Bean A
try {
beanA.methodA();
/* maybe do some more things */
}
catch (EvilException e) {
/* recover from evil */
}
}
}
#Bean
class MainWorkerBean {
#Autowired private BeanB beanB;
#Autowired private OtherBean otherBean;
#Transactional(propagation = Propagation.REQUIRED)
public void doSomeWork() {
beanB.methodB();
otherBean.doSomeWork();
}
}
Important note: I'm using JDBC transaction manager that supports savepoints.
What I'm expecting this to do is, when EvilException is thrown, the transaction of the BeanA is rolled back, which with this setup happens to be the savepoint created by starting methodB. However, this appears to not be the case.
When going over with debugging tools, what I'm seeing is this:
When doSomeWork of MainWorkerBean starts, new transaction is created
When methodB starts, transaction manager properly initializes a savepoint and hands it to TransactionInterceptor
When methodA starts, transaction manager sees Propagation.REQUIRED again, and hands out a clean reference to the actual JDBC transaction again, that has no knowledge of the savepoint
This means that when exception is thrown, TransactionStatus::hasSavepoint return false, which leads to roll back of the whole global transaction, so recovery and further steps are as good as lost, but my actual code has no knowledge of the rollback (since I've written recovery for it).
For now, I can't consider changing BeanA's transaction to Propagation.NESTED. Admittedly, looks like it's going to allow me to have the more local rollback, but it's going to be too local, because as I understand it, Spring then will have two savepoints, and only roll back the BeanA savepoint, not BeanB one, as I'd like.
Is there anything else I'm missing, such as a configuration option, that would make internal transaction with Propagation.REQUIRED consider that it is running inside a savepoint, and roll back to savepoint, not the whole thing?
Right now we're using Spring 4.3.24, but I already crawled through their code and can't spot any relevant changes, so I don't think upgrading will help me.
As described in this bug ticket: https://github.com/spring-projects/spring-framework/issues/11234
For spring versions < 5.0, in the situation described, the global transaction is set to 'rollback-only'.
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch globalRollbackOnParticipationFailure to false in such scenarios.
However, even for Spring5, I noticed when reproducing the problem, that the nested transaction may be rolled back, including all things done in the catch block of methodB(). So your recover code might not work inside methodB(), depending on what your recovery looks like. If methodA() was not transactional, that would not happen. Just something to watch out for.
Some more details to be found here: https://github.com/spring-projects/spring-framework/issues/8135
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.
is it possible to declare an entity property so that it is reset to some default value, but only if the server hosting the ejb.jar is restarted?
the properties are used to to reflect the state of some connected clients that will be disconnected on restart.
Perhaps i should aprouch this problem with a different solution, but think the question is still valid.
How about if you have a hook that is triggered on application start/stop? Like ServletContextListener? On stop save the entries(users) interested in into a intermediar DB table and on start just re-connect.
I went with #Singleton and #Startup annotated EJB, tests seem to work
#Singleton
#Startup
public class ClientResetBean {
// Injected database connection:
#PersistenceContext private EntityManager em;
#PostConstruct
private void resetClientState() {
Query query = em.createQuery("UPDATE Client c SET c.online = false");
query.executeUpdate();
}
}