Reputation: 458
I have tried for literally hours to get this working, looking at the docs:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html
...various stackoverflow questions and as much other stuff as I can find. But, this is proving elusive (read, making me want to bash my head against a wall). Any help would be so, so welcome!
I need to connect to two different databases (sounds simple enough?) and I have a Spring Boot web application using the spring-boot-starter-data-jpa
dependency which got things off the ground very nicely with a single data source. Now I need to talk to a second database and things have not been working. I thought I had it working for a while, but it turned out that everything was going to the primary database.
I'm currently trying to get this working on a separate 'cut down' project to try and reduce the number of moving parts, still not working though.
I have two @Configuration
classes - one for each data source, here's the first:
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "firstEntityManagerFactory",
transactionManagerRef = "firstTransactionManager",
basePackages = {"mystuff.jpaexp.jpatest"})
public class DataConfiguration {
@Bean
@Primary
@ConfigurationProperties(prefix = "app.datasource1")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@ConfigurationProperties("app.datasource1")
public DataSource firstDataSource() {
return firstDataSourceProperties().initializeDataSourceBuilder().
driverClassName("org.postgresql.Driver").
url("jdbc:postgresql://localhost:5432/experiment1").
username("postgres").
password("postgres").
build();
}
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean firstEntityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("mystuff.jpaexp.jpatest");
factory.setDataSource(firstDataSource());
factory.setPersistenceUnitName("ds1");
return factory;
}
@Primary
@Bean
public PlatformTransactionManager firstTransactionManager() {
return new JpaTransactionManager();
}
}
and here's the second:
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "secondEntityManagerFactory",
transactionManagerRef = "secondTransactionManager",
basePackages = {"mystuff.jpaexp.jpatest2"})
public class Otherconfiguration {
@Bean
@ConfigurationProperties(prefix = "app.datasource2")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@ConfigurationProperties("app.datasource2")
public DataSource secondDataSource() {
return secondDataSourceProperties().initializeDataSourceBuilder().
driverClassName("org.postgresql.Driver").
url("jdbc:postgresql://localhost:5432/experiment2").
username("postgres").
password("postgres").
build();
}
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("mystuff.jpaexp.jpatest2");
factory.setDataSource(secondDataSource());
factory.setPersistenceUnitName("ds2");
return factory;
}
@Bean
public PlatformTransactionManager secondTransactionManager() {
return new JpaTransactionManager();
}
}
In each of the two packages mystuff.jpaexp.jpatest
and mystuff.jpaexp.jpatest2
I have a simple @Entity
and CrudRepository
that should go together with the first and second datasources respectively.
I then have a main()
to test things out:
@SpringBootApplication
@EnableAutoConfiguration(exclude = {WebMvcAutoConfiguration.class})
@ComponentScan("mystuff.jpaexp.*")
public class SpringbootCommandLineApp implements CommandLineRunner {
private final MyRepository myRepository;
private final OtherRepo otherRepo;
@Autowired
public SpringbootCommandLineApp(MyRepository myRepository, OtherRepo otherRepo) {
this.myRepository = myRepository;
this.otherRepo = otherRepo;
}
public static void main(String[] args) {
new SpringApplicationBuilder(SpringbootCommandLineApp.class)
.web(false)
.run(args);
}
@Override
public void run(String... args) throws Exception {
myRepository.save(new MyEntity("Goodbye or hello"));
myRepository.save(new MyEntity("What?"));
myRepository.save(new MyEntity("1,2,3..."));
myRepository.findAll().forEach(System.out::println);
otherRepo.save(new MyEntity2("J Bloggs"));
otherRepo.save(new MyEntity2("A Beecher"));
otherRepo.save(new MyEntity2("C Jee"));
otherRepo.findAll().forEach(x -> {
System.out.println("Name:" + x.getName() + ", ID: " + x.getId());
});
}
}
And lastly, some props in application.properties
:
app.datasource1.driver-class-name=org.postgresql.Driver
app.datasource1.url=jdbc:postgresql://localhost:5432/experiment1
app.datasource1.username=postgres
app.datasource1.password=postgres
app.datasource2.driver-class-name=org.postgresql.Driver
app.datasource2.url=jdbc:postgresql://localhost:5432/experiment2
app.datasource2.username=postgres
app.datasource2.password=postgres
These have absolutely no effect -- things appear to still be configured by spring.datasource.*
instead, which is obviously no use.
Final output:
2018-05-25 17:04:00.797 WARN 29755 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Tomcat.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
2018-05-25 17:04:00.800 INFO 29755 --- [ main] utoConfigurationReportLoggingInitializer :
Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
2018-05-25 17:04:00.803 ERROR 29755 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
Cannot determine embedded database driver class for database type NONE
Action:
If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
Process finished with exit code 1
I know there's a lot of code here, sorry and thanks!
Upvotes: 3
Views: 7714
Reputation: 458
Well, it took a long time, I think there were multiple subtle problems and also some bits that could be simplified a little:
DataSourceProperties
was required - both datasources can use it@ConfigurationProperties
is needed on the DataSource bean definition, not the DataSourceProperties
bean@ComponentScan("mystuff.jpaexp.*")
annotation was incorrect, and replacing this with simply @ComponentScan
seemed to fix picking up of some of the bean definitionsEntityManagerFactor
into the JpaTransactionManager
definition: return new JpaTransactionManager(secondEntityManagerFactory().getObject());
JpaProperties
bean, and explicity pulled those properties into a VendorAdapter
The VendorAdapter/JpaProperties changes looked like this (it seems odd that JpaProperties is vendor-independent yet it has a hibernateProperties on it?!):
@Bean
public LocalContainerEntityManagerFactoryBean secondEntityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("...entity-package...");
factory.setDataSource(secondDataSource());
Map<String, String> props = new HashMap<>();
props.putAll(secondJpaProperties().getProperties());
props.putAll(secondJpaProperties().getHibernateProperties(secondDataSource()));
factory.setJpaPropertyMap(props);
factory.setPersistenceUnitName("ds2");
return factory;
}
@Bean
@ConfigurationProperties(prefix = "jpa.datsource2")
public JpaProperties secondJpaProperties() {
return new JpaProperties();
}
I think this was enough to get things going. In addition, the ever-so-clever defaulting of various properties to make an embedded H2 instance spring to life, no longer worked, so I also had to be explicit about all the DB properties:
jpa.datasource1.hibernate.ddl-auto=create
app.datasource1.driver-class-name=org.h2.Driver
app.datasource1.url=jdbc:h2:mem:primary
app.datasource1.username=
app.datasource1.password=
jpa.datasource2.hibernate.ddl-auto=create
app.datasource2.driver-class-name=org.h2.Driver
app.datasource2.url=jdbc:h2:mem:view
app.datasource2.username=
app.datasource2.password=
Upvotes: 1