Access Datasource inside Bean method injected at other Bean Method - java

In an spring boot project, is there a way to use injected object inside a #Bean method. In my example following, isdatasourceUse() method able to acccess injected Datasource (either from dev or war profile)
#EnableScheduling
#Configuration
#EnableAspectJAutoProxy
#Profile({ "dev", "war" })
public class AppConfig {
Logger logger = LoggerFactory.getLogger(AppConfig.class);
#Autowired
DBPropertyBean dbPropertyBean;
#Bean(destroyMethod = "")
#Profile("war")
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName(dbPropertyBean.getJndiName());
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
#Bean(destroyMethod = "close")
#Profile("dev")
public DataSource getDataSource() throws Exception {
com.mchange.v2.c3p0.ComboPooledDataSource ds = new com.mchange.v2.c3p0.ComboPooledDataSource();
ds.setUser(dbPropertyBean.getDsUsername());
ds.setPassword(dbPropertyBean.getDsPassword());
ds.setJdbcUrl(dbPropertyBean.getDsJdbcUrl());
ds.setDriverClass(dbPropertyBean.getDsDriverClass());
ds.setMaxPoolSize(dbPropertyBean.getDsMaxPoolSize());
ds.setMinPoolSize(dbPropertyBean.getDsMinPoolSize());
ds.setInitialPoolSize(dbPropertyBean.getDsInitPoolSize());
ds.setAcquireIncrement(dbPropertyBean.getDsAcquireInc());
ds.setAcquireRetryAttempts(dbPropertyBean.getDsAcquireRetryAtt());
ds.setPreferredTestQuery(dbPropertyBean.getPreferredTestQuery());
ds.setIdleConnectionTestPeriod(dbPropertyBean.getIdleConnectionTestPeriod());
return ds;
}
#Bean
public void datasourceUse() {
//How to user datasource here
}
}

Use it like below:
#Autowired
public void datasourceUse(DataSource dataSource) {
System.out.println(dataSource);
}

Related

My Injected sessionFactory instance is null

When I inject my sessionFactory Bean using Java based configuration for Hibernate my bean is null and I don't know why. I've scoured the internet for answers but couldn't find any. I've looked over my configuration and compared it against guides online. Any answers are greatly appreciated.
Here's the exact error
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "org.hibernate.SessionFactory.getCurrentSession()" because "com.example.demo.DemoApplication.sessionFactory" is null
Here's my code
HibernateConfiguration File, Annotation based.
#Configuration
#EnableTransactionManagement
public class HibernateConfig {
#Bean
#Scope //By default the scope is singleton which means that the IOC will only create a single instance of the bean and return that one reference for subsequent calls for that bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan( packagesToScan()); //Model packages to scan
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
//Direct Physical Connection Information
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/demo");
dataSource.setUsername("root");
dataSource.setPassword("password");
return dataSource;
}
#Bean
public PlatformTransactionManager hibernateTransactionManager() {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
//List of Entities to scan
#Bean
public String [] packagesToScan() {
return new String [] { "com.example.demo.Entities.Student" };
}
//Configures properties of our hibernate configuration, dialect,
private final Properties hibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "create");
hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
hibernateProperties.setProperty("show_sql", "true");
hibernateProperties.setProperty("current_session_context_class", "thread");
return hibernateProperties;
}
}
My Main application where I'm attempting to inject my Session Factory singleton bean for use.
#SpringBootApplication
public class DemoApplication {
#Autowired
static SessionFactory sessionFactory;
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AnimalConfig.class, HibernateConfig.class); // Makes the sessionFactory bean known to the IOC
Session currentSession = sessionFactory.getCurrentSession();
(( ConfigurableApplicationContext )ctx).close(); //Close the applicationContext
SpringApplication.run(DemoApplication.class, args);
}
}
The container gets started when SpringApplication.run invokes. I think before all of this an injection and the usage of that bean does not makes sense. First you need to fire Springapplication and then the rest of the business logic.
Your bean isn't scanned and initialize at the moment of the injection.
Possible solution :
#Component
public class IOCAfterInitializationListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
static SessionFactory sessionFactory;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Session currentSession = sessionFactory.getCurrentSession();
//do whatever you want
}
}

Prevent Spring container from performing injection on provided Bean

My goal is to use Spring Batch with different instances of DataSource for my ItemWriter and the JobRepository respectively which should work like this.
Unfortunately the Spring container injects the primary datasource at a later stage which I can confirm via debugger. Here's my configuration:
#RunWith(SpringJUnit4ClassRunner.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#SpringBootTest(classes = { BatchTest.DatabaseConfig.class, BatchTest.BatchTestConfig.class })
public class BatchTest {
#Configuration
static class DatabaseConfig {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create()
.build();
}
#Bean
#ConfigurationProperties("spring.secondaryDatasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create()
.build();
}
}
#Configuration
#EnableBatchProcessing
static class BatchTestConfig {
#Bean()
BatchConfigurer configurer(#Qualifier("secondaryDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
}
}
I reckon this is due to the setter-injection defined in
package org.springframework.batch.core.configuration.annotation;
#Component
public class DefaultBatchConfigurer implements BatchConfigurer {
#Autowired(required = false)
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.transactionManager = new DataSourceTransactionManager(dataSource);
}
}
So now I'm wondering how above mentioned SO response works or rather doesn't work in my case. Can I somehow disable the additional setter-injection on the provided bean?
Ttry to override DefaultBatchConfigurer#setDataSource and add the qualifier to the setDataSource method:
#Bean()
BatchConfigurer configurer(#Qualifier("secondaryDataSource") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource) {
#Autowired(required = false)
public void setDataSource(#Qualifier("secondaryDataSource") DataSource dataSource) {
super.setDataSource(dataSource);
}
};
}
I agree it's a bit odd, but it's odd too that spring batch has such a constraint.
You could even try to override without any annotation at all. I don't remember if Spring searches annotation too in the class hiearchy.

Multiple transaction managers NoUniqueBeanDefinitionException

I'm using Spring boot and I defined the spring.datasource.* properties to enable my datasource. If I only use this it works fine. However, I'm now trying to add JMS to my application as well, using the following config:
#Configuration
#EnableJms
public class TriggerQueueConfig {
private Logger logger = LoggerFactory.getLogger(getClass());
#Value("${jms.host:localhost}")
private String host;
#Value("${jms.port:1414}")
private int port;
#Value("${jms.concurrency.min:3}-${jms.concurrency.max:10}")
private String concurrency;
#Value("${jms.manager}")
private String queueManager;
#Value("${jms.cache:100}")
private int cacheSize;
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory() throws JMSException {
logger.debug("Setting queue concurrency to {} (min-max)", concurrency);
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(cachedConnectionFactory());
factory.setMessageConverter(messageConverter());
factory.setTransactionManager(transactionManager());
factory.setSessionTransacted(true);
factory.setConcurrency(concurrency);
return factory;
}
#Bean(name = "jmsTransactionManager")
public JmsTransactionManager transactionManager() throws JMSException {
JmsTransactionManager transactionManager = new JmsTransactionManager();
transactionManager.setConnectionFactory(cachedConnectionFactory());
return transactionManager;
}
#Bean
#Primary
public ConnectionFactory cachedConnectionFactory() throws JMSException {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(ibmConnectionFactory());
connectionFactory.setSessionCacheSize(cacheSize);
connectionFactory.setCacheConsumers(true);
return connectionFactory;
}
#Bean
public ConnectionFactory ibmConnectionFactory() throws JMSException {
logger.debug("Connecting to queue on {}:{}", host, port);
MQQueueConnectionFactory connectionFactory = new MQQueueConnectionFactory();
connectionFactory.setHostName(host);
connectionFactory.setPort(port);
connectionFactory.setQueueManager(queueManager);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
return connectionFactory;
}
#Bean
public MessageConverter messageConverter() {
MarshallingMessageConverter converter = new MarshallingMessageConverter();
converter.setMarshaller(marshaller());
converter.setUnmarshaller(marshaller());
return converter;
}
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setPackagesToScan("com.example");
return marshaller;
}
}
The JMS listener I created is working fine. However, when I'm trying to persist data using my repository (Spring Data JPA) in a #Transactional method, I'm getting the following exception:
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: transactionManager,jmsTransactionManager
This makes sense, because both transactionmanagers are PlatformTransactionManager's. Usually you would put #Primary on top of the bean that should be the default one. However, in this case I'm using Spring boot's autoconfiguration so I can't add the #Primary on it.
An alternative solution would be to provide the name of the transaction manager with each #Transactional annotation (for example #Transactional("transactionManager"), but this would be a lot of work, and it would make more sense to have a default transactionmanager because the JMS transactionmanager is an exceptional case.
Is there an easy way to define the automatically configured transactionmanager to be used by default?
The Spring boot 'magic' is really only this:
#Bean
#ConditionalOnMissingBean(PlatformTransactionManager.class)
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
in org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration class.
Notice the #ConditionalOnMissingBean annotation - this will get configured only if a bean of type PlatformTransactionManager doesn't exist. So you can override this by creating your own bean with #Primary annotation:
#Bean
#Primary
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}

Multiple DataSource and JdbcTemplate in Spring Boot (> 1.1.0)

I would like to inject a specific JdbcTemplatein a Spring Boot project. I tried to follow this example for multiple DataSourceconfiguration : http://spring.io/blog/2014/05/27/spring-boot-1-1-0-m2-available-now
My code does compile and run, but only the DataSource with the #Primaryannotation is taken into account, no matter what I put as #Qualifier in the SqlServiceclass. My relevant code is the following :
DatabaseConfig.java:
#Configuration
public class DatabaseConfig {
#Bean(name = "dsSlave")
#ConfigurationProperties(prefix="spring.mysql_slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "dsMaster")
#Primary
#ConfigurationProperties(prefix="spring.mysql_master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "jdbcSlave")
#Autowired
#Qualifier("dsSlave")
public JdbcTemplate slaveJdbcTemplate(DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}
#Bean(name = "jdbcMaster")
#Autowired
#Qualifier("dsMaster")
public JdbcTemplate masterJdbcTemplate(DataSource dsMaster) {
return new JdbcTemplate(dsMaster);
}
}
And I did a quick service to try it out :
SqlService.java:
#Component
public class SqlService {
#Autowired
#Qualifier("jdbcSlave")
private JdbcTemplate jdbcTemplate;
public String getHelloMessage() {
String host = jdbcTemplate.queryForObject("select ##hostname;", String.class);
System.out.println(host);
return "Hello";
}
}
It should looks like this:
#Bean(name = "jdbcSlave")
#Autowired
public JdbcTemplate slaveJdbcTemplate(#Qualifier("dsSlave") DataSource dsSlave) {
return new JdbcTemplate(dsSlave);
}
Try to move #Qualifier annotation to the parameter on your #Bean methods for JdbcTemplate.
I guess, when you remove #Primary you end up with error, where more than one appropriate beans are presented

Get JNDI resources programatically in spring

I have a Tomcat servlet container that has a list of DataSources managed by Tomcat's connection pool. From my Spring application (Spring 3.2.3) I would like to get one of these datasources on runtime, something like:
public class MyService {
#Autowired
private JndiObjectLocator jndiLocator;
public void myMethod(String jndiName) {
DataSource myDataSource = jndiLocator.locate(jndiName);
}
}
Any ideas on how to do that?
You can always do a JNDI lookup in your code, you can use the JndiDataSourceLookup for that and call the getDataSource() method.
public class MyService {
#Autowired
private JndiDataSourceLookup lookup;
public void myMethod(String jndiName) {
DataSource myDataSource = lookup.getDataSourcejndiName);
}
}
Another option would be to make your bean aware of the BeanFactory and retrieve the DataSource from there.
public class MyService {
#Autowired
private BeanFactory factory;
public void myMethod(String jndiName) {
DataSource myDataSource = factory.getBean(jndiName, DataSource.class);
}
}

Categories