How to get Spring Data to handle multiple heterogeneous DataSources? - java

I've been successful using using the Accessing Data With JPA tutorial for Spring. I've gotten a CrudRepository of my own to work automatically by just configuring a specific DataSource #Bean, and the internal connections between these are managed by Spring Data (or Spring Boot, it's hard to tell which).
However, I can't figure out how to get that automated plumbing to handle a second DataSource #Bean. Injecting a second one causes the autoconfiguration classes to explode during startup.
Any thoughts as to how to do this? The searches I've done for this resulted in articles discussing multiple homogeneous DataSources for load balancing or other purposes, which is really not what I need. I have multiple databases with completely separate content that I need to pull into this app and I'd really like to avoid having to replicate all that automated configuration just because a second database entered the mix.
I'm hoping this is simple, but I'm fearful that it's an unsupported edge case in the autoconfiguration.

You can create two datasources and entitymanagers, one bean of them mark as #Primary
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository1")
public class FirstConfiguration {
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource postgresDataSource() {
return DataSourceBuilder.create().
build();
}
#Bean(name = "entityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean emf1(EntityManagerFactoryBuilder builder){
return builder
.dataSource(postgresDataSource())
.packages("io.eddumelendez.springdatajpa.domain1")
.persistenceUnit("users")
.build();
}
}
Configuration for another datasource:
#Configuration
#EnableJpaRepositories(basePackages = "io.eddumelendez.springdatajpa.repository2", entityManagerFactoryRef = "emf2")
public class SecondConfiguration {
#Bean
#ConfigurationProperties(prefix = "datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean emf2(EntityManagerFactoryBuilder builder){
return builder
.dataSource(mysqlDataSource())
.packages("io.eddumelendez.springdatajpa.domain2")
.persistenceUnit("customers")
.build();
}
}
Your application.properties should looks like this:
datasource.mysql.url=jdbc:mysql://localhost:3306/mysql_demo
datasource.mysql.username=root
datasource.mysql.password=root
datasource.postgres.url=jdbc:postgresql://localhost:5432/postgres_demo
datasource.postgres.username=postgres
datasource.postgres.password=postgres

Related

Using single configuration for a group of tests in Spring Boot application

I have a Spring Boot + JPA application, configured for Oracle. But in my tests I am using H2 database.
When I create a base class for data-aware tests as
#ComponentScan("com.payeshgaran")
#DataJpaTest(properties = "spring.jpa.hibernate.ddl-auto=none")
#EnableTransactionManagement
#Sql(scripts = {"/sql/insurance-field.sql", "/sql/base-info.sql"})
#Transactional
public abstract class AbstractDataAwareTest {
}
and extend test classes from it, everything works fine, but if I put the configurations (specially #Sql on a #TestConfiguration class) and then use #ContextConfiguration class it doesn't work). Also if I configure the datasource by code in the #TestConfguration class as
#ComponentScan("com.payeshgaran")
#EnableTransactionManagement
#TestConfiguration
public class TestDataConfiguration {
#Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.H2)
.setScriptEncoding("UTF-8")
.ignoreFailedDrops(true)
.addScript("classpath:sql/insurance-field.sql")
.addScript("classpath:sql/base-info.sql")
.build();
}
}
again the initialization scripts won't run.
Anyone knows how should I use a single point of configuration without inheritance?

How to run two independent transaction managers for jpa and jdbc datasources

I'm developing a standalone application with following technology stack:
Spring Boot version 2.1.0.RELEASE
Oracle12c System with driver ojdbc6 (11.2.0.3)
Apache Camel
JPA for the main datasource
JDBC for a secondary datasource (read only)
The JPA datasource is the primary datasource where the application itself is connected to and write data to. The JDBC is an additional datasource to read data from a database with another purpose.
During runtime I encounter the following issue:
I poll/select a JPA Entity from the primary datasource and do some processing. This processing includes running a select query on the secondary datasource via a jdbc template. Now if the execution of the query throws an exception I am able to catch it and want to update a status field on the JPA Entity and write it to the datasource.
I've already read that Oracle tries to do a rollback if a SQLException occurrs. The issue is that my JPA datasource is unable to commit the changes to the Entity I do when the JDBC query fails.
It seems to me like the two datasources/transaction managers are not completely independent of each other and that an exception in the secondary datasource causes errors in the primary datasource during commiting changes.
Is this even possible? If yes, how can I configure two independent transaction managers?
EDIT:
I have already tried to annotate the respective methods and classes with #Transactional(noRollbackFor = Exception.class) but this does not solve the problem.
Here are the two Datasource configurations:
ApplicationDatasourceConfig (JPA)
#Configuration
#EnableJpaRepositories(basePackages = "foo.bar.repository.jpa",
entityManagerFactoryRef = "applicationEntityManagerFactory",
transactionManagerRef = "applicationTransactionManager")
#ConfigurationProperties(prefix = "spring.datasource.hikari")
#EnableTransactionManagement
public class ApplicationDatasourceConfig extends HikariConfig{
#Bean("applicationDatasource")
#Primary
public DataSource applicationDataSource(){
return new HikariDataSource(this);
}
#Bean("applicationDatasourceProperties")
#Primary
public DataSourceProperties dataSourceProperties(){
return new DataSourceProperties();
}
#Bean("applicationEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean applicationEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("applicationDatasource") DataSource dataSource){
return builder
.dataSource(dataSource)
.packages("foo.bar.entity")
.build();
}
#Bean("applicationTransactionManager")
#Primary
public PlatformTransactionManager applicationTransactionManager(#Qualifier("applicationEntityManagerFactory")EntityManagerFactory entityManagerFactory){
return new JpaTransactionManager(entityManagerFactory);
}
}
SecondaryDatasourceConfig (JDBC)
#Configuration
#ConfigurationProperties(prefix = "secondary.datasource")
#EnableTransactionManagement
public class SecondaryDatasourceConfig {
#Bean("secondaryDatasource")
public DataSource secondaryDataSource(){
return secondaryDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
public DataSourceProperties secondaryDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(#Qualifier("secondaryDatasource") DataSource secondaryDataSource){
return new DataSourceTransactionManager(secondaryDataSource);
}
}

Spring boot JPA how to add multiple databases from same connection?

I have two different mysql database from the same connection and would like to use both in my application.properties like this way:
spring.datasource.url=jdbc:mysql://localhost:3306/membership
spring.datasource.username=root
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost:3306/finance
spring.datasource.username=root
spring.datasource.password=
But this is not allowed (duplicate key's).
I have found this guide but this is to much code i think not a elegant solution, looks like workaround for simple problem.
Are there better and much simpler solutions?
PS: I am new to spring boot.
You have to decleare different keys for different datasource. The main config for multi datasource is(this is the demo):
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSource fooDataSource() {
return fooDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("app.datasource.bar")
public DataSourceProperties barDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("app.datasource.bar")
public DataSource barDataSource() {
return barDataSourceProperties().initializeDataSourceBuilder().build();
}
You can refer to this link and the post to have an overview for the configuration.
I think you can use only 1 MySQL database. Try to combine with PostgreSQL. Example

Spring batch 2.2 JavaConfig

I'm trying to get Spring Batch 2.2 working with JavaConfig.
Nowadays they have a #EnableBatchProcessing annotation that sets up a lot of things.
Default that annotation uses a datasource for its job data, but we don't want to save this data and don't want to create the table for it. The documentation says something about customizing but I have not been able to get it working:
The user has to provide a DataSource as a bean in the context, or else implement BatchConfigurer in the configuration class itself, e.g.:
public class AppConfig extends DefaultBatchConfigurer {
In our older version we've been able to use MapJobRepositoryFactoryBean class so it keeps all its data in memory. Is there anyway to use the full JavaConfig way and not define a DataSource? I've not been able to get it working.
Even if I define two data sources (one HSQL in-memory that never gets used) and our real Oracle datasource it does not work because it finds two data sources instead of one.
Anyone have an idea how to get this working? Or is the only solution going back to configuring this in the XML way?
Assuming that no other artifacts require a DataSource, you can use java config to create a context without a DataSource. To do that, your configuration will need to extend DefaultBatchConfigurer as you point out. In there, you'll override two methods, createJobRepository() and setDataSource(). Below is an example context (it doesn't define a job or steps, but it bootstraps all the related beans correctly).
#Configuration
#EnableBatchProcessing
public static class BatchConfiguration extends DefaultBatchConfigurer {
#Override
protected JobRepository createJobRepository() throws Exception {
MapJobRepositoryFactoryBean factory =
new MapJobRepositoryFactoryBean();
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
#Override
#Autowired
public void setDataSource(DataSource dataSource) {
if(dataSource != null) {
super.setDataSource(dataSource);
}
}
#Bean
public DataSource dataSource() {
return null;
}
}
I do think that simplifying this would be a useful feature and have added it to Jira. You can track it's progress here: https://jira.springsource.org/browse/BATCH-2048
Just define a dataSource() method in your BatchConfig Class
Here is how
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(driverUrl);
dataSource.setUsername(driverUsername);
dataSource.setPassword(driverPassword);
return dataSource;
}
This will automatically be invoked while setting up the TransactionManager

Multiple jpa:repositories in xml config, how to configure with #EnableJPARepositories using Spring java config?

I have researched and found an explaination and sample code as to how to use spring data jpa with multiple datasources which refers to configuring multiple jpa:repositories in the xml configuration as follows:
<jpa:repositories base-package="org.springframework.data.jpa.repository.sample"
entity-manager-factory-ref="entityManagerFactory">
<repository:exclude-filter type="assignable" expression="org.springframework.data.jpa.repository.sample.AuditableUserRepository" />
</jpa:repositories>
<jpa:repositories base-package="org.springframework.data.jpa.repository.sample"
entity-manager-factory-ref="entityManagerFactory-2"
transaction-manager-ref="transactionManager-2">
<repository:include-filter type="assignable" expression="org.springframework.data.jpa.repository.sample.AuditableUserRepository" />
</jpa:repositories>
How would you declare both of the above jpa:repositories configurations using java configuration and the #EnableJpaRepositories annotation?
The annotation seems to support only one set of attributes (i.e. for one jpa:repository only) and it is not possible to declare the annotation multiple times.
I created a 'minimal' multiple datasource project to help me work out how to do this. There are 7 Java classes and other config in there, so I will only post key extracts in this answer. You can get the full project from GitHub: https://github.com/gratiartis/multids-demo
The demo sets up two JPA entities:
#Entity public class Foo { /* Constructors, fields and accessors/mutators */ }
#Entity public class Bar { /* Constructors, fields and accessors/mutators */ }
Associated with these we will create two repositories. Thanks to the awesomeness of Spring Data, we can get ourselves some pretty full-featured repositories purely by defining interfaces which extend JpaRepository:
public interface FooRepository extends JpaRepository<Foo, Long> {}
public interface BarRepository extends JpaRepository<Bar, Long> {}
Now we need to ensure that each of these maps to a table in its own database.
To achieve this, we will need two separate entity managers, each of which has a different datasource. However, in a Spring Java config #Configuration class, we can only have one #EnableJpaRepositories annotation and each such annotation can only reference one EntityManagerFactory. To achieve this, we create two separate #Configuration classes: FooConfig and BarConfig.
Each of these #Configuration classes will define a DataSource based on an embedded HSQL database:
#Bean(name = "fooDataSource")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setName("foodb").setType(EmbeddedDatabaseType.HSQL).build();
}
#Bean(name = "barDataSource")
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setName("bardb").setType(EmbeddedDatabaseType.HSQL).build();
}
#Bean(name = "barEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lef =
new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource());
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("com.sctrcd.multidsdemo.domain.bar");
lef.setPersistenceUnitName("barPersistenceUnit");
lef.afterPropertiesSet();
return lef.getObject();
}
#Bean(name = "fooEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean lef =
new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource());
lef.setJpaVendorAdapter(jpaVendorAdapter);
lef.setPackagesToScan("com.sctrcd.multidsdemo.domain.foo");
lef.setPersistenceUnitName("fooPersistenceUnit");
lef.afterPropertiesSet();
return lef.getObject();
}
Each configuration should define an EntityManagerFactory, as above, which references its own dataSource() #Bean method. It also defines a path to the #Entity beans which it manages. You need to make sure that #Entity beans for different data sources are in different packages.
At this point it's worth noting that if each of these configurations uses the default namings for key persistence beans (i.e. entityManagerFactory), then Spring will see that there are two beans with the EntityManager interface, both of which have the same name. So one will be chosen. This leads to errors such as:
Not an managed type: class com.sctrcd.multidsdemo.domain.bar.Bar
This can be seen in the branch of the demo project here: https://github.com/gratiartis/multids-demo/tree/1-unnamed-entitymanager-beans
This is because in that example, Spring has wired up the beans relating to the "foodb" database, and Bar is not an entity in that database. Unfortunately the BarRepository has been wired up with the Foo entity manager.
We resolve this issue by naming all our beans in each of config class. i.e.
#Bean(name = "fooDataSource") public DataSource dataSource() { .. }
#Bean(name = "fooEntityManager") public EntityManager entityManager() { .. }
At this point if you were to run the tests in the project, you might see warnings such as:
No bean named 'entityManagerFactory' is defined.
This is because ... drumroll ... we do not have an EntityManagerFactory with the default name "entityManagerFactory". We have one called "fooEntityManagerFactory" and another called "barEntityManagerFactory". Spring is looking for something with a default name, so we need to instruct it to wire things up differently.
As it turns out, this is incredibly simple to do. We just need to put the correct references in the #EnableJpaRepositories annotation for each #Configuration class.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "fooEntityManagerFactory",
transactionManagerRef = "fooTransactionManager",
basePackages = {"com.sctrcd.multidsdemo.integration.repositories.foo"})
public class FooConfig {
// ...
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager",
basePackages = { "com.sctrcd.multidsdemo.integration.repositories.bar" })
public class BarConfig {
// ...
}
As you can see, each of these #EnableJpaRepositories annotations defines a specific named EntityManagerFactory and PlatformTransactionManager. They also specify which repositories should be wired up with those beans. In the example, I have put the repositories in database-specific packages. It is also possible to define each individual repository by name, by adding includeFilters to the annotation, but by segregating the repositories by database, I believe that things should end up more readable.
At this point you should have a working application using Spring Data repositories to manage entities in two separate databases. Feel free to grab the project from the link above and run the tests to see this happening. Hopefully this answer is useful to more folks, as I have spent a decent amount of time working out to do this as cleanly as possible with as little code as I could manage. Any ideas for improvement of the answer or demo project are welcome.
You may try put it on two #Configuration classes (one #EnableJpaRepositories per #Configuration).

Categories

Resources