spring-boot configure hibernate-ogm for cassandra [dataSource not found] - spring-boot

I am trying to configure hibernate-ogm for Cassandra on spring boot, but there are no dataSource are transparent provided to entityManager and below error with run:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a bean of type 'javax.sql.DataSource' that could not be found.
- Bean method 'dataSource' not loaded because #ConditionalOnProperty (spring.datasource.jndi-name) did not find property 'jndi-name'
- Bean method 'dataSource' not loaded because #ConditionalOnBean (types: org.springframework.boot.jta.XADataSourceWrapper; SearchStrategy: all) did not find any beans
Action:
Consider revisiting the conditions above or defining a bean of type 'javax.sql.DataSource' in your configuration.
Blow a workflow for case:
start with disable auto configuration for related jpa on spring:
#Configuration
#ComponentScan
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class
})
public class GsDataApplication {
public static void main(String[] args) {
SpringApplication.run(GsDataApplication.class, args);
}
}
and project dependencies are:
dependencies {
compile project(':shared')
compile('joda-time:joda-time:2.9.4')
//compile('org.hibernate:hibernate-core:5.2.2.Final')
compile('org.hibernate.ogm:hibernate-ogm-cassandra:5.0.1.Final')
compile('org.hibernate:hibernate-search-orm:5.5.4.Final')
compile('javax.transaction:jta:1.1')
compile('org.springframework.data:spring-data-jpa:1.10.4.RELEASE')
compile('org.springframework.boot:spring-boot-starter-social-facebook')
compile('org.springframework.boot:spring-boot-starter-social-twitter')
}
Persistence configuration:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.gs.gsData.identity"
})
#EnableTransactionManagement
class PersistenceContext {
#Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(Environment env) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan("com.gs.gsData.domain");
Properties jpaProperties = new Properties();
jpaProperties.put("javax.persistence.transactionType", "resource_local");
jpaProperties.put("hibernate.ogm.datastore.provider", "cassandra_experimental");
jpaProperties.put("hibernate.ogm.datastore.host", "127.0.0.1");
jpaProperties.put("hibernate.ogm.datastore.port", "9042");
jpaProperties.put("hibernate.ogm.datastore.database", "db-name");
jpaProperties.put("hibernate.ogm.datastore.username", "cassandra");
jpaProperties.put("hibernate.ogm.datastore.password", "password");
jpaProperties.put("hibernate.ogm.datastore.create_database", "true");
entityManagerFactoryBean.setPersistenceProviderClass(HibernateOgmPersistence.class);
entityManagerFactoryBean.setJpaProperties(jpaProperties);
return entityManagerFactoryBean;
}
#Bean
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
Entity sample
#Entity
#Data
#AllArgsConstructor
#Table(name = "`User`")
public class User extends AbstractTiming{
#Id #GeneratedValue
private Long id;
private String firstName, lastName, description;
public User(){}
}
ADO
#Repository
public interface UserDAO extends JpaRepository<User, Long> {
User findByFirstName(String firstName);
List<User> findByLastName(String lastName);
}
So in the end, is there way to create DataSource to reference Hibernate OGM?
or any other actions to avoid direct dataSource provider. Any help is greatly appreciated!

Hibernate OGM stores the connection in its own class: the org.hibernate.ogm.datastore.spi.DatastoreProvider.
So at start up it does not deal with DataSources, not sure if there is a way to create a DataSource from the cassandra connection and add it in the JNDI for springboot to find it.
The reason for this is that at the moment there is no standard among NoSQL datastores defining the connection details.

Related

SpringBoot - Websphere - Java Config - web.xml - resource-ref

I have a web application where I have 3 jndi resources defined in the web.xml
1 for the database and 2 for dyna cache
How can I convert it into Java configuration in Spring boot.
Following is the sample resource ref configuration in the application
<resource-ref>
<description>Resource reference for database</description>
<res-ref-name>jdbc/dbname</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<resource-ref id="cache1">
<description>cache1 description</description>
<res-ref-name>cache/cache1</res-ref-name>
<res-type>com.ibm.websphere.cache.DistributedMap</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
<resource-ref id="cache2">
<description>cache2 description</description>
<res-ref-name>cache/cache2</res-ref-name>
<res-type>com.ibm.websphere.cache.DistributedMap</res-type>
<res-auth>Container</res-auth>
<res-sharing-scope>Shareable</res-sharing-scope>
</resource-ref>
Thanks
Multiple Databases in Spring Boot
More on / Font: baeldung
Spring Boot can simplify the configuration above.
By default, Spring Boot will instantiate its default DataSource with the configuration properties prefixed by spring.datasource.*:
spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
We now want to keep using the same way to configure the second DataSource, but with a different property namespace:
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]
Because we want the Spring Boot autoconfiguration to pick up those different properties (and instantiate two different DataSources), we'll define two configuration classes similar to the previous sections:
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.user",
entityManagerFactoryRef = "userEntityManager",
transactionManagerRef = "userTransactionManager")
public class PersistenceUserAutoConfiguration {
#Primary
#Bean
#ConfigurationProperties(prefix="spring.datasource")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
// userEntityManager bean
// userTransactionManager bean
}
.
#Configuration
#PropertySource({"classpath:persistence-multiple-db-boot.properties"})
#EnableJpaRepositories(
basePackages = "com.baeldung.multipledb.dao.product",
entityManagerFactoryRef = "productEntityManager",
transactionManagerRef = "productTransactionManager")
public class PersistenceProductAutoConfiguration {
#Bean
#ConfigurationProperties(prefix="spring.second-datasource")
public DataSource productDataSource() {
return DataSourceBuilder.create().build();
}
// productEntityManager bean
// productTransactionManager bean
}
Now we have defined the data source properties inside persistence-multiple-db-boot.properties according to the Boot autoconfiguration convention.
The interesting part is annotating the data source bean creation method with #ConfigurationProperties. We just need to specify the corresponding config prefix. Inside this method, we're using a DataSourceBuilder, and Spring Boot will automatically take care of the rest.
But how do the configured properties get injected into the DataSource configuration?
When calling the build() method on the DataSourceBuilder, it'll call its private bind() method:
public T build() {
Class<? extends DataSource> type = getType();
DataSource result = BeanUtils.instantiateClass(type);
maybeGetDriverClassName();
bind(result);
return (T) result;
}
This private method performs much of the autoconfiguration magic, binding the resolved configuration to the actual DataSource instance:
private void bind(DataSource result) {
ConfigurationPropertySource source = new
MapConfigurationPropertySource(this.properties);
ConfigurationPropertyNameAliases aliases = new
ConfigurationPropertyNameAliases();
aliases.addAliases("url", "jdbc-url");
aliases.addAliases("username", "user");
Binder binder = new Binder(source.withAliases(aliases));
binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result));
}
Although we don't have to touch any of this code ourselves, it's still useful to know what's happening under the hood of the Spring Boot autoconfiguration.
Besides this, the Transaction Manager and Entity Manager beans configuration is the same as the standard Spring application.
#Configuration
#EnableTransactionManagement
#ComponentScan("org.example")
#EnableJpaRepositories(basePackages = "org.yours.persistence.dao")
public class PersistenceJNDIConfig {
#Autowired
private Environment env;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory()
throws NamingException {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
// rest of entity manager configuration
return em;
}
#Bean
public DataSource dataSource() throws NamingException {
return (DataSource) new JndiTemplate().lookup("jdbc/dbname");
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
// rest of persistence configuration
}
We’re going to use a simple model with the #Entity annotation with a generated id and a name:
#Entity
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
// default getters and setters
}
Let’s define a simple repository:
#Repository
public class FooDao {
#PersistenceContext
private EntityManager entityManager;
public List<Foo> findAll() {
return entityManager
.createQuery("from " + Foo.class.getName()).getResultList();
}
}
And lastly, let’s create a simple service:
#Service
#Transactional
public class FooService {
#Autowired
private FooDao dao;
public List<Foo> findAll() {
return dao.findAll();
}
}
With this, you have everything you need in order to use your JNDI datasource in your Spring application.

SpringBoot app. accessing 2 DataSources using JdbcTemplate

I have a SpringBoot app. that has to access to different datasources to export data from 1 to another (1 local datasource and another remote datasource)
This is how my persistenceConfig looks like
public class PersistenceConfig {
#Bean
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
#Bean
public JdbcTemplate remoteJdbcTemplate() {
return new JdbcTemplate(remoteDataSource());
}
#Bean
public DataSource localDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getLocalDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getLocalDbDriverClassName());
config.setJdbcUrl(getLocalDbJdbcUrl());
config.addDataSourceProperty("user", getLocalDbUser());
config.addDataSourceProperty("password", getLocalDbPwd());
return new HikariDataSource(config);
}
#Bean
public DataSource remoteDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getRemoteDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getRemoteDbDriverClassName());
config.setJdbcUrl(getRemoteDbJdbcUrl());
config.addDataSourceProperty("user", getRemoteDbUser());
config.addDataSourceProperty("password", getRemoteDbPwd());
return new HikariDataSource(config);
}
}
But when I init my app I got this error:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: expected single matching bean but found 2: localDataSource,remoteDataSource
I also tried to user qualified beans, as follows:
#Bean(name = "localJdbcTemplate")
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
#Bean(name = "remoteJdbcTemplate")
public JdbcTemplate remoteJdbcTemplate() {
return new JdbcTemplate(remoteDataSource());
}
#Bean(name = "localDataSource")
public DataSource localDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getLocalDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getLocalDbDriverClassName());
config.setJdbcUrl(getLocalDbJdbcUrl());
config.addDataSourceProperty("user", getLocalDbUser());
config.addDataSourceProperty("password", getLocalDbPwd());
return new HikariDataSource(config);
}
#Bean(name = "remoteDataSource")
public DataSource remoteDataSource(){
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(getRemoteDbPoolSize());
config.setMinimumIdle(5);
config.setDriverClassName(getRemoteDbDriverClassName());
config.setJdbcUrl(getRemoteDbJdbcUrl());
config.addDataSourceProperty("user", getRemoteDbUser());
config.addDataSourceProperty("password", getRemoteDbPwd());
return new HikariDataSource(config);
}
but then I got this other error:
A component required a bean of type 'org.springframework.transaction.PlatformTransactionManager' that could not be found.
- Bean method 'transactionManager' not loaded because #ConditionalOnSingleCandidate (types: javax.sql.DataSource; SearchStrategy: all) did not find a primary bean from beans 'remoteDataSource', 'localDataSource'
I also tried
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
but then I got
A component required a bean of type 'org.springframework.transaction.PlatformTransactionManager' that could not be found.
You can use bean names to qualify them:
#Bean(name = "localDataSource")
public DataSource localDataSource() {
...
}
#Bean(name = "remoteDataSource")
public DataSource remoteDataSource() {
...
}
Please note: You have to do the same for your JdbcTemplate beans - just give them a name and it will work.
See the Spring JavaDoc for more Information: Bean
#Bean(name = "localJdbcTemplate")
public JdbcTemplate localJdbcTemplate() {
return new JdbcTemplate(localDataSource());
}
When you use your JdbcTemplate beans within your export service implementation via autowiring (#Autowired), you need to use #Qualifier to qualify them:
#Autowired
#Qualifier("localJdbcTemplate")
private JdbcTemplate jdbcTemplate;
#Autowired
#Qualifier("remoteJdbcTemplate")
private JdbcTemplate jdbcTemplate;
Bean gets its name from the method name, providing name attribute just makes it explicit (keeping the name the same as the method name). Overall suggestion about #Bean(name="...") and #Qualifier didn't fix the error for me.
I set up sample project with two embedded databases and got the same error as the aothor. Spring suggestion was to annotate one of the DataSource beans as #Primary and, in fact, this fixes the error. Usually it happens, when some other application parts want to see only one or one primary DataSource, if several present.
What seems to be a better solution is to disable not needed autoconfiguration beans keeping rest of the code as it is:
#SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
or:
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class})
depending on which annotations are in use.
If author doesn't use any JPA provider and operates directly with JdbcTemplate it may be a suitable solution.

Spring Data JPA - Multiple EnableJpaRepositories

My application has multiple data sources , so i have created two data source configuration classes based on this URL .
But while running the spring boot application am getting error
Description:
Field userDataRepo in com.cavion.services.UserDataService required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
From this Question on StackOverflow helped me to figure out the issue.i need to specify the entityManagerFactoryRef on my JPA repositories .
But i have many repository classes some of them uses Entitymanager 'A' and some of them uses 'B' . my current spring boot application class is like this
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
#EnableTransactionManagement
#EntityScan("com.info.entity")
#ComponentScan({"com.info.services","com.info.restcontroller"})
#EnableJpaRepositories("com.info.repositories")
public class CavionApplication {
public static void main(String[] args) {
SpringApplication.run(CavionApplication.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}}
I have given the EnableJpaRepositories on the spring boot class , so how can i configure multiple EnableJpaRepositories so that i can configure multiple entityManagerFactory ?
Please suggest the best way to setup the multiple data sources .
In order to let spring knows what DataSource is related to what Repository you should define it at the #EnableJpaRepositories annotation. Let's assume that we have two entities, the Servers entity and the Domains entity and each one has its own Repo then each Repository has its own JpaDataSource configuration.
1. Group all the repositories based on the Data Source that they are related to. For example
Repository for Domains entities (package: org.springdemo.multiple.datasources.repository.domains):
package org.springdemo.multiple.datasources.repository.domains;
import org.springdemo.multiple.datasources.domain.domains.Domains;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DomainsRepository extends JpaRepository<Domains,Long> {
}
Repository for Servers entities (package: org.springdemo.multiple.datasources.repository.servers)
package org.springdemo.multiple.datasources.repository.servers;
import org.springdemo.multiple.datasources.domain.servers.Servers;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ServersRepository extends JpaRepository<Servers,Long> {
}
2. For each JPA Data Soruce you need to define a configuration, in this example I show how to configure two different DataSources
Domains Jpa Configuration: the relationship between the Data Source and the repository is defined in the basePackages value, that is the reason why is necessary to group the repositories in different packages depending on the entity manager that each repo will use.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "domainsEntityManager",
transactionManagerRef = "domainsTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
)
public class DomainsConfig {
Servers Data Source Configuration: as you can see the basePackages value has the package name of the Servers Repository , and also the values of entityManagerFactoryRef and transactionManagerRef are different in order to let spring separate each entityManager.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "serversEntityManager",
transactionManagerRef = "serversTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
)
public class ServersConfig {
3. Set one Datasource as primary
In order to avoid the error message: Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found: just set one of the datasource as #Primary, in this example I select the Servers Datasource as primary:
#Bean("serversDataSourceProperties")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("serversDataSource")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(#Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
return serversDataSourceProperties().initializeDataSourceBuilder().build();
}
If you need more information please see the full example for each configuration:
Servers JPA Configuration
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "serversEntityManager",
transactionManagerRef = "serversTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
)
public class ServersConfig {
#Bean(name = "serversEntityManager")
public LocalContainerEntityManagerFactoryBean getServersEntityManager(EntityManagerFactoryBuilder builder,
#Qualifier("serversDataSource") DataSource serversDataSource){
return builder
.dataSource(serversDataSource)
.packages("org.springdemo.multiple.datasources.domain.servers")
.persistenceUnit("servers")
.properties(additionalJpaProperties())
.build();
}
Map<String,?> additionalJpaProperties(){
Map<String,String> map = new HashMap<>();
map.put("hibernate.hbm2ddl.auto", "create");
map.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
map.put("hibernate.show_sql", "true");
return map;
}
#Bean("serversDataSourceProperties")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("serversDataSource")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(#Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
return serversDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean(name = "serversTransactionManager")
public JpaTransactionManager transactionManager(#Qualifier("serversEntityManager") EntityManagerFactory serversEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(serversEntityManager);
return transactionManager;
}
}
Domains JPA Configuration
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "domainsEntityManager",
transactionManagerRef = "domainsTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
)
public class DomainsConfig {
#Bean(name = "domainsEntityManager")
public LocalContainerEntityManagerFactoryBean getdomainsEntityManager(EntityManagerFactoryBuilder builder
,#Qualifier("domainsDataSource") DataSource domainsDataSource){
return builder
.dataSource(domainsDataSource)
.packages("org.springdemo.multiple.datasources.domain.domains")
.persistenceUnit("domains")
.properties(additionalJpaProperties())
.build();
}
Map<String,?> additionalJpaProperties(){
Map<String,String> map = new HashMap<>();
map.put("hibernate.hbm2ddl.auto", "create");
map.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
map.put("hibernate.show_sql", "true");
return map;
}
#Bean("domainsDataSourceProperties")
#ConfigurationProperties("app.datasource.domains")
public DataSourceProperties domainsDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("domainsDataSource")
public DataSource domainsDataSource(#Qualifier("domainsDataSourceProperties") DataSourceProperties domainsDataSourceProperties) {
return domainsDataSourceProperties.initializeDataSourceBuilder().build();
}
#Bean(name = "domainsTransactionManager")
public JpaTransactionManager transactionManager(#Qualifier("domainsEntityManager") EntityManagerFactory domainsEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(domainsEntityManager);
return transactionManager;
}
}
In order to separate each datasource I put the configuration in the application.properties file, like this:
app.datasource.domains.url=jdbc:h2:mem:~/test
app.datasource.domains.driver-class-name=org.h2.Driver
app.datasource.servers.driver-class-name=com.mysql.jdbc.Driver
app.datasource.servers.url=jdbc:mysql://localhost:3306/v?autoReconnect=true&useSSL=false
app.datasource.servers.username=myuser
app.datasource.servers.password=mypass
If you need more information please see the following documentation:
Spring Documentation: howto-two-datasources
A similar example of how configure two different databases: github example
The answered provided by #Daniel C. is correct. Small correction/observation from my side.
#Primary is not required if you don't want to mark any datasource as
default one, otherwise necessary.
If you are defining any of the EntityManagerFactoryBean with #Bean name as entityManagerFactory then it's better to mark it #Primary to avoid conflict.
#ConfigurationProperties("app.datasource.servers")
can be marked at class level instead of defining at method level.
Better to return HikariDataSource as datasource if you using Spring
Boot 2.x or higher version as it has been changed.
Make sure you define exact property for jdbc-url which is being used by
HikariDataSource to refer JDBC Connection URL.
I just added a module aware multi database aware library for mysql in github.Some application properties need to be added and you are done .
Documentation and other details could be found at :-
https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql

why is spring boot's DataJpaTest scanning #Component

Confident this hasn't been asked but reading through the Spring docs and testing utilities I found this annotation and thought I'd start using it. Reading through the fine print I read:
Regular #Component beans will not be loaded into the ApplicationContext.
That sounded good and I even liked the idea of using H2 except from what I found the entity I wanted to use had catalog and schema modifiers to it and the default H2 I couldn't figure out how to support that. I made an H2 datasource for the test branch and use that and override the replace. I wound up with
#RunWith(SpringRunner.class)
#ContextConfiguration(classes=ABCH2Congfiguration.class)
#DataJpaTest
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.NONE)
public class StatusRepositoryTest {
}
However my tests fails fro Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type.
which leads to:
Error creating bean with name 'customerServiceImpl': Unsatisfied dependency.
However the customerServiceImpl is this bean:
#Component
public class CustomerServiceImpl implements CustomerService {
}
That says #Component. The fine print for DataJpaTest says it doesn't load #Components. Why is it doing that and thus failing the test?
As Kyle and Eugene asked below here's the rest:
package com.xxx.abc.triage;
#Component
public interface CustomerService {
}
Configuration
#ComponentScan("com.xxx.abc")
#EnableJpaRepositories("com.xxx.abc")
//#Profile("h2")
public class ABMH2Congfiguration {
#Primary
#Bean(name = "h2source")
public DataSource dataSource() {
EmbeddedDatabase build = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName("ABC").addScript("init.sql").build();
return build;
}
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter bean = new HibernateJpaVendorAdapter();
bean.setDatabase(Database.H2);
bean.setShowSql(true);
bean.setGenerateDdl(true);
return bean;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource, JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
bean.setDataSource(dataSource);
bean.setJpaVendorAdapter(jpaVendorAdapter);
bean.setPackagesToScan("com.xxx.abc");
return bean;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
And just to clarify the question, why is #Component being loaded into the context within a #DataJpaTest?
#ComponentScan automatically inject all found #Component and #Service into context. You could override it by separate #Bean:
#Bean
CustomerService customerService{
return null;
}
Or remove #Component annotation from CustomerService and CustomerServiceImpl, but you should add #Bean at your production #Configuration
#DataJpaTest does not load #Component, #Service... by default, only #Repository and internal things needed to configure Spring data JPA.
In your test, you can load any #Configuration you need, and in your case, you load #ABMH2Congfiguration which performs a #ComponentScan that's why Spring try to load your CustomerService.
You should only scanning the #Repository in this configuration class, and scan others #Component, #Service... in another #Configuration like DomainConfiguration. It's always a good practice to separate different types of configurations.

Spring Data: inject 2 repositories with same name but in 2 different packages

Context
I want to use in the same Spring context two different databases that have entities that share the same name, but not the same structure. I rely on Spring Data MongoDB and JPA/JDBC. I have two packages, containing among others the following files:
com.bar.entity
Car.class
com.bar.repository
CarRepository.class
RepoBarMarker.class
com.bar.config
MongoConfiguration.class
com.foo.entity
Car.class
com.foo.repository
CarRepository.class
RepoFooMarker.class
com.foo.config
JPAConfiguration.class
SpecEntityManagerFactory.class
The content of each Car.class is different, I cannot reuse them. bar uses Spring-Mongo and foo uses Spring-JPA, and repositories are initialised via #EnableMongoRepositories and #EnableJpaRepositories annotations. When in one of my application component I try to access the foo version of the repository:
#Resource
private com.foo.repository.CarRepository carRepository;
I have the following exception when the class containing the #Resource field is created:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'carRepository' must be of type [com.foo.repository.CarRepository], but was actually of type [com.sun.proxy.$Proxy31]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:374)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:446)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:420)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:545)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:155)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessPropertyValues(CommonAnnotationBeanPostProcessor.java:305)
... 26 more
It appears that Spring tries to convert a bar repository to a foo repository, instead of creating a new bean, as in the same stack I also have the following exception:
Caused by: java.lang.IllegalStateException: Cannot convert value of type [com.sun.proxy.$Proxy31 implementing com.bar.repository.CarRepository,org.springframework.data.repository.Repository,org.springframework.aop.SpringProxy,org.springframework.aop.framework.Advised] to required type [com.foo.repository.CarRepository]: no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:267)
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:93)
at org.springframework.beans.TypeConverterSupport.doConvert(TypeConverterSupport.java:64)
... 35 more
If I try instead to autowire the repository:
#Autowire
private com.foo.repository.CarRepository carRepository;
I get the following exception:
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.foo.CarRepository com.shell.ShellApp.carRepository; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.foo.CarRepository] 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.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:509)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:87)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:290)
... 26 more
Spring-data configuration
In foo (JPA) package, JPAConfigration.class:
#Configuration
#EnableJpaRepositories(basePackageClasses = RepoFooMarker.class)
public class JPAConfiguration {
#Autowired
public DataSource dataSource;
#Autowired
public EntityManagerFactory entityManagerFactory;
#Bean
public EntityManager entityManager(final EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public Session session(final EntityManager entityManager)
{
return entityManager.unwrap(Session.class);
}
#Bean
public PlatformTransactionManager transactionManager() throws SQLException {
final JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator() {
return new HibernateExceptionTranslator();
}
}
SpecEntityManagerFactory.class:
#Configuration
public class SpecEntityManagerFactory {
#Bean
public EntityManagerFactory entityManagerFactory(final DataSource dataSource) throws SQLException {
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setDatabase(Database.POSTGRESQL);
final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.foo.entity");
factory.setJpaProperties(getHibernateProperties());
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
private Properties getHibernateProperties()
{
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
return hibernateProperties;
}
}
In bar (MongoDB) package, MongoConfiguration.class:
#Configuration
#EnableMongoRepositories(basePackageClasses = RepoBarMarker.class)
public class MongoConfiguration extends AbstractRepoConfig {
#Override
#Bean
public MongoOperations mongoTemplate() {
final MongoClient mongo = this.getMongoClient();
final MongoClientURI mongoUri = this.getMongoClientUri();
final MongoTemplate mongoTemplate = new MongoTemplate(mongo, mongoUri.getDatabase());
mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred());
mongoTemplate.setWriteConcern(WriteConcern.UNACKNOWLEDGED);
return mongoTemplate;
}
}
Question
If I change in foo repository the entity name to CarFoo.class and the repository to CarFooRepository.class, then everything works. But is there away to avoid renaming them and still have a real wiring per type, instead of name (as it is what seems to be done here), for Spring Data repositories?
In your case, you can use
#Repository("fooCarRepository")
on the interface declaration of
com.foo.repository.CarRepository
Although when using Spring Data #Repository is not generally needed on the interface, however in your case you need to supply it. That's because you need to make Spring register the implementation of the bean with a custom name (in this case fooCarRepository) in order to avoid the name collision.

Resources