SpringBoot configurate two jndi data sources - spring-boot

I use springboot to configure two datasources.
First:
#Bean
#Primary
#ConfigurationProperties(prefix = "datasource_app")
public DataSource appDataSource(){
if(config.getJndiName()!=null){
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
dataSourceLookup.setResourceRef(true);
return dataSourceLookup.getDataSource(config.getJndiName());
}
return DataSourceBuilder.create().build();
}
Second one
#Bean
#ConfigurationProperties(prefix = "datasource_domain")
public DataSource domainDataSource(){
if(config.getJndiName()!=null){
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(config.getJndiName());
}
return DataSourceBuilder.create().build();
}
But when i run application i get this exception:
Caused by: javax.management.InstanceAlreadyExistsException: Catalina:type=DataSource,host=localhost,context=/dir-master,class=javax.sql.DataSource,name="jdbc/dir"
at com.sun.jmx.mbeanserver.Repository.addMBean(Repository.java:437)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerWithRepository(DefaultMBeanServerInterceptor.java:1898)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerDynamicMBean(DefaultMBeanServerInterceptor.java:966)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerObject(DefaultMBeanServerInterceptor.java:900)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.registerMBean(DefaultMBeanServerInterceptor.java:324)
at com.sun.jmx.mbeanserver.JmxMBeanServer.registerMBean(JmxMBeanServer.java:522)
at org.springframework.jmx.support.MBeanRegistrationSupport.doRegister(MBeanRegistrationSupport.java:195)
at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:670)
at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:615)
... 25 more
What am i doing wrong? Thanks in advance

By default, Spring Boot will attempt to register with JMX any beans in the application context that are MBeans. That causes a problem here as Tomcat has already registered the JNDI DataSource as an MBean.
Spring Boot's own JndiDataSourceAutoConfiguration avoids the problem by telling the application context's MBeanExporter to not export the MBean:
private void excludeMBeanIfNecessary(Object candidate, String beanName) {
try {
MBeanExporter mbeanExporter = this.context.getBean(MBeanExporter.class);
if (JmxUtils.isMBean(candidate.getClass())) {
mbeanExporter.addExcludedBean(beanName);
}
}
catch (NoSuchBeanDefinitionException ex) {
// No exporter. Exclusion is unnecessary
}
}
You can avoid the problem by doing similar in your own configuration class.

Related

Hibernate JNDI lookup for WebLogic is throwing unable to resolve

I have configured a datasource configured on Oracle Weblogic 12.1.3 and trying to lookup this datasource in a Spring MVC application.
// Weblogic configuration
Datasource Name in weblogic : Admin Data Source
JNDI Name : bsh/AdminDS
// Spring MVC Configuration
#Bean
public DataSource getDataSourceUsingJndi() throws NamingException {
return (DataSource) new JndiTemplate().lookup("bsh/AdminDS"); // 1st way
}
#Bean
public LocalSessionFactoryBean sessionFactory() throws NamingException {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSourceUsingJndi());
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put(AvailableSettings.DIALECT, hibernateDialect);
properties.put(AvailableSettings.SHOW_SQL, showSql);
properties.put(AvailableSettings.FORMAT_SQL, formatSql);
return properties;
}
I am getting the below stacktrace:
Caused by: javax.naming.NameNotFoundException: Unable to resolve 'bsh.AdminDS'. Resolved 'bsh'; remaining name 'AdminDS'
at weblogic.jndi.internal.BasicNamingNode.newNameNotFoundException(BasicNamingNode.java:1180)
at weblogic.jndi.internal.BasicNamingNode.lookupHere(BasicNamingNode.java:270)
at weblogic.jndi.internal.ServerNamingNode.lookupHere(ServerNamingNode.java:187)
at weblogic.jndi.internal.BasicNamingNode.lookup(BasicNamingNode.java:210)
at weblogic.jndi.internal.BasicNamingNode.lookup(BasicNamingNode.java:224)
at weblogic.jndi.internal.WLEventContextImpl.lookup(WLEventContextImpl.java:253)
at weblogic.jndi.internal.WLContextImpl.lookup(WLContextImpl.java:426)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at org.springframework.jndi.JndiTemplate.lambda$lookup$0(JndiTemplate.java:156)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:91)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:156)
at com.bsh.config.WebConfig.getDataSourceUsingJndi(WebConfig.java:68)
I tried different ways to lookup - 2nd way
public DataSource getDataSourceUsingJndi() {
JndiDataSourceLookup jndiLookup = new JndiDataSourceLookup();
jndiLookup.setResourceRef(true);
return jndiLookup.getDataSource("bsh/AdminDS");
}
Is there anything wrong with my configuration ?

Spring Batch Multiple DataSource Session/Entitymanager Closed

I am trying to Pull Records from SQLServer Database to Persist into Mysql Using Spring Boot and Sprin Batch(JpaPagingItemReader and JpaItemWriter).
I have configured Multiple Datasources.
How ever i am facing with below error.
org.springframework.batch.item.ItemStreamException: Error while closing item reader
at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:138)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:337)
at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:271)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:957)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:964)
at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1041)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1017)
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:967)
at org.springframework.batch.core.launch.support.CommandLineJobRunner.start(CommandLineJobRunner.java:377)
at org.springframework.batch.core.launch.support.CommandLineJobRunner.main(CommandLineJobRunner.java:597)
at net.com.org.batch.MyApplication.main(MyApplication.java:15)
Caused by: java.lang.IllegalStateException: Session/EntityManager is closed
at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:344)
at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:137)
at org.hibernate.internal.AbstractSharedSessionContract.checkOpenOrWaitingForAutoClose(AbstractSharedSessionContract.java:350)
at org.hibernate.internal.SessionImpl.close(SessionImpl.java:413)
at org.springframework.batch.item.database.JpaPagingItemReader.doClose(JpaPagingItemReader.java:232)
at org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader.close(AbstractItemCountingItemStreamItemReader.java:135)
... 17 common frames omitted
20:10:55.875 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Retrieved dependent beans for bean 'jpaMappingContext': [mySqljobRepository, sqlServerLogsRepository]
Below is my Batch,Step Configuration
#Autowired
private StepBuilderFactory stepBuilderFactory;
#Autowired
#Qualifier("mysqlEntityManager")
private LocalContainerEntityManagerFactoryBean mysqlLocalContainerEntityManagerFactoryBean;
#Autowired
#Qualifier("secondarySqlEntityManager")
private LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean;
#Autowired
#Qualifier("mysqlTransactionManager")
private PlatformTransactionManager mySqlplatformTransactionManager;
#Autowired
#Qualifier("secondaryTransactionManager")
private PlatformTransactionManager secondaryTransactionManager;
#Autowired
private JobBuilderFactory jobBuilderFactory;
#Bean
public JpaPagingItemReader itemReader(PlatformTransactionManager secondaryTransactionManager) {
JpaPagingItemReader<SqlServerJobLogs> serverJobLogsJpaPagingItemReader = new JpaPagingItemReader<>();
serverJobLogsJpaPagingItemReader.setMaxItemCount(1000);
serverJobLogsJpaPagingItemReader.setPageSize(100);
serverJobLogsJpaPagingItemReader.setEntityManagerFactory(localContainerEntityManagerFactoryBean.getNativeEntityManagerFactory());
serverJobLogsJpaPagingItemReader.setQueryString("select p from SqlServerJobLogs p");
return serverJobLogsJpaPagingItemReader;
}
#Bean
public ItemProcessor itemProcessor() {
return new DataItemProcessor();
}
#Bean
public ItemWriter itemWriter(PlatformTransactionManager mySqlplatformTransactionManager) {
DataWriter dataWriter = new DataWriter();
return dataWriter;
}
#Bean
public Step step() {
return stepBuilderFactory.get("myJob").chunk(100).reader(itemReader(secondaryTransactionManager)).processor(itemProcessor()).writer(itemWriter(mySqlplatformTransactionManager)).build();
}
#Bean(name = "myJob")
public Job myJob() throws Exception {
return jobBuilderFactory.get("myJob").start(step()).build();
}
#Bean
public ResourcelessTransactionManager resourcelessTransactionManager(){
return new ResourcelessTransactionManager();
}
#Bean
public JobRepository jobRepository() throws Exception{
MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean = new MapJobRepositoryFactoryBean(resourcelessTransactionManager());
return mapJobRepositoryFactoryBean.getObject();
}
#Bean
public SimpleJobLauncher jobLauncher() throws Exception {
SimpleJobLauncher simpleJobLauncher = new SimpleJobLauncher();
simpleJobLauncher.setJobRepository(jobRepository());
return simpleJobLauncher;
}
I have Tried to Configure BatchConfigurer.But No luck.
Please let me know if i need to configure anything else apart from the above mentioned details
Thanks in Advance
I would like to answer the question.
A big thanks to #MahmoudBenHassine
Providing a custom transaction manager by overriding
DefaultBatchConfigurer#getTransactionManager works only with Spring
Batch v4.1+ as said in comments. We need to use Spring Boot v2.1 to
have Spring Batch 4.1

Quartz JDBCJobStore with RoutingDataSource

For my application, we are using the spring's
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
The target dataSources are configured and chosen based on request's domain URL.
Eg:
qa.example.com ==> target datasource = DB1
qa-test.example.com ==> target datasource = DB2
Following is the configuration for the same
#Bean(name = "dataSource")
public DataSource dataSource() throws PropertyVetoException, ConfigurationException {
EERoutingDatabase routingDB = new EERoutingDatabase();
Map<Object, Object> targetDataSources = datasourceList();
routingDB.setTargetDataSources(targetDataSources);
return routingDB;
}
public class EERoutingDatabase extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
// This is derived from the request's URL/Domain
return SessionUtil.getDataSourceHolder();
}
}
The task is now using Quartz JDBCJobStore to store the quartz jobs/triggers.
The preferred option is using JobStoreCMT.
We used the following config
#Configuration
public class QuartzConfig {
private static final Logger LOG = LoggerFactory.getLogger(QuartzConfig.class);
private static final String QUARTZ_CONFIG_FILE = "ee-quartz.properties";
#Autowired
private DataSource dataSource;
#Autowired
private PlatformTransactionManager transactionManager;
#Autowired
private ApplicationContext applicationContext;
/**
* Spring wrapper over Quartz Scheduler bean
*/
#Bean(name="quartzRealTimeScheduler")
SchedulerFactoryBean schedulerFactoryBean() {
LOG.info("Creating QUARTZ Scheduler for real time Job invocation");
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setConfigLocation(new ClassPathResource(QUARTZ_CONFIG_FILE));
factory.setDataSource(dataSource);
factory.setTransactionManager(transactionManager);
factory.setJobFactory(springBeanJobFactory());
factory.setWaitForJobsToCompleteOnShutdown(true);
factory.setApplicationContextSchedulerContextKey("applicationContext");
return factory;
}
#Bean
public SpringBeanJobFactory springBeanJobFactory() {
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
jobFactory.setApplicationContext(applicationContext);
jobFactory.setIgnoredUnknownProperties("applicationContext");
return jobFactory;
}
}
and following is the config in quartz properties file (ee-quartz.properties)
org.quartz.scheduler.instanceId=AUTO
org.quartz.jobStore.useProperties=false
org.quartz.jobStore.misfireThreshold: 60000
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
On starting the application, following exception occurs
Caused by: java.lang.IllegalStateException: Cannot determine target DataSource for lookup key [null]
at org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.determineTargetDataSource(AbstractRoutingDataSource.java:202) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at com.expertly.config.EERoutingDatabase.determineTargetDataSource(EERoutingDatabase.java:60) ~[EERoutingDatabase.class:na]
at org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.getConnection(AbstractRoutingDataSource.java:164) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(DataSourceUtils.java:111) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:77) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:289) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.jdbc.support.JdbcUtils.extractDatabaseMetaData(JdbcUtils.java:329) ~[spring-jdbc-4.1.6.RELEASE.jar:4.1.6.RELEASE]
at org.springframework.scheduling.quartz.LocalDataSourceJobStore.initialize(LocalDataSourceJobStore.java:149) ~[spring-context-support-4.0.1.RELEASE.jar:4.0.1.RELEASE]
at org.quartz.impl.StdSchedulerFactory.instantiate(StdSchedulerFactory.java:1321) ~[quartz-2.2.2.jar:na]
at org.quartz.impl.StdSchedulerFactory.getScheduler(StdSchedulerFactory.java:1525) ~[quartz-2.2.2.jar:na]
at org.springframework.scheduling.quartz.SchedulerFactoryBean.createScheduler(SchedulerFactoryBean.java:599) ~[spring-context-support-4.0.1.RELEASE.jar:4.0.1.RELEASE]
at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:482) ~[spring-context-support-4.0.1.RELEASE.jar:4.0.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1612) ~[spring-beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1549) ~[spring-
beans-4.0.1.RELEASE.jar:4.0.1.RELEASE]
It seems that
Quartz is trying to create connections with my datasource upfront.
Since my dataSource isn't concrete one (its routing dataSource) and in addition doesn't have knowledge to which target Db to connect (at config time), it fails
Do we have any provision, where quartz can be used with RoutingDataSource? If Not, what would be the next best thing?
Ideally you can try making SchedulerFactoryBean as #Lazy.
But It seems lazy initialization will not work bug, there is also a work around listed in the comments.
Create schedulerFactory bean dynamically after
ContextRefreshedEvent received on root context.
Let us know, If this works.

multiple datasources in Spring Boot application

I'm trying to using two database connections w/in a Spring Boot (v1.2.3) application as described in the docs (http://docs.spring.io/spring-boot/docs/1.2.3.RELEASE/reference/htmlsingle/#howto-two-datasources.
Problem seems to be that the secondary datasource is getting constructed with the properties for the primary datasource.
Can someone point out what I'm missing here?
#SpringBootApplication
class Application {
#Bean
#ConfigurationProperties(prefix="spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public JdbcTemplate secondaryJdbcTemplate(DataSource secondaryDataSource) {
return new JdbcTemplate(secondaryDataSource)
}
#Bean
#Primary
#ConfigurationProperties(prefix="spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "primaryJdbcTemplate")
public JdbcTemplate jdbcTemplate(DataSource primaryDataSource) {
return new JdbcTemplate(primaryDataSource)
}
static void main(String[] args) {
SpringApplication.run Application, args
}
}
application.properties:
spring.datasource.primary.url=jdbc:oracle:thin:#example.com:1521:DB1
spring.datasource.primary.username=user1
spring.datasource.primary.password=
spring.datasource.primary.driverClassName=oracle.jdbc.OracleDriver
spring.datasource.secondary.url=jdbc:oracle:thin:#example.com:1521:DB2
spring.datasource.secondary.username=user2
spring.datasource.secondary.password=
spring.datasource.secondary.driverClassName=oracle.jdbc.OracleDriver
Both JdbcTemplate beans will be getting created with the primary DataSource. You can use #Qualifier to have the secondary DataSource injected into the secondary JdbcTemplate. Alternatively, you could call the DataSource methods directly when creating the JdbcTemplate beans.

Spring Boot Using Embedded Tomcat with JNDI

I am using Spring Boot with Embedded Tomcat and attempting to use JNDI but getting the following error:
javax.naming.NameNotFoundException: Name [jdbc/dataSource]
Any tips would be greatly appreciated.
Here is my code:
#Configuration
public class TomcatJndiConfiguration{
#Value("${database.driver}")
private String driverClassName;
#Value("${database.url}")
private String databaseUrl;
#Value("${database.username}")
private String databaseUsername;
#Value("${database.password}")
private String databasePassword;
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
ContextResource resource = new ContextResource();
resource.setName("jdbc/dataSource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", driverClassName);
resource.setProperty("url", databaseUrl);
resource.setProperty("password", databaseUsername);
resource.setProperty("username", databasePassword);
context.getNamingResources().addResource(resource);
}
};
}
#Bean
public DataSource dataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("jdbc/dataSource");
bean.setLookupOnStartup(true);
bean.setProxyInterface(DataSource.class);
bean.setResourceRef(true);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
Stacktrace is:
Caused by: javax.naming.NameNotFoundException: Name [jdbc/dataSource] is not bound in this Context. Unable to find [jdbc].
at org.apache.naming.NamingContext.lookup(NamingContext.java:818)
at org.apache.naming.NamingContext.lookup(NamingContext.java:166)
at org.apache.naming.SelectorContext.lookup(SelectorContext.java:157)
at javax.naming.InitialContext.lookup(InitialContext.java:417)
at org.springframework.jndi.JndiTemplate$1.doInContext(JndiTemplate.java:155)
at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:87)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152)
at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179)
at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95)
at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106)
at org.springframework.jndi.JndiObjectTargetSource.afterPropertiesSet(JndiObjectTargetSource.java:97)
at org.springframework.jndi.JndiObjectFactoryBean$JndiObjectProxyFactory.createJndiObjectProxy(JndiObjectFactoryBean.java:318)
at org.springframework.jndi.JndiObjectFactoryBean$JndiObjectProxyFactory.access$000(JndiObjectFactoryBean.java:307)
at org.springframework.jndi.JndiObjectFactoryBean.afterPropertiesSet(JndiObjectFactoryBean.java:200)
at com.kronos.daas.configuration.TomcatJndiConfiguration.dataSource(TomcatJndiConfiguration.java:72)
You need to set lookupOnStartup to false on the JndiObjectFactoryBean.
Alternatively, if you need the lookup to work during startup, then this answer may be of interest.
Edit: you've also set the JNDI name on your JndiObjectFactory bean incorrectly. It needs to be java:comp/env/jdbc/myDataSource not jdbc/dataSource.
You use a different name when you're looking up the resource versus when you registered it as the registration automatically places the resource beneath java:comp/env/.
If you are using spring boot, no need for all of that class.
It is already configured in #EnableAutoConfiguration or
#SpringBootApplication
Just put the following in your application.properties file or equivalent in application.yml file
spring.datasource.driverClassName=JDBCDriverClassName
spring.datasource.url=databaseUrl
spring.datasource.username=databaseUsername
spring.datasource.password=databasePassword
spring.datasource.jndi-name=java:jdbc/dataSource

Resources