bitronix transaction manager - spring

I'm trying to migrate from JPA to JTA and use bitronix transaction manager. I'm getting below error message when try to run unit tests. According to bitronix documentation this is normal b/c my spring context configuration is trying to load the resources twice (once in base class and then in the test class, see code below), I have tried the same with atomikos and I got similar result.
Caused by:
java.lang.IllegalArgumentException:
resource with uniqueName 'xyzDb'
has already been registered
My base class
#ContextConfiguration(locations = {"classpath:com/xyz/baseContext.xml"})
#Transactional
public abstract class AbstractTestSupport extends Assert implements ApplicationContextAware
{
In some unit tests I have to extend the test support and add a context config file like below. so it loads context once for base class and another time for child class and fails
Child class
#ContextConfiguration(locations = {"classpath:com/xyz/testContext.xml"})
public class UnitTest extends AbstractTestSupport
{
After the test I'm shutting down context, so next test works fine as long as it doesn't extend the base class with another context config file.
#AfterClass
public static void onTearDownAfterClass() throws Exception
{
applicationContext.shutdownApplicationContext();
assertFalse("Spring application context is still active after shutdown. ", applicationContext.isActive());
}
I want to keep context config files in the child classes and make this work like that, any ideas greatly appreciated....

The error message basically means you created the connection pool with unique name 'xyzDb' (remember there is a uniqueName property you need to set on BTM's pools?) for the second time at the time the exception is thrown. You cannot do that: each connection pool must have a unique name and must be closed before another one with an identical name can be created.
I suppose there is some overlap between your two context files causing this or maybe the connections pools aren't always closed like they should. Unfortunately you published too little information to get a definitive answer.

Related

how to Resolve "could not initialize proxy - no session" error when using Spring repository

I'm working on a mutitenant project it maintains different schema for each tenant, followed Project
As we are dynamically switching the tenants so it looks like some configuration is missed which is closing the session or not keeping the session open to fetch the LAZY loaded objects. Which results in "could not initialize proxy - no session" error.
Please check below link to access the complete project and db schema scripts, please follow the steps given in Readme file.
Project
It will be helpful if someone can point out the issue in the code.
i tried to put service methods in #Transactional annotation but that didn't work.
I'm expecting it to make another call to the LAZY loaded object, This project is simplefied verson of the complex project, actually i have lot more lazy loaded objects.
Issue:-
I'm getting no Session error "could not initialize proxy [com.amran.dynamic.multitenant.tenant.entity.Tenant#1] - no Session"
at line 26 (/dynamicmultitenant/src/main/java/com/amran/dynamic/multitenant/tenant/service/ProductServiceImpl.java)
The issue is that your transaction boundaries are not correct. In TenantDatabaseConfig and MasterDatabaseConfig you've correctly added #EnableTransactionManagement, which will setup transactions when requested.
However - the outermost component that has an (implicit) #Transactional annotation is the ProductRepository (by virtue of it being implemented by the SimpleJpaRepository class - which has the annotation applied to it - https://github.com/spring-projects/spring-data-jpa/blob/864c7c454dac61eb602674c4123d84e63f23d766/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java#L95 )
and so your productRepository.findAll(); call will start a transaction, create a JPA session, run the query, close the session, close the transaction, which means that there is no longer any transaction / session open in which to perform the lazy-loading.
Therefore, your original attempt of
i tried to put service methods in #Transactional annotation but that didn't work.
IS the correct thing to do.
You don't say exactly what you tried to do, and where, but there are a few things that could have gone wrong. Firstly, make sure you're adding a org.springframework.transaction.annotation.Transactional and not a javax.transaction.Transactional annotation.
Secondly (and the more likely problem in this scenario), you'll need to configure the annotation with which transaction manager the transaction should be bound to, otherwise it may use an existing / new transaction created against the master DB connection, not the tenant one.
In this case, I think that:
#Service
#Transactional(transactionManager = "tenantTransactionManager")
public class ProductServiceImpl implements ProductService {
should work for you, and make all the methods of the service be bound to a transaction on the tenant DB connection.
EDIT: Answering a follow-up question:
can you please also suggest a better way to inject my tenantTransactionManager in all my service classes, as I don't want to mention tenantTxnManger in all service classes if there is any better way to do it ?
Yes, sure. You can create a meta-annotation that applies multiple other annotations, so you could create:
/**
* Marks class as being a service operating on a single Tenant
*/
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Service
#Transactional("tenantTransactionManager")
public #interface TenantService {
}
and then you can simply annotate your service classes with #TenantService instead of #Service:
#TenantService
public class ProductServiceImpl implements ProductService {

Unable to read values from external property file in Spring Boot

I have a running Spring Boot project. I want to read some environment specific properties from an external properties file.
I mentioned config files names and locations while starting the server as follows:
java -jar AllergiesConditions.jar --spring.config.name=application,metadata --spring.config.location=classpath:/,/APPS/SpringBoot/
The property files loads successfully(because i tried to log one of the external key values inside datasource bean and It printed successfully) But when i try to access a value using #Value annotation - It returns null.
My test Class is as follows:
#Component
public class testclass {
private Logger logger = LogManager.getLogger(testcla.class);
#Value("${sso.server}")
public String sso;
public void test(){
logger.info("sso url is: "+sso); //This sso is logged as null
otherStuff();
}
}
This test() function is called when a particular API is hit after server is running.
The external config file - metadata.properties contains this variable:
sso.server=1234test
Edit: As suggested in this apparently duplicate question I also tried adding #PropertySource(name = "general-properties", value = { "classpath:path to your app.properties"}) in main Application configuration class and It loaded the files, but still I get null value itself.
Can someone please help in what's going wrong here?? Does the testclass need some specific annotation OR it needs to be a bean or something??
Thanks in Advance :)
Thanks to M.Deinum for great input and saving my time
Just posting his comment as answer
Factually ${sso.server} cannot be null. If ${sso.server} couldn't be resolved, my application will break at startup itself.
So the obvious problem was that I was creating a new instance of testclass in my controller using
testclass obj = new testclass(); obj.test();
Rather I should be using spring managed instance by autowiring testclass in my controller.

Spring boot AMQP and Spring Hadoop together ends up with missing EmbeddedServletContainerFactory bean

I have two small apps, one uses spring-boot-starter-amqp, other uses spring-data-hadoop-boot. I can run them separately without any problems.
When I join them together, app start fails with exception: org.springframework.context.ApplicationContextException: Unable to start EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory bean.
My main class is pretty much generic and it works fine for both of them separately:
#PropertySource("file:conf/app.properties")
#SpringBootApplication
public class Job {
public static void main(String[] args) throws Exception {
SpringApplication.run(Job.class, args);
}
}
I am at lost here. AFAIK #SpringBootApplication contains all annotations needed, including auto configuration and components scanning. I've had no need to configure web environment as I am not using it. Why do I need to do it when both dependencies are in class path, and how do I fix it?
UPDATE
I dug a little bit in the Spring Boot code. Main problem is that SpringApplication.deduceWebEnvironment() automatically detects what kind of environment should be configured based on existence of certain classes in class path.
For web environment two classes are being checked. When both of them are in class path, web environment is detected which requires proper configuration, obviously.
javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext
spring-boot-starter-amqp:1.3.1.RELEASE contains ConfigurableWebApplicationContext, and spring-data-hadoop-boot:2.3.0.RELEASE-cdh5 contains Servlet (in native Hadoop libs).
Now, when run alone, one of above classes is missing in both cases, which results in web environment not being set.
But when I use both of them - both classes can be found. Web environment is detected, false positive, and it requires configuration, which I am not able (and don't want) to provide.
So question now is - can I force non web environment, even when I have those classes in class path? Or is there any other way to solve the issue? (other than excluding them from Gradle dependencies)
Solved.
Following this question: How to prevent spring-boot autoconfiguration for spring-web?
I run application as follows.
#PropertySource("file:conf/app.properties")
#SpringBootApplication
public class Job {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(Job.class).web(false).run(args);
}
}
Answers to above question also suggested to use property spring.main.web_environment=false or annotation #EnableAutoConfiguration(exclude = WebMvcAutoConfiguration.class). Both solutions haven't worked for me. Only programmatic solution works in my case.

Bitronix + Spring tests + Different spring profiles

I have several tests which all extends the same root test which define the Spring test application context. One of my test use a different profile so I have annotated the child class with #ActiveProfiles("specialTestProfile"), this profile create a special mock bean which is injected in the context. I want to clear my context before and after executing this test, but I didn't find the correct way to do it. I know that the Spring test framework does some context caching and that in my case I should have two different context and it should not be necessary to reload the context but it is not working because of bitronix which generate this strange error if I don't clean the context:
Caused by: bitronix.tm.resource.ResourceConfigurationException: cannot create JDBC datasource named unittestdb
at bitronix.tm.resource.jdbc.PoolingDataSource.init(PoolingDataSource.java:57)
at sun.reflect.GeneratedMethodAccessor404.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeCustomInitMethod(AbstractAutowireCapableBeanFactory.java:1608)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1549)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1479)
... 62 more
Caused by: java.lang.IllegalArgumentException: resource with uniqueName 'unittestdb' has already been registered
at bitronix.tm.resource.ResourceRegistrar.register(ResourceRegistrar.java:55)
at bitronix.tm.resource.jdbc.PoolingDataSource.buildXAPool(PoolingDataSource.java:68)
at bitronix.tm.resource.jdbc.PoolingDataSource.init(PoolingDataSource.java:53)
... 68 more
Even if I reload the context for each test class (by annotating my parent class with #DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS), I still get the error above at some point... do you have any idea how to solve this problem?
Without seeing your exact configuration for the PoolingDataSource, I cannot know exactly how to solve your issue.
However, it appears that you can likely solve this issue by creating your PoolingDataSource with a unique name by invoking the setUniqueName() method (in an #Bean method if you're using Java config) or by setting the uniqueName property (if you're using XML config). How you generate the unique name depends on the configuration style you are using.
If you do not set a unique name for each ApplicationContext that creates the PoolingDataSource bean, you will continue to see the exception telling you that a second pool cannot be created with the "unittestdb" name since it already exists. The reason is that the init() method in PoolingDataSource delegates to ManagementRegistrar.register() which registers an MBean under the unique name, and the same MBeanServer is used for all tests within the same JVM process (i.e., for all tests in your suite).
Instead of generating a unique pool name per application context, another option might be to disable the use of JMX by setting the bitronix.tm.disableJmx property to false. Consult the isDisableJmx() and setDisableJmx() methods in bitronix.tm.Configuration for details.

Problems with #Transactional and JerseyTest on Linux or Mac but not on Windows

We use jersey test with grizzly2 to run acceptence tests against mocked REST resources. On my windows machine everything's fine. But another developer with his Mac is getting the same error as our Jenkins (on Linux):
INFO: Creating Grizzly2 Web Container configured at the base URI http://localhost:9998/
02.08.2012 09:46:36 org.glassfish.grizzly.http.server.HttpServer start
SEVERE: Failed to start listener [NetworkListener{name='grizzly', host='localhost', port=9998, secure=false}] : java.net.BindException: Address already in use
Obviously we checked that this is not the case: No other process is using 9998..
I've been tracking the problem down to a single test , that is using #Transactional in combination with extending JerseyTest:
#ContextConfiguration({ "classpath:context-test.xml" })
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#TransactionConfiguration(defaultRollback = true)
public class TestPage extends JerseyTest {
public TestPage() throws Exception {
super("", "", "path.to.package");
}
// init database to be rolled back after test
// ...
// test that calls a controller requiring database access and then sends a request to a mock REST resource.
// ...
}
So, the questions here are: Why not on windows? And what's wrong with using #Transactional.
edit2
It seems #Transactional is creating a proxy, this might be a problem..?! See here
I ran into a similar issue, make sure that you are not overRiding the tearDown() method in your TestPage test. If you have something to tear down, do it using a another method customTearDown() and annotate it with #After.
JerseyTest also uses the same method name tearDown() and the container never gets shut down if we override it.

Resources