Java Spring boot, Spring batch, multi datasource circular references error - java

I have a little test project to config several data sources on a spring batch, boot application.
As far as I see is ok, but I always have the jpaMappingContext does not support circular references error. .
This is my dependencies. .
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
My database config
#Configuration
public class DatabaseConfiguration {
#Bean
#DependsOn({"entityManagerFactory"})
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory);
return jpaTransactionManager;
}
#Bean
#DependsOn({"dataSource"})
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
result.setPackagesToScan("juanjo.domain.entity");
result.setDataSource(dataSource);
HibernateJpaVendorAdapter obj = new HibernateJpaVendorAdapter();
obj.setDatabasePlatform("org.hibernate.dialect.PostgreSQLDialect");
obj.setShowSql(true);
obj.setGenerateDdl(false);
result.setJpaVendorAdapter(obj);
return result;
}
#Bean(name = "dataSourceMaster", destroyMethod = "close")
#ConfigurationProperties(prefix = "spring.datasource.db-1")
public DataSource dataSourceMaster() {
return DataSourceBuilder.create().build();
}
#Bean(name = "dataSourceOrigin", destroyMethod = "close")
#ConfigurationProperties(prefix = "spring.datasource.db-O")
public DataSource dataSourceOrigin() {
return DataSourceBuilder.create().build();
}
#Bean(name = "dataSourceReplica", destroyMethod = "close")
#ConfigurationProperties(prefix = "spring.datasource.db-2")
public DataSource dataSourceReplica() {
return DataSourceBuilder.create().build();
}
#Bean()
#DependsOn({"dataSourceMaster", "dataSourceOrigin", "dataSourceReplica"})
public DataSource dataSource() {
RoutingDataSource result = new RoutingDataSource();
Map<Object, Object> params = new HashMap<Object, Object>();
params.put(DbType.MASTER, dataSourceMaster());
params.put(DbType.REPLICA1, dataSourceReplica());
params.put(DbType.ORIGIN, dataSourceOrigin());
result.setDefaultTargetDataSource(dataSourceMaster());
result.setTargetDataSources(params);
result.afterPropertiesSet();
return result;
}
}
My job:
#Configuration
public class CountryBatch {
#Autowired
private CountryWriter countryWriter;
...
}
#Component
public class CountryWriter implements ItemWriter<CountryDTO> {
#Autowired
private CountrySplit countrySplit;
...
}
#Component
public class CountrySplit {
#Autowired
private CountryRepository repository;
...
}
public interface CountryRepository extends JpaRepository<Country, Long>
{
}
And the error:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error
creating bean with name 'countryBatch': Unsatisfied dependency
expressed through field 'countryWriter'; nested exception is org.
springframework.beans.factory.UnsatisfiedDependencyException: Error
creating bean with name 'countryWriter': Unsatisfied dependency
expressed through field 'countrySplit'; nested exception is
org.springframework.beans.factory.UnsatisfiedDependencyException: Error
creating bean with name 'countrySplit': Unsatisfied dependency
expressed through field 'repository'; nested exception is
org.springframework.beans.factory.BeanCreationException: Error creating
bean with name 'countryRepository': Cannot resolve reference to bean
'jpaMappingContext' while setting bean property 'mappingContext';
nested exception is
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'jpaMappingContext':
org.springframework.beans.factory.FactoryBeanNotInitializedException: org.springframework.data.jpa.repository.config.JpaMetamodelMappingContextFa
ctoryBean$$EnhancerBySpringCGLIB$$24da9e51 does not support circular
references
I couldn't see the circular dependencies problem.

Related

How to configure multi-modules Spring Data JPA so that beans are found

I'm starting a multi-modules Spring Data JPA project.
I'm having the following exception when running a unit test :
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.citizenweb.webmarket.dao.objects.ProductTest': Unsatisfied dependency expressed through field 'productRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.citizenweb.webmarket.dao.objects.repositories.ProductRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
Here's the global design :
marketapp
|__ dao (module)
|__ model (module)
In the marketapp there's the MarketappApplication.class :
#SpringBootApplication(scanBasePackages = "com.citizenweb.webmarket")
#EnableJpaRepositories(basePackages = {"com.citizenweb.webmarket.dao.objects.repositories"})
#EntityScan(basePackages = {"com.citizenweb.webmarket.model.objects.entities"})
public class MarketappApplication {
public static void main(String[] args) {
SpringApplication.run(MarketappApplication.class, args);
}
}
In the model module, I've created a simple entity : Product.class
In the dao module, I've created the ProductRepository.class interface :
#Repository("productRepository")
#Qualifier("productRepository")
public interface ProductRepository extends org.springframework.data.jpa.repository.JpaRepository<Product, Long> {
}
In the dao module, I also have added a unit test :
#SpringBootTest(classes = {ProductRepository.class})
public class ProductTest {
#Autowired
private ProductRepository productRepository;
#Test
public void persistProduct() {
Product product = new Product();
product.setProductName("Mon Produit");
productRepository.save(product);
}
}
Because of the exception, I've tried many things inspired by a lot of posts from SOF, Baeldung, Spring.io (of course) :
I've added a DaoConfig.class in the dao module :
#SpringBootConfiguration
#EnableJpaRepositories(basePackages = {"com.citizenweb.webmarket.dao.objects.repositories"})
#EnableTransactionManagement
public class DaoConfig {
}
I've also added a DaoTestConfig.class in the dao module (test directory) :
#SpringBootConfiguration
public class DaoTestConfig {
}
I've also tried this :
#SpringBootConfiguration
#EnableJpaRepositories(basePackages = {com.citizenweb.webmarket.dao.objects.repositories"})
#EnableTransactionManagement
public class DaoConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.citizenweb.webmarket.dao.objects.repositories");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
System.out.println("EntityManager = " + em.getDataSource());
return em;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/marketplace");
dataSource.setUsername( "xxxxx" );
dataSource.setPassword( "xxxxx" );
System.out.println("Database = " + dataSource.getUrl());
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
return properties;
}
}
The result is always the same.
Is there something obvious I don't see ?

How can I lazy create the EntityManagerFactory using Spring Data ORM/JPA?

I'm creating a standalone Java application that uses Spring Data with JPA.
Part of the class that creates the factory for the EntityManagerFactory is below:
#Configuration
#Lazy
public class JpaConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(MultiTenantConnectionProvider connProvider, CurrentTenantIdentifierResolver tenantResolver) {
...
}
The problem is: I can only detect the Hibernate Dialect after the ApplicationContext is initialized, because this information is read from an external configuration service.
Since #Lazy did not work, is there any strategy to avoid creating this bean before it is used, i.e, only create it when another bean injects an instance of EntityManager?
I stumbled upon this issue recently and found a solution that worked. Unfortunately "container" managed beans will be initialized during startup and #Lazy is ignored even if the EntityManager is not injected anywhere.
I fixed it by using an in-memory H2 DB to construct the factory bean during startup and changed it later. I think here's what you can do for your issue.
pom.xml:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
Source code:
#Configuration
public class DataSourceConfig {
#Bean
public HikariDataSource realDataSource() {
...
}
#Bean
public DataSource localH2DataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
}
#Bean
public LocalContainerEntityManagerFactoryBean myEntityManagerFactory() throws PropertyVetoException {
LocalContainerEntityManagerFactoryBean factoryBean = new LocalContainerEntityManagerFactoryBean();
factoryBean.setDataSource(localH2DataSource());
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setShowSql(true);
factoryBean.setJpaVendorAdapter(jpaVendorAdapter);
return factoryBean;
}
}
#Component
#Lazy
public class Main {
#Autowired
private LocalContainerEntityManagerFactoryBean emf;
#Autowired
private HikariDataSource realDataSource;
#PostConstruct
private void updateHibernateDialect() {
// read the external config here
emf.setDataSource(realDataSource);
Properties jpaProperties = new Properties();
jpaProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.DB2Dialect");
factoryBean.setJpaProperties(jpaProperties);
}
}

Running domain's tests in multi-module project with #SpringApplicationConfiguration throws NoSuchBeanDefinitionException?

I have my multi-module project structured like this:
project-example (parent)
-- project-example-domain: contains DAOs and Services
-- project-example-service: contains Controllers
Service module has domain as dependency, so service knows domain, but not the other way around.
So, for testing ClientDAO integrity (in this case), i have this test:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = DAOConfiguration.class)
public class ClientDAOTests {
#Autowired
private ClientDAO dao;
#Test
public void testFindClient() {
...
}
}
When i run it, i get this exception:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [javax.sql.DataSource] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1301)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1047)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:942)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:533)
... 51 more
And finally my DAOConfiguration
#Configuration
#PropertySource("classpath:persistence.properties")
#EnableJpaRepositories(basePackages = {"com.example.movies.domain.feature"})
#EnableTransactionManagement
public class DAOConfiguration {
#Autowired
private Environment env;
#Autowired
private DataSource dataSource;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setDataSource(this.dataSource);
entityManager.setPackagesToScan(new String[] {"com.example.movies.domain"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManager.setJpaVendorAdapter(vendorAdapter);
return entityManager;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
When i run the server from the service it works fine, Spring makes the DataSource bean. But when i run my test in domain, i get the exception mentioned before.
So i think that my problem is that i am passing incorrect argument to #SpringApplicationConfiguration. I don't understand what do i have to pass... should i pass it the Application class with the #SpringBootApplication annotattion? In this case, this is inside Service module so domain doesn't know this class. Can you explain what does the SpringApplicationConfiguration should take as parameter that is making my test fail? Thanks in advance!
EDIT
This is my ApplicationRunner class located in service layer:
#SpringBootApplication
#Import({ServicesConfiguration.class, CustomDataBindingConfiguration.class, CustomMappingConfiguration.class})
#PropertySource(value = {"application.properties"})
public class ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(ApplicationRunner.class, args);
}
}
I resolved the very same problem.
The problem comes from #SpringApplicationConfiguration(classes = DAOConfiguration.class).
You have to reference a class annotated with #EnableAutoConfiguration for datasource to be automatically injected by Spring Boot. (this annotation is generally inherited from #SpringBootApplication)
You have several options :
Reference your application class annotated with #SpringBootApplication from #SpringApplicationConfiguration
Add #EnableAutoConfiguration to DAOConfiguration class
... (you understand the point)

Spring - Java configuration #Bean parameter

I've just updated from Spring 3.1.1 to 3.2.6
With 3.1 the following code worked well:
#Bean(name = DEMO_DS)
public JndiObjectFactoryBean demoDataSource()
{
JndiObjectFactoryBean factory = new JndiObjectFactoryBean();
factory.setJndiName(JDBC_DEMO_DS);
factory.setProxyInterface(DataSource.class);
return factory;
}
#Bean(name = DEMO_SESSION_FACTORY)
public SqlSessionFactoryBean demoSqlSessionFactory(#Qualifier(DEMO_DS) DataSource dataSource)
{
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setConfigLocation(new ClassPathResource("demo/config.xml"));
return sessionFactory;
}
However with the uprgraded version I get the following exception:
Caused by:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type [javax.sql.DataSource] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for
this dependency. Dependency annotations:
{#org.springframework.beans.factory.annotation.Qualifier(value=DemoDataSource)}
I have multiple DataSources hence the #Qualifier is a need.
Thanks.
Edit:
It seems that this solves the problem:
public DataSource dataSourceFactory() {
try
{
return (DataSource) demoDataSource().getObject();
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
...
sessionFactory.setDataSource(dataSourceFactory());
However I don't think it's a nice solution.
Depending on your need rewrite your configuration a little. If you don't really need the datasource injected you can do something like this.
#Bean(name = DEMO_DS)
public JndiObjectFactoryBean demoDataSource() {
JndiObjectFactoryBean factory = new JndiObjectFactoryBean();
factory.setJndiName(JDBC_DEMO_DS);
factory.setProxyInterface(DataSource.class);
return factory;
}
#Bean(name = DEMO_SESSION_FACTORY)
public SqlSessionFactoryBean demoSqlSessionFactory() {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(demoDataSource().getObject());
sessionFactory.setConfigLocation(new ClassPathResource("demo/config.xml"));
return sessionFactory;
}
If you need to have a datasource injected you might want to switch to using a JndiLocatorDelegate to do the lookup instead of a JndiObjectFactoryBean.
#Bean(name = DEMO_DS)
public DataSource demoDataSource() throws NamingException {
return JndiLocatorDelegate.createDefaultResourceRefLocator().lookup(JDBC_DEMO_DS, DataSource.class);
}
This gives you a DataSource directly instead of a FactoryBean<Object> (which is what the JndiObjctFactoryBean is) what probably is the source of the problem.
Or (in theory) you should also be able to use a #Value annotation on a property in your config class. Instead of a #Value a normal #Resource should also do the trick (that can also delegate a call to JNDI for a lookup).
public class MyConfig {
#Value("${" + JDBC_DEMO_DS + "}")
private DataSource demoDs;
}
With #Resource
public class MyConfig {
#Resource(mappedName=JDBC_DEMO_DS)
private DataSource demoDs;
}
And you can then simply reference it in your configuration method.
#Bean(name = DEMO_SESSION_FACTORY)
public SqlSessionFactoryBean demoSqlSessionFactory() {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(demoDs);
sessionFactory.setConfigLocation(new ClassPathResource("demo/config.xml"));
return sessionFactory;
}

Unit testing a Spring Boot service class with(out) repository in JUnit

I am working on a spring boot based webservice with following structure:
Controller (REST) --> Services --> Repositories (as suggested in some tutorials).
My Database Connection (JPA/Hibernate/MySQL) is defined in a #Configuration class. (see below)
Now I'd like to write simple tests for methods in my Service classes, but I don't really understand how to load ApplicationContext into my test classes and how to mock the JPA / Repositories.
This is how far I came:
My service class
#Component
public class SessionService {
#Autowired
private SessionRepository sessionRepository;
public void MethodIWantToTest(int i){
};
[...]
}
My test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
public class SessionServiceTest {
#Configuration
static class ContextConfiguration {
#Bean
public SessionService sessionService() {
return new SessionService();
}
}
#Autowired
SessionService sessionService;
#Test
public void testMethod(){
[...]
}
}
But I get following exception:
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'sessionService': Injection of autowired
dependencies failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: private com.myApp.SessionRepository
com.myApp.SessionService.sessionRepository; nested exception is
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
qualifying bean of type [com.myApp.SessionRepository] found for
dependency: expected at least 1 bean which qualifies as autowire
candidate for this dependency. Dependency annotations:
{#org.springframework.beans.factory.annotation.Autowired(required=true)}
For completeness: here's my #Configuration for jpa:
#Configuration
#EnableJpaRepositories(basePackages={"com.myApp.repositories"})
#EnableTransactionManagement
public class JpaConfig {
#Bean
public ComboPooledDataSource dataSource() throws PropertyVetoException, IOException {
...
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
...
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
...
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
...
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
...
}
}
use #SpringBootTest and #RunWith(SpringRunner.class) to load the context
#RunWith(SpringRunner.class)
#SpringBootTest
class Demo{
#Test
void contextLoad(){}
}
In your test Spring will use configuration only from inner ContextConfiguration class. This class describes your context. In this context you created only service bean and no repository. So the only bean that will be created is SessionService. You should add description of SessionRepository in inner ContextConfiguration. Your JpaConfig class will not be used in test class(you don't specify this), only in application.

Categories