Same class for different spring bean instances - java

I have a Spring application that connects to two databases at the same time. So I have for this two LocalSessionFactoryBean instances for each connection like this:
#Bean
public LocalSessionFactoryBean firstSessionFactory() {
final LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
lsfb.setPackagesToScan("ro.mycompany.myproject.classes");
lsfb.setDataSource(dataSourceOne);
lsfb.setEntityInterceptor(auditInterceptor1);
lsfb.setHibernateProperties(getHibernateProperties1());
return lsfb;
}
#Bean
public LocalSessionFactoryBean secondSessionFactory() {
final LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
lsfb.setPackagesToScan("ro.mycompany.myproject.classes2");
lsfb.setDataSource(dataSourceTwo);
lsfb.setEntityInterceptor(auditInterceptor2);
lsfb.setHibernateProperties(getHibernateProperties2());
return lsfb;
}
For the DAO layer I have a class that injects the SessionFactory object like this.
public class GenericDAOImpl extends HibernateDAOSupport implements GenericDAO {
#Autowired
private SessionFactory sessionFactory;
//Other methods goes here
}
I instantiate the beans in my config file like this:
#Bean
public GenericDAO firstGenericDAO() {
final GenericDAOImpl genericDAO = new GenericDAOImpl();
return genericDAO;
}
#Bean
public GenericDAO secondGenericDAO() {
final GenericDAOImpl genericDAO = new GenericDAOImpl();
return genericDAO;
}
How can I make the firstGenericDAO to use firstSessionFactory and secondGenericDAO to use secondSessionFactory without creating the setters method? I want to use both connection at the same time so also Spring profiles won't help me.
Thank you

Either use #Qualifier("...") in addition to #Autowired or just use #Resource(name = "..."). Personally I prefer using #Resource as it replaces the two Annotations with the single one.
In your case, #Resource(name = "firstSessionFactory") and #Resource(name = "secondSessionFactory") respectively.

For you following code,
public class GenericDAOImpl extends HibernateDAOSupport implements GenericDAO {
#Autowired
private SessionFactory sessionFactory;
//Other methods goes here
}
Spring should be known clearly which bean would be autowired. That means, there should be setter method or some other variable to distinguish your sessionFactory1 and sessionFactory2.
Just as you said, xml-based is still power than annotation. If you don't want to use XML based, and don't want setter method either, I think sessionFactory can be initialized by another variable to identify which bean used.
For example,
public class GenericDAOImpl extends HibernateDAOSupport implements GenericDAO {
private SessionFactory sessionFactory;
public GenericDAOImpl(boolean tag) {
super();
ApplicationContext apx = new AnnotationConfigApplicationContext(xxxx.class);
sessionFactory = tag ? (SessionFactory) apx.getBean("sessionFactory1")
: (SessionFactory) apx.getBean("sessionFactory1");
}
}
Of course, you need to specify the bean name for LocalSessionFactoryBean with #Bean(name = "sessionFactory1") and #Bean(name = "sessionFactory2")

Related

Spring 5 + Hibernate 5 : How SessionFactory is getting Autowired with LocalSessionFactoryBean

I am developing REST apis using Spring MVC 5.0.8 and Hibernate 5.2.11
I have created AppConfig class,in which I have created getSessionFactory() method with return type LocalSessionFactoryBean
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setPackagesToScan("saptarsi.auditdb.model");
factoryBean.setHibernateProperties(hibernateProperties());
return factoryBean;
}
And Inside DaoImpl class I have autowired SessionFactory
#Repository
public class LOcaldbDaoImpl implements LocaldbDao {
#Autowired
private SessionFactory sessionFactory;
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
#Override
public void getAllApiDetails(HttpRequestEntity<ApiDetailsFilterDto> requestEntityDto) {
}
}
And everything is working fine
But I want to know how SessionFactory is getting autowired.
Because I am not returning factoryBean.getObject(),which is responsible to return SessionFactory type object.
And #Autowire will look for SessionFactory type in Bean factory.
So how Autowiring is happening ?
Because after you initialized the LocalSessionFactoryBean, the buildSessionFactory method was called. Link to calling.
protected SessionFactory buildSessionFactory(LocalSessionFactoryBuilder sfb) {
return (this.bootstrapExecutor != null ? sfb.buildSessionFactory(this.bootstrapExecutor) :
sfb.buildSessionFactory());
}
After that, SessionFactory bean will be in the ApplicationContext.
All Spring beans was load in ApplicationContext. Try to read here more https://docs.spring.io/spring/docs/1.2.x/reference/beans.html

Spring : Do I require to define separate TransactionManager for 2 data sources?

I am working on a Spring-MVC application in which I have 2 data-sources defined for different types of tasks. Currently, during migration from XML to Java, I stumbled upon a requirement to add a new HibernateTransactionManager object for #Transactional to work. For the 2 different data-sources, I have 2 separate SessionFactory instances. But when I try to create yet another instance of HibernateTransactionManager with secondary data-source, I get a non-unique exception.
Do I require a secondary HibernateTransactionManager instance for the config I am posting or will 1 suffice? If required, how can I create one? Thank you.
Error log :
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.springframework.transaction.PlatformTransactionManager] is defined: expected single matching bean but found 2: primary_tx,extended_tx
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:368)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:331)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:367)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:271)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springfram
WebConfig.java :
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.ourapp.spring"})
#EnableTransactionManagement
#EnableCaching
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
#Bean(name="extended_tx")
public HibernateTransactionManager txName() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(getExtendedSessionFactory().getObject());
return txName;
}
#Bean
#Qualifier("sessionFactory_origin")
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig().primaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
#Bean
#Qualifier("sessionFactory_extended")
public LocalSessionFactoryBean getExtendedSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(new DataSourceConfig_Extended().secondaryDataSource());
sessionFactory.setPackagesToScan("com.ourapp.spring");
return sessionFactory;
}
}
Typical Service Layer method :
#Service
#Transactional("primary_tx")
public class ChatRoomMembersServiceImpl implements ChatRoomMembersService{
private final ChatRoomMembersDAO chatRoomMembersDAO;
#Autowired
public ChatRoomMembersServiceImpl(ChatRoomMembersDAO chatRoomMembersDAO){
this.chatRoomMembersDAO = chatRoomMembersDAO;
}
}
Typical DAO layer method :
#Repository
#Transactional("primary_tx")
public class ChatRoomMembersDAOImpl implements ChatRoomMembersDAO{
#Autowired
#Qualifier(value = "sessionFactory_origin")
private SessionFactory sessionFactory;
#Autowired
#Qualifier(value = "sessionFactory_extended")
private SessionFactory sessionFactory_extended;
}
Now, whenever required, I am referring to the extended SessionFactory instance. Right now, I have annotated DAO layer methods requiring extended sessionFactory with secondary_tx, but it's not working. Thank you. :-)
The answer to your question
Do I require to define separate TransactionManager for 2 data sources?
is YES
Java8 onwards you can have same annotation(provided that the annotation is marked #Repeatable) multiple times on a method. Other way to accomplish this is to gave a custom annotation which takes care of one of the datasources.
The exception you are getting is because there are two qualifying beans for transation management. You need a Qualifier for this.
Instead of:
#Bean(name="primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Use
#Bean #Qualifier("primary_tx")
public HibernateTransactionManager getPrimaryTransactionManager() throws IOException {
HibernateTransactionManager txName= new HibernateTransactionManager();
txName.setSessionFactory(sessionFactory().getObject());
return txName;
}
Now, Let say You want to make the transactions in my method m1 as atomic. The following is what you need
#Transactional("primary_tx")
public void m1(){
}

Autowired object is getting null

I am using Spring Boot for my application. I am defining JNDI name in the application.properties file.
When I am trying to get JdbcTemplate in below class, its null:
#Configuration
public class DemoClass
{
#Autowired
private JdbcTemplate template;
#Bean
private DataSource getDS(){
return template.getDataSource(); //NPE
}
}
Another Class
#Component
public class SecondClass {
#Autowired
private JdbcTemplate template;
public void show(){
template.getDataSource(): // Working Fine
}
}
I am not sure this configured by default.. In case it is not, then maybe you can try configuring it yourself:
#Autowired
DataSoure dataSource;
#Bean
public JdbcTemplate getJdbcTemplate() {
return new JdbcTemplate(dataSource);
}
in any case if you need only the DataSource, I think it is auto-configured by Spring Boot so you can autowire it directly when you need it.
#Repository
public class DataRepository {
private JdbcTemplate jdbcTemplate;
#Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int updateCandidate() {
return this.jdbcTemplate.update("update .... from table where ....");
}
}
application.properties
database connection details
spring.datasource.url=jdbc:oracle:thin:***
spring.datasource.username=Scott
spring.datasource.password=Tiger
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.tomcat.initial-size=1
spring.datasource.tomcat.max-active=1
spring.datasource.tomcat.min-idle=1
spring.datasource.tomcat.max-idle=1
If you're getting a NPE at getDS. This means JdbcTemplate hasn't been injected yet, maybe it couldn't be injected.
Give spring a hint at bean dependencies by
#Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource)
}
Or
#Bean
#DependsOn("template")
public DataSouce getDS(){
return template.getDataSource();
}
By default #Autowired sets required=true so the DemoClass should not be constructed by Spring.
Most likely you are creating new DemoClass() or disabled the annotation config altogether and the DemoClass class is registered manually e.g. using XML.
Instead ensure that the DemoClass class is discovered using Spring's component scan e.g. using #SpringBootApplication or #ComponentScan e.g. as per this example.

Spring and Hibernate with multiple databases

Good evening,
what is the correct and common approach of handling two or more databases?
Consider this HibernateConfiguration class configuring only one datasource:
#Configuration #EnableTransactionManagement
#PropertySource(value = { "classpath:hibernate.properties" })
public class HibernateConfiguration {
#Autowired
private Environment env;
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// ... setting data source
return dataSource;
}
private Properties getHibernateProperties() {
Properties properties = new Properties();
// ... setting Hibernate properties
return properties;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setPackagesToScan(new String[] { "POJOs'" });
sessionFactory.setHibernateProperties(getHibernateProperties());
return sessionFactory;
}
#Bean public HibernateTransactionManager transactionManager(SessionFactory sf) {
HibernateTransactionManager htm = new HibernateTransactionManager();
htm.setSessionFactory(sf);
return htm;
}
}
Is recommended to let one class configure one datasource? Or is enough to configure all at once? How do I specify in Dao class which SessionFactory will be used and what is the recommended approach in case of switching two exact same databases on two different hosting servers?
The example DAOs. First I need to switch between Foo and Bar.
#Repository
public class RepositoryImpl implements RepositoryDao {
#Autowired // Here I need to switch between databases "foo" and "bar"
private SessionFactory sessionFactory;
...
The second one I need fixed on example database Foo.
#Repository
public class FooImpl implements FooDao {
#Autowired // Here I need fixed on "Foo"
private SessionFactory sessionFactory;
One approach
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSource fooDataSource() {
return fooDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("app.datasource.bar")
public BasicDataSource barDataSource() {
return (BasicDataSource) DataSourceBuilder.create()
.type(BasicDataSource.class).build();
}
Spring multiple datasources config
Other approach could be : loading different mapping (orm.xml) from persistence.xml or refer to different schemas in Entity classes.

transaction tutorial - test rollback

I am trying to learn how to use transaction in java spring. I am a java novice so please bear with me :-) The unit test im trying to achieve below is testing:
rollback if a runtime exception is thrown.
The problem im having is a
java.lang.NullPointerException?
Ok here goes.. I have stripped out some code to help improve readability
TutorialTest.java
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class TutorialTest {
#Autowired
private Dao dao;
#Test
public void that_if_a_runtime_exception_is_thrown_transaction_rolledback(){
User u = new User();
u.setUsername("FAIL_TEST");
u.setPhone("0161");
u.setEmail("FAIL_TEST#gmail.com");
//dao.addUser(u); // -- this will insert if uncommented out so I know it works
OuterService os = new OuterService();
os.addUserThrowError(u);
}
}
OuterService.java
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class OuterService {
#Autowired
private Dao dao;
#Transactional
public void addUserThrowError(User user) throws RuntimeException{
dao.addUser(user); // gives me a java.lang.NullPointerException?
throw new RuntimeException("This should roll back DB entry");
}
}
Beans are declared in
AppConfig.java
#Configuration
#EnableTransactionManagement
#ComponentScan(value = {"com.training.spring.tx.tutorial.dao",
"com.training.spring.tx.tutorial.service"})
public class AppConfig {
public DataSource dataSource() {
// Create a BasicDataSource object and configure database
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/spring_training_tx");
dataSource.setUsername("training");
dataSource.setPassword("training");
return dataSource;
}
#Bean
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
#Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(transactionManager().getDataSource());
}
}
First of all, to quote the javadoc of #ContextConfiguration
#ContextConfiguration defines class-level metadata that is used to
determine how to load and configure an ApplicationContext for
integration tests.
Consider how you are using it on OuterService. Does it seem right? Is OuterService meant to be used to load and configure an ApplicationCOntext for integration tests? Unless I'm missing something essential, the answer is: No.
So what is OuterService? It's some kind of service. You seem to want to use it as a bean. What is a bean? A bean is an object whose lifecycle is managed by Spring. This includes instantiation of the bean class, initialization of the object, post processing, and, finally, destruction of the object.
If you created the object like so
OuterService os = new OuterService();
then Spring is not involved. You created the object and there is no way for Spring to hook into that. You cannot therefore expect Spring to autowire its field
#Autowired
private Dao dao;
And since you haven't initialized the field, it remains null, which causes the NullPointerException.
So how do we get an OuterService bean, which is managed by Spring? You either declare a #Bean method for OuterService or you annotate OuterService with #Component or any of its specializations and component-scan the package it is in. You then inject the bean into any other bean that uses it. For example,
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = AppConfig.class, loader=AnnotationConfigContextLoader.class)
public class TutorialTest {
#Autowired
private Dao dao;
#Autowired
private OuterService os;
You can then use that variable directly.

Categories