Spring Boot How to load only specified #Repository components - spring

I have a project containing many Dao annotated by #Repository each.
Also several spring boot projects, each having its spring context an can be run independently and they have a reference to the project containing the Daos.
The thing is, I don't want to load all Dao into the spring context in each project. Only some specified Dao are required for each spring boot project.
I used to specify Dao classes by defining them as beans in an XML configuration for each project.
Now we are moving to java and annotation based configuration.
Is there a way to tell the spring context only to load the #Repository that I specify?
I know I can make a #Configuration class and define #Bean methods but I still need them to be treated as #Repository and not a normal bean. Any idea if this is supported and how to implement this?

You can use #Conditional on each of those DAO classes.
Class will be loaded in context only when the condition mentioned using #Conditional annotation is fulfilled. You can have condition like:
#ConditionalOnProperty(
value="module.name",
havingValue = "module1",
matchIfMissing = false)
class DaoForModule1 {
This will load the DaoModule1 if and only if the property module.name has value module1. If you want to load this DaoModule1 when proerty is not set, you can change matchIfMissing to true.
You can also use #Profile annotation to limit the classes loaded based on profile
#Profile("module2")
class DaoForModule2 {
This would load DaoForModule2 only when you have module2 in the list of active profiles. But i would not prefer profile as the use case of profiles is different. We use profiles generally to specify variable resources based on environment.

#SpringBootApplication just combine #EnableAutoConfiguration , #SpringBootConfiguration and #ComponentScan.
The #ComponentScan is the guy that cause all #Repository beans under the scanned package to be registered automatically which is the thing that you don't want it to happen.
So you can use these annotations separately but excluding #ComponentScan. And use #Import to explicitly define the beans that you want to register.
The main application class will look like :
#SpringBootConfiguration
#EnableAutoConfiguration
#Import(value = {FooRepoistory.class, BarRepository.class,.......})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

From your question, I assume you want to reuse a Spring DAO project with multiple repositories and JPA Entity objects, maybe belonging to different datasources, in several other Spring projects. You prefer to load only a specific set of the JPA entities/repos. The first step is to organize the related entities and repositories into distinct packages and include this project in the path of the other projects.
This is one way to handle this, assuming you have separated the repositories and entities into different packages. Create your own Configuration bean that will instantiate a JPA EntityManagerFactory bean with the specific packages and datasource it needs. in this code below, EntityManagerFactory below will load the entities from MODEL_PACKAGE and the repositories from REPOSITORIES_PACKAGE.
#Configuration
#ComponentScan(basePackages = MODEL_PACKAGE)
#EnableJpaRepositories(basePackages = REPOSITORIES_PACKAGE,
entityManagerFactoryRef = "ENTITY_MANAGER_FACTORY")
#EnableTransactionManagement
public class PersistenceConfig {
public static final String MODEL_PACKAGE = "Your model package";
public static final String REPOSITORIES_PACKAGE = "Your repository package";
public static final String ENTITY_MANAGER_FACTORY = "entity_manager_factory";
public static final String TRANSACTION_MANAGER = "transaction_manager";
#Autowired //This is to get your property file entries (DB connection, etc).
private Environment environment;
#Bean(DATA_SOURCE)
public DataSource dataSource() {
//Create your datasource from environment properties. Example - org.apache.tomcat.jdbc.pool.DataSource
}
#Bean(ENTITY_MANAGER_FACTORY) #Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
#Qualifier(DATA_SOURCE) DataSource dataSource) throws IllegalStateException {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
Properties jpaProperties = new Properties();
// set properties for your JPA, for example, hibernate.dialect, hibernate.format_sql, etc.
entityManagerFactoryBean.setJpaProperties(jpaProperties);
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPackagesToScan(MODEL_PACKAGE);
}
#Bean(TRANSACTION_MANAGER) #Autowired
#Primary
#Qualifier(value = "transactionManager")
public JpaTransactionManager transactionManager(
#Qualifier(ENTITY_MANAGER_FACTORY) EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;

Related

SpringBoot 3 native compilation not generating bean definition for second JpaRepository and failing to start with -Dspring.aot.enabled=true

I am facing an issue with Spring Boot 3 native compilation where the project contains two JpaRepository connecting to two different datasources. The creation of the second datasource configuration depends on the first datasource and JpaRepository as it contains the details about the databases to connect.
The problem is that the Spring Boot Maven plugin process-aot goal does not generate bean definition for repositories which are processed later on. As a result, the application fails to start with the -Dspring.aot.enabled=true property enabled.
I have tried several solutions, including:
Adding the #DependsOn annotation to the second datasource configuration class, but it didn't work.
Adding the #DependsOn annotation to the second JpaRepository, but it also didn't work.
Adding a #Configuration class that contains both datasource configurations, but it also didn't work.
Here is a simplified version of my code:
package com.company.multidatabases.config
#Configuration
public class DataSource1Config {
// datasource1 configuration
#Autowired
private MyEntity1Repository repo;
private Map<Object,Object> dataSources;
}
package com.company.config
#Configuration
public class DataSource2Config {
#Autowired
private DataSource1Config dataSource1Config;
#Bean
public DataSource dataSource(){
return // AbstractDataSourceRouting with datasources map from DataSource1Config
}
// datasource2 configuration that depends on dataSource1Config
}
package com.company.multidatabases.repository
#Repository
public interface MyEntity1Repository extends JpaRepository<MyEntity1, Long> {
// MyEntity1Repository definition
}
package com.company.repository
#Repository
public interface MyEntity2Repository extends JpaRepository<MyEntity2, Long> {
// MyEntity2Repository definition that depends on DataSource2Config
}
And here is the error message I get:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'myEntity1Repository' available
Any help or suggestion is highly appreciated. Thank you in advance.

spring boot with multiple databases

I'm trying to write an application that accesses data from two sources. I'm using Spring Boot 2.3.2. I've looked at several sources for info about how to configure the app: the Spring documentation talks about setting up multiple datasources, but does not explain how to link up JPA repositories. This Baeldung article goes a lot further, but I'm looking to take advantage of autoconfiguration in Spring.
So far, I've created a separate package, added a config class (along with model and repositories), and included this package in scanBasePackages so that it's picked up. Since I'll have more than one datasource, I've added this to my #SpringBootApplication:
#Bean
#Primary
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
This successfully loads up my Spring app using the standard spring config values. The two databases are on different servers, but should share characteristics (other than url and credentials).
So, my auxiliary configuration file looks like this
#Configuration
#EnableAutoConfiguration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "orgEntityManagerFactory",
transactionManagerRef = "orgTransactionManager",
basePackages = {
"pacage2.repositories"
}
)
public class DataSourceConfiguration {
// added because of this answer: https://stackoverflow.com/a/51305724/167889
#Bean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
}
#Bean
#ConfigurationProperties(prefix = "external.datasource")
public DataSourceProperties orgDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
public HikariDataSource orgDataSource(#Qualifier("orgDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class)
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean orgEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("orgDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("package2.model")
.build();
}
#Bean
public PlatformTransactionManager orgTransactionManager(
#Qualifier("orgEntityManagerFactory") EntityManagerFactory entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Now, the error I'm getting right now is Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set. However, I have that value in my config and it's applied by the Spring auto config. I believe it needs to be set in the EntityManagerFactoryBuilder and by creating my own, the autoconfig isn't getting applied.
How can I have my cake and eat it too? I'd like to leverage as much of the robust autoconfiguration that Spring provides to setup datasources and wire them to the appropriate repositories. Effectively, all that I want to change is the url and credentials, and I can separate the entities and repositories into a completely separate package for easy scanning.

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 persisted by JdbcTemplate unable to be seen by JpaRepository

I am building a Spring Boot application which requires the need for persistence via JDBC and selecting/reading via JPA/Hibernate. I have implemented both of these types of operations using Spring's JdbcTemplate and Spring Data's JpaRepository.
After I persist using JdbcTemplate I am unable to see the data via JpaRepository even though they share the same datasource. I am able to read the data if I use JdbcTemplate.
NOTE: I am using two data sources. One is configured in another class without the #Primary annotation using its own entity manager factory and transaction manager, which is why I've needed to explicitly define it below using Spring Boot's default bean terminology "transactionManager" and "entityManagerFactory".
The following is my embedded database configuration for the primary beans:
#Configuration
#EnableJpaRepositories(basePackages = {"com.repository"})
public class H2DataSourceConfiguration {
private static final Logger log = LoggerFactory.getLogger(H2DataSourceConfiguration.class);
#Bean(destroyMethod = "shutdown")
#Primary
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.setName("dataSource")
.build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com.my.domain", "org.springframework.data.jpa.convert.threeten")
.build();
}
#Bean
#Primary
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(entityManagerFactory);
return jpaTransactionManager;
}
}
The persistence happens in a different transaction to the reading of the data, however they share the same service.
Both operations happen within the #Transactional annotation. Both repository beans are specified in the same service and also contain the #Transactional annotation. The service looks as follows:
#Service
#Transactional
public class MyServiceImpl implements MyService {
private static final Logger log = LoggerFactory.getLogger(MyServiceImpl.class);
#Autowired
private MyJpaRepository myJpaRepository;
#Autowired
private MyJdbcRepository myJdbcRepository;
...
}
MyJdbcRepositoryImpl.java:
#Repository
#Transactional(propagation = Propagataion.MANDATORY)
public class MyJdbcRepositoryImpl implements MyJdbcRepository {
#Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
// methods within here all use jdbcTemplate.query(...)
}
MyJpaRepository.java:
#Repository
#Transactional(propagation = Propagataion.MANDATORY)
public interface AcquisitionJpaRepository extends JpaRepository<AcquisitionEntity, Long> {
}
Is it at all possible that the jdbctemplate calls are saving to a different h2 database?
The above configuration is correct!
The problem was that the JdbcTemplate calls had the schema owner as a prefix.
For example:
select * from I_AM_SCHEMA.KILL_ME
However, I had both the #Entity annotation and the #Table annotation on the entity object and only specified the table name!
Example:
#Entity
#Table(name = "KILL_ME")
So, we were writing to one table with JdbcTemplate but reading from a completely different other table via JPA/Hibernate due to us missing the prefix.
The correct fix was to prefix the entity name in the #Entity annotation:
#Entity("I_AM_SCHEMA.KILL_ME")
DONE!

Best way to test JPA Spring 3.1

I have written several JPA Repositories, Services and support classes for my Spring 3.1.1/JPA/Hibernate 4 web app. However, I really want to write some unit and integration tests for it (I know, you are supposed to write those first). I am using JavaConfig rather than XML, so I am wondering the best way to test. Here is the particular problem I am trying to solve:
I have a #Configuration that declares DataSource, JpaTransactionManager, LocalContainerEntityManagerFactoryBean, and all my respositories. Obviously I don't want to start all that up for an integration test, so I thought I could use the EmbeddedDatabase and H2 to create an in memory database, populate with values, and then use my Repositories against it. However, the documentation I have seen hasn't helped me put this together. I have this:
#RunWith( SpringJUnit4ClassRunner.class )
public class TestMenuService {
private EmbeddedDatabase database;
#Autowired
private MenuRepository menuRepository;
#Before
public void setUp() throws Exception {
database = new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName("myschema")
.addScript("classpath:schema.sql").build();
menuRepository = new MenuRepository();
Assert.assertNotNull(database);
}
But the menuRepository does not get instantiated, so I tried creating a test version of my #Configuration
#Configuration } )
#ComponentScan( basePackages = { "com.mycompany.service"} )
#EnableTransactionManagement
#ImportResource( "classpath:applicationContext.xml" )
#PropertySource( "classpath:test-application.properties" )
public class TestEdmConfiguration {
#Bean
MenuRepository menuRepository() {
return new MenuRepository();
}
My test-applicationContext.xml
<jpa:repositories base-package="com.mycompany.servce.repository"/>
My test-application.properties:
db.driver=org.h2.Driver
db.username=sa
db.password=
db.url=jdbc:h2:mem:myschema
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.format_sql=true
hibernate.hbm2ddl.auto=create
hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy
hibernate.show_sql=true
But this requires that I create all the datasources, etc mentioned above. It seems like I am just duplicating all the support beans for this one.
Is there a way to have the reposository and embeddeddatabase isolated to my test without all the other dependencies?
If you want to test your repository in a full integration test I would image that you need everything else to be setup i.e. EntityManagerFactory, PlatformTransactionManager etc.
Since you are using Spring 3.1 I would suggest that you achieve this using bean profiles.
I would create two profiles one for tests and one for the application, each of which supplies a datasource.
#Configuration
#Profile("test")
public class EmbeddedDataSource {
#Bean
public DataSource dataSource() {
// Return a H2 datasource
}
}
#Configuration
#Profile("application")
public class ApplicationDataSource {
#Bean
public DataSource dataSource() {
// Return a normal datasource
}
}
The you can create a test which starts up the spring context as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { MyConfigClass.class })
#ActiveProfiles(profiles = {"test"})
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class TestRepository {
}
In here you can specify the profiles where are active for the test.

Resources