Exclude class from ComponentScan of SpringBoot - java

I have two classes out of which I want to use only one class at run-time (depending on if I am running tests or executing the app on a server(local or otherwise)) and exclude the other from Spring's Component Scanning.
Here the 1st class which I want to use when testing:
public class HibernateUtilForH2 implements HibernateUtil {
private static SessionFactory sessionFactory;
static {
try {
Configuration configuration = new Configuration().configure("hibernate.cfg.xml.h2");
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(builder.build());
} catch (Exception ex) {
throw new ExceptionInInitializerError(ex);
}
}
public Session openSession() {
return sessionFactory.openSession();
}
}
Here's the second class for usage during production or local execution:
public class HibernateUtilForMySql implements HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
Configuration configuration = new Configuration().configure();
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(builder.build());
} catch (Exception ex) {
throw new ExceptionInInitializerError(ex);
}
}
HibernateUtil here is an interface containing declaration of openSession() method only.
I want HibernateUtilForH2 to be injected using DI when testing and HibernateUtilForMySql to be used for production or execution on a local server. How do I do that?
I've tried using #TestComponent and #ConditionalOnWebApplication but neither seems to work. I need a solution that is compatible with GitLab's CI/CD setup so that deployments can be smooth and hassle-free.

You could work with profiles.
Annotate your integration test with #ActiveProfiles("test") and your component that should be loaded for integration tests with #Profile("test") and the Components that should not be loaded for integration tests with #Profile("!test")

Related

Spring boot Mybatis multiple datasource

I am using Spring boot 2.0.3 and mybatis with PostgreSql.
I am trying to set up multiple data source connection as follows by following https://programmer.help/blogs/spring-boot-integrates-mybatis-multiple-data-sources.html.
Datasource1
#Configuration
#MapperScan(basePackages = "com.repositories.StaRepository", sqlSessionFactoryRef = "sqlPromptSessionFactory", annotationClass = Mapper.class)
//SqlSessionFactory is created from DB1 and then a SqlSessionTemplate is created from the created SqlSessionFactory.
public class MyBatisConfigPrompt {
#Bean(name = "DB1")
#ConfigurationProperties(prefix = "spring.datasource.pro")
public DruidDataSource DB1() {
return DruidDataSourceBuilder.create().build();
}
#Bean(name = "sqlProSessionFactory")
SqlSessionFactory sqlProSessionFactory() {
SqlSessionFactory sessionFactory = null;
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(DB1());
sessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}
#Bean
public MapperScannerConfigurer proMapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.repositories.StaRepository");
configurer.setSqlSessionFactoryBeanName("sqlProSessionFactory");
return configurer;
}
}
Datasource2
#Configuration
#MapperScan(basePackages = "com.repositories.ContDBRepository", sqlSessionFactoryRef = "sqlContSessionFactory", annotationClass = Mapper.class)
//SqlSessionFactory is created from contDB and then a SqlSessionTemplate is created from the created SqlSessionFactory.
public class MyBatisConfigCont {
#Bean(name = "contDB")
#ConfigurationProperties(prefix = "spring.datasource.cont")
public DruidDataSource contDB() {
return DruidDataSourceBuilder.create().build();
}
#Bean(name = "sqlContSessionFactory")
SqlSessionFactory sqlContSessionFactory() {
SqlSessionFactory sessionFactory = null;
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(contDB());
sessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}
#Bean
public MapperScannerConfigurer contMapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.repositories.ContDBRepository");
configurer.setSqlSessionFactoryBeanName("sqlContSessionFactory");
return configurer;
}
}
I have also a ContDBRepository.class with #Mapper Annotation and ContDBRepository.xml and same as StaRepository.class with #Mapper Annotation and StaRepository.xml in same package.
With the above configuration i am getting ERROR
No qualifying bean of type 'org.apache.ibatis.session.SqlSessionFactory' available: expected single matching bean but found 2: sqlContSessionFactory,sqlProSessionFactory
As a fix to the above error i set #Primary to one of the SqlSessionFactory but other SqlSessionFactory is never called when i want to use second datasource.
Can anyone help what i am missing.
UPDATE 20210301
This example help me to find a solution for makeing sure I could use the specific datasource.
The basic idea is to create a abstract data source as the router giving to the mybatis config. Then use a enum and #interface as the selector and adding them before any interface you want a specific data source. Finally AOP is the program paradigm to define how to change the data source.
Some key points:
AbstractRoutingDataSource will be the key to store our whole datasources.
#interface will be the key to create our router for our different ServiceImpl with specific interface, which will not need #Repository anymore, by adding that to any interface you override with the specific data source type.
#Aspect and #Pointcut will be the key to guarantee our router will work properly.
ORIGINAL
I found the same question for most example online about multiple data source.
The example you saw is not checked very much because he only use two localhost with same database info and table info.
The config example used:
spring.datasource.one.url=jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf-8
spring.datasource.one.username=root
spring.datasource.one.password=123456
spring.datasource.one.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.two.url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
spring.datasource.two.username=root
spring.datasource.two.password=123456
spring.datasource.two.type=com.alibaba.druid.pool.DruidDataSource
All the example I tried, all failed at get table from wrong database, which actually means the #Repository only can get one DataSource or config.

How can I get EntityManager in BaseDao (Maven+JSF+hibernate)

I have JSF maven project with Hibernate. There are some DAO classes in project, but it have failed implementation I think.
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
try {
// Create the SessionFactory from standard (hibernate.cfg.xml)
// config file.
Configuration configuration = new Configuration().configure();
StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().
applySettings(configuration.getProperties());
sessionFactory = configuration.buildSessionFactory(builder.build());
} catch (Throwable ex) {
// Log the exception.
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
In each DAO I call this method
Session mySession = HibernateUtil.getSessionFactory().openSession();
And After that doing transactions.
Now I want create generic BaseDAO class and create base CRUD operations in it. But I need get EntityManager. How can I getEntityManager in my BaseDao?
In spring I do it:
public class BaseJpaDao<E> implements BaseDao<E>{
protected Class<?> entityClass;
#PersistenceContext(unitName = "mainDataBase")
private EntityManager entityManager;
public BaseJpaDao(Class<?> entityClass) {
this.entityClass = entityClass;
}
#Override
public E persist(E e) {
entityManager.persist(e);
return e;
}
But how do it in not spring project?
Use Hibernates factory methods:
// Use persistence.xml configuration
EntityManagerFactory emf = Persistence.createEntityManagerFactory("mainDataBase")
EntityManager em = emf.createEntityManager();
// Retrieve an application managed entity manager
// Work with the EM
em.close();
Taken from the docs.

Can't inject Hibernate SessionFactory in ApplicationListener

I am building a SpringBoot application and I am trying to programatically populate my test database.
I came up with this:
#Profile("dev")
#Component
public class DatabaseFillerOnStartup implements ApplicationListener<ContextRefreshedEvent> {
#Resource
private SomeRepository someRepository;
#Resource //This doesn't work
private SessionFactory sessionFactory;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
...
One of my entities has a Blob where I want to save an image:
private Blob createBlobWithSampleImage() {
InputStream imageStream = this.getClass().getClassLoader().getResourceAsStream("sample1.jpg");
LobCreator lobCreator = Hibernate.getLobCreator(sessionFactory.getCurrentSession());
try {
return lobCreator.createBlob(IOUtils.toByteArray(imageStream));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
The problem is that I can't manage to inject the sessionFactory.
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException
Is there a better way to achieve what I want?
You're not showing where you've configured your session factory. Are you using spring-boot-starter-data-jpa or are you wiring up the SessionFactory yourself in an #Configuration annotated class or via standard xml bean config?
Edit:
Based on that answer check out this stackoverflow answer.

Spring-boot - add hibernate mapping file to entity manager

I'm migrating legacy app to Spring-boot and have to integrate an hibernate named query mapping file (previously configured in persitence.xml file).
I've come out with a solution with an
...
#Autowired
private DataSource dataSource;
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
//...
sessionFactoryBean.setMappingResources("META-INF/named-queries.hbm.xml");
return sessionFactoryBean;
}
But i'm ending having an entityManager bean and a sessionFactory bean in my application!
is it a good solution according to you?
Is there a way to add somehow the hibernate mapping file (named-query.hbm.xml) to the entityManager without using the sessionFactory bean?
Thanks in advance for you suggestions
** EDIT **
fro JB Nizet's suggestion, also come up with another solution
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
// ...
entityManagerFactory.setMappingResources("META-INF/named-queries.hbm.xml");
return entityManagerFactory;
}
and in my DAO/Service, i can still get hibernate session with:
private Session getSession() {
//return this.sessionFactory.getCurrentSession();
return this.entityManager.unwrap(Session.class);
}
But if someone nows if we can do the same thing with spring-boot auto-config with properties, it's welcomed!
Put the *.hbm.xml files under the src/main/resources folder and Spring Boot can automatically scan for them.
If you want to specify the location in the application.properties file, define them to the spring.jpa.mapping-resources attribute.
spring.jpa.mapping-resources=hibernate/MyMapping.hbm.xml,hibernate/MyMapping2.hbm.xml
Tested in SpringBoot 2.1.3, following is the folder structure
src/main/resources/hibernate : Store all the *.hbm.xml files
src/main/resources/application.properties : define the spring boot properties
And if you want to get the hibernate session in your Dao classes, define them as follows:
#Repository
#Transactional
public class XxxDao {
#Autowired
private EntityManager entityManager;
private Session getSession() {
return entityManager.unwrap(Session.class);
}
...
}
#Autowired
private ResourceLoader rl;
#Bean
public LocalSessionFactoryBean sessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setMappingLocations(loadResources());
}
public Resource[] loadResources() {
Resource[] resources = null;
try {
resources = ResourcePatternUtils.getResourcePatternResolver(rl)
.getResources("classpath:/hibernate/*.hbm.xml");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return resources;
}

Hibernate with more than one dataBase

I need to use hibernate with 2 or more bases. Since these bases have the same table, I imagine that there will be no problem when using the same objects paras tables that repeat. So being, I wonder how to spend other files on HibernateUtil establishing the connection, using different files hibernate.cfg.xml, but with the same mapping properties.
I imagine it's something along these lines:
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
but i dont have idea how to configure a other file.
The thing is to have 2 different session factory providers, each one configured with its corresponding data base.
In the example below HibernateUtilDb1 will use the configuration in hibernate-db1.cfg.xml, and HibernateUtilDb2 will use hibernate-db2.cfg.xml.
public class HibernateUtilDb1 {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
return new Configuration().configure("hibernate-db1.cfg.xml").buildSessionFactory();
}
catch (Throwable ex) { ...}
}
...
And:
public class HibernateUtilDb2 {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
return new Configuration().configure("hibernate-db2.cfg.xml").buildSessionFactory();
}
catch (Throwable ex) { ...}
}
...

Categories

Resources