I'm having some trouble with transaction management in EJB3.0. What I want to do is to log an error into the database in case an exception happens.
For that purpose I have 2 stateless beans: Bean A and Bean B.
Bean A does the following:
save something
call Bean B to log an error if needed
In Step 1, the save is basically using the EntityManager#merge(-) method.
In Step 2, I have put the following lines at the top of Bean B:
#Stateless(name = "ErrorLogDAO", mappedName = "ErrorLogDAO")
#Remote
#Local
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class ErrorLogDAOBean {...}
However, when an exception is taking place in the save method, I'm catching it and then I manually invoke the ctx.setRollBackOnly() method and after that I call ErrorLogDAOBean that inserts an error log to the DB. But the error log is not being inserted and the error I'm getting is:
javax.transaction.TransactionRolledbackException: EJB Exception: :
weblogic.transaction.internal.AppSetRollbackOnlyException at
weblogic.transaction.internal.TransactionImpl.setRollbackOnly(TransactionImpl.java:551)
at
weblogic.transaction.internal.TransactionManagerImpl.setRollbackOnly(TransactionManagerImpl.java:319)
at
weblogic.transaction.internal.TransactionManagerImpl.setRollbackOnly(TransactionManagerImpl.java:312)
at
org.eclipse.persistence.transaction.JTATransactionController.markTransactionForRollback_impl(JTATransactionController.java:145)
at
org.eclipse.persistence.transaction.AbstractTransactionController.markTransactionForRollback(AbstractTransactionController.java:196)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.rollbackTransaction(UnitOfWorkImpl.java:4486)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1351)
at
org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:468)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1439)
at
org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:316)
at
org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:527)
....
I'm well familiar with transaction management logic, and based on the code above I assumed I had this covered, but it appears not.
Any ideas?
UPDATE
Bean A Code:
#TransactionManagement(value = TransactionManagementType.CONTAINER)
#TransactionAttribute(value = REQUIRED)
public class WMServiceBOBean {
public void saveInBeanA {
int errorCode = save();
if (errorCode != SUCCESS)
{
ClassX.logError();
ctx.setRollbackOnly();
return errorCode;
}
}
}
Class X Code:
public class classX
{
...
public void logError()
{
ErrorLog e = new ErrorLog;
BeanB beanB = //Local lookup of Bean B
beanB.insertErrorLog (e);
}
...
}
BEAN B Code:
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class ErrorLogDAOBean
{
...
public void insertErrorLog (ErrorLog e)
{
merge (e);
}
...
}
I finally figured out the problem.
Here's the problem, when looking up the ErrorLogBean, i was instantiating a new Persistence Manager. When the transaction gets flagged for rollback, the process of getting a new PM was failing. I know it doesn't make sense to get a new Persistence Manager but it was part of test we are conducting.
Thanks Piotr for all your help in this!
Related
I need to create a Conditional Bean in Spring. The use case is as following:
Class 1
In this class we are trying to create the Bean, which should be created for some clients who have the required permission, and for others it will return empty(). Thus the application should boot-up for all the clients without the BeanCreationException
#org.springframework.context.annotation.Configuration
public class SomeBeanConfiguration {
#Bean
public Optional<SomeBean> someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
final String somePermission = whoAmI();
try {
return Optional.of(SomeBean.builder()
.withPermission(new SomeCredentialsProvider(somePermission))
.build());
} catch (Exception ex) {
LOG.error("SomeBean creation exception : ", ex);
}
return Optional.empty();
}
}
Class 2
Where we are using this Bean in Constructor injection
#Bean
public SomeHelper someHelper(Optional<SomeBean> someBean) {
return new someHelper(someBean);
}
But the someHelper for client, who have permission are also getting an Optional.empty() in constructor.
What I am doing wrong here? Can anyone please help?
You need to change your method that's creating the bean. It should not be returning a bean of type Optional, it should be returning a bean of type SomeBean. Also, consider rewriting your logic to something more understandable, like dropping the catch block and creating the bean based on the output of whoAmI().
#Bean
public SomeBean someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
String somePermission = whoAmI();
if (somePermission.equals("IAmClient_1") {
return SomeBean.builder().withPermission(newSomeCredentialsProvider(somePermission)).build());
} else {
return null;
}
}
Now, when you autowire the Optional, the optional will contain the bean for IAmClient_1, and will be empty for all other cases.
In my opinion, it would be better to always construct SomeBean and just modify its behavior based on the value of the permission you're checking, but that's up to you.
I have an application scoped bean as follows
#ApplicationScoped
public class Worker {
public void process(Long id) {
final Runnable runnable = () -> {
doATransaction(id);
};
executor.execute(runnable);
}
#Transactional
public void doATransaction(Long id) {
User user = User.findById(id);
}
}
I am getting a javax.enterprise.context.ContextNotActiveException. I also tried adding the doATransaction() to another Bean, which was injected in this Worker, as suggested here. Still had the same problem.
Does anyone have an idea of what I could do next?
The Exception I am getting is
Exception in thread "pool-13-thread-3" javax.enterprise.context.ContextNotActiveException
at io.quarkus.arc.impl.ClientProxies.getDelegate(ClientProxies.java:40)
at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder_ClientProxy.arc$delegate(RequestScopedSessionHolder_ClientProxy.zig:42)
at io.quarkus.hibernate.orm.runtime.RequestScopedSessionHolder_ClientProxy.getOrCreateSession(RequestScopedSessionHolder_ClientProxy.zig:160)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.acquireSession(TransactionScopedSession.java:103)
at io.quarkus.hibernate.orm.runtime.session.TransactionScopedSession.find(TransactionScopedSession.java:168)
at io.quarkus.hibernate.orm.runtime.session.ForwardingSession.find(ForwardingSession.java:68)
at io.quarkus.hibernate.orm.panache.common.runtime.AbstractJpaOperations.findById(AbstractJpaOperations.java:173)
at com.example.User.findById(User.java)
I am registering transaction managers in my code, I would normally use annotation based configuration but as I don't know until runtime how many data sources (and hence transaction managers) there will be, I have to programmatically register these, as follows:
private final void registerTransactionManagerBean(final DataSource dataSource, ConfigurableApplicationContext context) {
String transactionManagerName = this.getName() + "-transactionManager";
context.getBeanFactory().registerSingleton(transactionManagerName, new DataSourceTransactionManager(dataSource));
LOG.info("Registering transaction manager under name : " + transactionManagerName);
}
Assuming this.getName() returned 'mydb', I originally expected to be able to qualify a transaction manager like this:
#Transactional("mydb-transactionManager")
What I've realised however is the value of that annotation refers to the qualifier and not the name. I did a quick test by declaring a bean as below and it works:
#Bean
#Qualifier("mydb-transactionManager")
public PlatformTransactionManager test() {
return new DataSourceTransactionManager(new EmbeddedDatabaseBuilder().build());
}
My question is, is there a way I can programmatically add a qualifier when registering a bean?
UPDATE
I've worked this out, I'm falling foul of this problem (in BeanFactoryAnnotationUtils:isQualifierMatch):
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programmatically register transaction managers as a runtime thing sadly.
I've worked this out, I'm falling foul of this problem:
catch (NoSuchBeanDefinitionException ex) {
// ignore - can't compare qualifiers for a manually registered singleton object
}
I am manually registering my transaction manager bean so I presume this is why I'm stuck. I'm not really sure what options that gives me apart from to not programatically register transaction managers as a runtime thing sadly.
Raised as a JIRA issue - https://jira.spring.io/browse/SPR-11915
public class RuntimeRegistrationWithQualifierTest {
private AnnotationConfigApplicationContext context;
#Test
public void beanWithQualifier() {
final GenericBeanDefinition helloBeanDefinition = new GenericBeanDefinition();
helloBeanDefinition.addQualifier(new AutowireCandidateQualifier(Hello.class));
final GenericBeanDefinition worldBeanDefinition = new GenericBeanDefinition();
worldBeanDefinition.addQualifier(new AutowireCandidateQualifier(World.class));
final DefaultListableBeanFactory factory = context.getDefaultListableBeanFactory();
factory.registerBeanDefinition("helloBean", helloBeanDefinition);
factory.registerSingleton("helloBean", "hello");
factory.registerBeanDefinition("worldBean", worldBeanDefinition);
factory.registerSingleton("worldBean", "world");
context.register(Foo.class);
context.refresh();
final Foo foo = context.getBean(Foo.class);
assertThat(foo.hello).isEqualTo("hello");
assertThat(foo.world).isEqualTo("world");
}
#Before
public void newContext() {
context = new AnnotationConfigApplicationContext();
}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface Hello {}
#Qualifier
#Retention(RUNTIME)
#Target({FIELD, PARAMETER})
#interface World {}
static class Foo {
final String hello;
final String world;
Foo(#Hello final String hello, #World final String world) {
this.hello = hello;
this.world = world;
}
}
}
I have configured an application to work with MyBatis-Spring and I would like to connect to multiple databases.
For this purpose, in my applicationContext.xml I have defined one datasource, one Transaction Manager (org.springframework.jdbc.datasource.DataSourceTransactionManager), one Sql Session Factory (org.mybatis.spring.SqlSessionFactoryBean) and one MapperScannerConfigurer (org.mybatis.spring.mapper.MapperScannerConfigurer) for each one of them.
Then, inside my service class I would like to perform CRUD operations with multiple databases inside the same method. As I must point to the correct transaction manager I have done what is commented below:
#Service("myServiceDB")
public class MyServiceDB implements MyService {
[...]
#Transactional(value = "TransactionManager1", rollbackFor = MyCustomException.class)
public MyUser multipleMethod(final int idUser) throws MyCustomException {
MyUser myUser = null;
int rowsAffected1 = -1;
int rowsAffected2 = -1;
try {
myUser = this.mapperOne.getById(idUser);
if (myObject != null) {
rowsAffected1 = this.mapperOne.deleteByIdUser(idUser);
}
if (rowsAffected1 == 1) {
insertUser(myUser);
}
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
if ((myUser == null) || (rowsAffected1 != 1)) {
throw new MyCustomException(TRANSACTION_ERROR);
}
return myUser;
}
#Transactional(value = "TransactionManager2", rollbackFor = MyCustomException.class)
public void insertUser(final MyUser myUser) throws MyCustomException{
int rowsAffected = -1;
try {
rowsAffected = this.mapperTwo.insert(myUser);
**throw new MyCustomException();**
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
//if (rowsAffected != 1) {
// throw new MyCustomException(TRANSACTION_ERROR);
//}
}
[...]
}
So each method points to its corresponding transaction manager.
If I throw the custom exception in the second method after the insert, I get the delete made in the first method correctly rolled back. However, the insert performed by the second Transaction Manager is not rolled back properly as I would desire. (i.e. the user is inserted in the second database but not deleted in the first one).
My questions are:
Is it possible to achieve what I want?
How should I configure the #Transactional annotation?
Thanks in advance.
I found the solution here by #RisingDragon:
"If you are calling it from another local method then it will not work, because spring has no way of know that it is called and to start the transaction.
If you are calling it from a method of another class by using autowired object of the class which contains insertNotes() method, then it should work."
In my case, I created a second class (e.g. RisingDragom´s NoteClass) with some #Transactional methods (e.g. insertUser in my code) and then, the rollback worked!! This second class appeared in the debugger with the tail "$$EnhancedByCGLib".
However, if you need a method with several steps in different databases another "custom" rollback should be applied...The rollback is just applied method by method, not for the full process, so surely some data should be restored "by hand" in case of failure in any of the steps.
The following code does not help in roll back even if I throw null pointer exception at update() method. Everytime it inserts values into the database if I run the code. Please help me how can I roll back the transaction if null pointer is thrown at update() method. Am I missing something in the code?
#TransactionManagement(value = TransactionManagementType.CONTAINER)
public class Bean implements RemoteIF {
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertIntoDb() {
insert();
update();
}
private Integer update() {
val=0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val1 = st.executeUpdate("INSERT INTO tab VALUES('ab')");
st.close();
throw new NullPointerException();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
private Integer insert() {
int val = 0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val = st.executeUpdate("INSERT INTO tab VALUES('bnm')");
st.close();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
}
Couple things that stick out at me as suspect.
No #Stateless, #Stateful or #Singleton annotation on the Bean class. Unless you've declared the bean in an ejb-jar.xml file, then this is not getting recognized as an EJB. Definitely double check that.
The DbConn.getConnection() looks suspiciously like you might be trying to manage database connections yourself. If you have any code that uses the DriverManager or does new FooDataSource(), then that is definitely the problem. If you want transaction management to work you have to get all resources from the container via either
Injection via a #Resource DataSource datasource field in the EJB class
JNDI lookup of java:comp/env/yourDataSource, where yourDataSource is the name of a datasource you configured in the ejb-jar.xml or declared on the bean class via using #Resource(type=DataSource.class, name="youDataSource") -- that annotation goes on the class itself rather than a method or field.
See also these answers for some insight as to how transaction management works:
How does UserTransaction propagate?
Programming BMT - UserTransaction