I have been stuck trying to get multiple datasources hooked up with spring boot with a couple custom query implementations. When I call those customized queries the correct entity manager is used however when I use the basic crudrepository functions, it uses the primary manager and produces "Not a managed type" error.
Here are my two sources config:
#Configuration
public class DataSourceConfig {
#Bean(name="DB1")
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#PersistenceContext(unitName = "primary")
#Qualifier("entityManagerFactory")
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(primaryDataSource())
.persistenceUnit("primary")
.packages("com.company.monitoring.db.primary")
.build();
}
and
#Configuration
public class AuditLogDataSourceConfig {
#PersistenceContext(unitName = "auditlog")
#Bean(name="DB2")
#ConfigurationProperties(prefix="auditlog.datasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#PersistenceContext(unitName = "secondary")
#Qualifier("secondaryEntityManager")
#Bean(name = "secondaryEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(secondaryDataSource())
.persistenceUnit("secondary")
.packages("com.company.monitoring.db.secondary" )
.build();
}
}
Repo in controller that uses the wrong datasource:
import com.company.monitoring.db.secondary.repository.FileAuditLogDAO;
...
#RequestMapping("/eventdetails/")
public class EventDetailsController {
...
#Autowired
private FileAuditLogDAO repository;
...
FileAuditLogDAO is in package com.company.monitoring.db.secondary.repository
and FileAuditLogEntity is in package com.company.monitoring.db.secondary.entity.
I know the primary is being used since when I add "com.company.monitoring.db.secondary" to the primarys packages I no longer get the Not a managed type: class com.company.monitoring.db.secondary.entity.FileAuditLogEntity. I have tried various places for #EnableJPARepositories with no success. What am I doing wrong here?
Related
My project talks to at least two databases with the requirement that entity ids be exposed. I've tried most solutions online but kept getting this error: Field manager in ... required a single bean, but 2 were found:...
How do I achieve this requirement using this simplified config class?
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages={...}
public class MyConfig{
#Autowired
private EntityManager manager;
#Bean
#Primary
#ConfigurationProperties("spring.datasource.akad")
public DataSourceProperties akadDataSourceProps(){
return new DataSourceProperties();
}
#Primary
#Bean
#ConfigurationProperties("spring.datasource.akad")
public DataSource akaData(){
return akadDataSourceProps().initialzeDataSourceBuilder.type(HikariDataSource.class).build();
}
#Primary
#Bean(name = "akadEntityFac")
public LocalEntityManagerFactoryBean akaFact(EntityManagerFactoryBuilder build){
String [] packgesToScan = {...};
return builder.datasource(akad()).packagesToScan).build();
}
#Primary
#Bean
public PlatformTransactionManager akadMan("akadEntityFac") LocalEntityManagerFactoryBean akadManFac){
return JpaTransactionManager(akadManFac.getObject());
//this method below throws the error
#Primary
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors){
config.exposeIdsFor(manager.getMetaModel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new));
}
//If I comment the last method, the error would be cleared but the entity ids wouldn't get exposed.
}
I have an already working Jpa Repository exposing a native query method
myPrimary.datasource.jdbc-url=jdbc:sqlserver://host:1433;databaseName=dbName
myPrimary.datasource.username=user
myPrimary.datasource.password=pwd
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "myPrimaryEntityManagerFactory",
transactionManagerRef = "myPrimaryTransactionManager",
basePackages = {"myPrimaryPackage"}
)
#EnableTransactionManagement
public class PrimaryDaoConfig {
#Primary
#Bean(name = "myPrimaryDataSource")
#ConfigurationProperties(prefix = "myPrimary.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "myPrimaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, #Qualifier("myPrimaryDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("myPrimaryPackage").persistenceUnit("myPrimary").build();
}
#Primary
#Bean(name = "myPrimaryTransactionManager")
public PlatformTransactionManager transactionManager(#Qualifier("myPrimaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
#Repository
public interface MyPrimaryRepository<T extends MyPrimaryEntity, ID extends MyPrimaryId> extends org.springframework.data.repository.Repository<T, ID> {
String MY_CUSTOM_NATIVE_QUERY = "...";
#Query(
value = MY_CUSTOM_NATIVE_QUERY,
nativeQuery = true
)
List<MyPrimaryEntity> findAll();
}
#RunWith(SpringRunner.class)
#ActiveProfiles("dev")
#DataJpaTest
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class RepositoryTest {
#Autowired
private MyPrimaryRepository myPrimaryRepository;
#Test
public void findAll() {
assertNotNull(myPrimaryRepository);
List<MyPrimaryEntity> entities = myPrimaryRepository.findAll();
assertEquals(332, entities.size());
}
}
I'd like to use it in Spring IntegrationFlows.
From the docs I found, there's a way to pass an EntityManagerFactory like this
#Bean
public IntegrationFlow dbInboundFlow() throws Exception {
return IntegrationFlows
.from(Jpa
.inboundAdapter(myPrimaryEntityManagerFactory)
.nativeQuery(MyPrimaryRepository.MY_CUSTOM_NATIVE_QUERY)
.entityClass(MyPrimaryEntity.class),
e -> e.poller(Pollers.fixedDelay(10000))
)
.handle(jobLaunchingMessageHandler())
.channel("nullChannel")
.get();
}
My unit test is successful, but when trying to pass the entityMangerFactory in the IntegrationFlows, I get the
Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
error.
I tried to add
spring.jpa.hibernate.dialect=org.hibernate.dialect.SQLServer2012Dialect
but no luck either.
So is there a way to pass a repository instead of the entityManagerFactory ?
For now I only can answer that if you have already a JPA repository, you should use a generic method invocation MessageSource instead of that Jpa.inboundChannelAdapter(). The last one technically does for us whatever repository does but more in messaging manner.
The issue with dialect not clear for me: looks like you use the same. The dialect if not set explicitly is derived from the JpaVendorAdapter. The HibernateJpaConfiguration should do a stuff for us on the matter. Not clear if #DataJpaTest does for us everything what we needed.
Nothing to do with Spring Integration though. I'd like to see some simple project from you to play with locally on my side.
I wanted to declare two DataSource beans and use one of them dynamically using AbstractRoutingDataSource, which is declared as #Primary bean. Surprisingly, I was not able to run my application because of cyclic dependency:
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration
┌─────┐
| dataSource defined in <myclass>
↑ ↓
| readOnlyDataSource defined in <myclass>
↑ ↓
| org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker
└─────┘
It is cause because of this implementation:
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
#Primary
DataSource dataSource(#Qualifier("firstDS") DataSource firstDS,
#Qualifier("secondDS") DataSource secondDS) {
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setCurrentDS(firstDS);
return ds;
}
#Bean("firstDS")
public DataSource firstDS(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
#Bean("secondDS")
public DataSource secondDs(DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
class MyRoutingDataSource extends AbstractRoutingDataSource {
private DataSource currentDS;
public void setCurrentDS(DataSource currentDS) {
this.currentDS = currentDS;
}
#Override
protected Object determineCurrentLookupKey() {
return currentDS;
}
}
}
Please note that I don't want to exclude DataSourceAutoConfiguration - it provides some additional functionally that I want to use in my project (e.g. DataSourceInitializer).
Could you please explain to me why it does not work? I feel that this error message is misleading. There is no cyclic dependency between HibernateJpaConfiguration and DataSourceInitializerInvoker. Both of them uses DataSource which primary definition I provide.
There is full project with that issue: https://github.com/kozub/spring-dependency-management-bug
I ran into the same problem you have, with the difference that I am not using DataSourceAutoConfiguration.
I'm not a Spring expert, so I can't tell you the root cause. But I was able to get my code to work by going from something like this, which is similar to what you posted:
#Bean
#Primary
DataSource dataSource(#Qualifier("firstDS") DataSource firstDS,
#Qualifier("secondDS") DataSource secondDS) {
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setFirstDS(firstDS);
ds.setSecondDs(secondDS);
return ds;
}
#Bean("firstDS")
public DataSource firstDS() {
return /*create first DS*/
}
#Bean("secondDS")
public DataSource secondDs(DataSourceProperties properties) {
return /*create second DS*/
}
To this:
#Bean
DataSource dataSource() {
DataSource first = /*create first DS*/
DataSource second = /*create second DS*/
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setFirstDS(first);
ds.setSecondDs(second);
return ds;
}
As you can see, I was able to solve the problem by only having one Spring bean of type DataSource. I created the two "first" and "second" DataSources inside the method which creates the routing datasource so that they don't have to be Spring beans. Having only one bean of type DataSource got rid of my circular dependency error.
This solved my problem, but you also want to use DataSourceAutoConfiguration.
I think you may be able to achieve that with something like this:
#Bean
DataSource dataSource(#Qualifier("firstDSproperties") DataSourceProperties firstDSprops,
#Qualifier("secondDSproperties") DataSourceProperties secondDSprops) {
DataSource first = firstDSprops.initializeDataSourceBuilder().build();
DataSource second = secondDSprops.initializeDataSourceBuilder().build();
MyRoutingDataSource ds = new MyRoutingDataSource();
ds.setCurrentDS(firstDS);
return ds;
}
#Bean("firstDSproperties")
#ConfigurationProperties("datasource.first")
public DataSourceProperties firstDataSourceProperties() {
return new DataSourceProperties();
}
#Bean("secondDSproperties")
#ConfigurationProperties("datasource.second")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
What this code does is to make two beans of type DataSourceProperties rather than type DataSource. With DataSourceProperties beans you can still let Spring autowire your config without (hopefully) having the cyclical dependency problem caused by having multiple beans of type DataSource depending on each other.
I haven't tested doing this with DataSourceProperties since I am not using DataSourceAutoConfiguration in my code. But based on your code I think it might work.
There is slight a mistake here. Let me explain you with foo and bar DB example along with git repo reference.
Here is how your application.properties look like
# Oracle DB - "foo"
spring.datasource.url=jdbc:oracle:thin:#//db-server-foo:1521/FOO
spring.datasource.username=fooadmin
spring.datasource.password=foo123
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
# PostgreSQL DB - "bar"
bar.datasource.url=jdbc:postgresql://db-server-bar:5432/bar
bar.datasource.username=baradmin
bar.datasource.password=bar123
bar.datasource.driver-class-name=org.postgresql.Driver
Set the SQL Dialect to “default” in your application.properties to let Spring autodetect the different SQL Dialects of each datasource
spring.jpa.database=default
Package should look something like
src/main/java
- com.foobar
- foo
- domain
- repo
- bar
- domain
- repo
Here is the main part. Configuration classes
Foo Configuration class (Oracle)
package com.foobar;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.foobar.foo.repo" }
)
public class FooDbConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.foobar.foo.domain")
.persistenceUnit("foo")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Bar Configuration class (postgres)
package com.foobar;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager",
basePackages = { "com.foobar.bar.repo" }
)
public class BarDbConfig {
#Bean(name = "barDataSource")
#ConfigurationProperties(prefix = "bar.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "barEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
barEntityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("barDataSource") DataSource dataSource
) {
return
builder
.dataSource(dataSource)
.packages("com.foobar.bar.domain")
.persistenceUnit("bar")
.build();
}
#Bean(name = "barTransactionManager")
public PlatformTransactionManager barTransactionManager(
#Qualifier("barEntityManagerFactory") EntityManagerFactory
barEntityManagerFactory
) {
return new JpaTransactionManager(barEntityManagerFactory);
}
}
Your repositories would look something like
package com.foobar.bar.repo;
#Repository
public interface BarRepository extends JpaRepository<Bar, Long> {
Bar findById(Long id);
}
package com.foobar.foo.repo;
#Repository
public interface FooRepository extends JpaRepository<Foo, Long> {
Foo findById(Long id);
}
And you are done here.
You can refer code on github here
I want to have just one implementation of repository class for multiple datasources.
Imagine that there is 1 application for multiple countries (with their dbs) and all repositories are the same for all of those countries..
Basic structure looks like this:
org.company.name
.config.{country-shortcut}Config
.repository.{country-shortcut}.SameRepository{country-shortcut}
.. other parts ..
Repository class could look like this:
package org.company.name.repository.{country-shortcut}.SomethingRepository
public class SomethingRepository extends CrudRepository<Something, Long> {
... some methods ...
}
Let's now look at some implementation.. We take UK as a our country now..
Country repository class for some country..
package org.company.name.repository.uk.SomethingRepositoryUk
public class SomethingRepositoryUk extends SomethingRepository {}
Basic config for each datasource.. I have multiple configs (one for each country)..
package org.company.name.config.UkConfig
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactoryUK",
transactionManagerRef = "transactionManagerUK",
basePackages = {"org.company.name.repository.uk"})
public class UkConfig {
#Autowired
private Environment env;
#Bean(name = "dataSourceUk")
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
... datasource settings (url, user, pass, etc.) ...
return dataSource;
}
#Bean(name = "entityManagerFactoryUk")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSourceUk") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("org.company.name.repository.model")
.persistenceUnit("uk")
.build();
}
#Bean(name = "transactionManagerUk")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactoryUk") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Config is fine by me, but is it possible to do not have multiple repositories (each for country, everything the same..)? It's bothering me because it seems like an unclean implementation..
I have Spring Boot project with 2 database configs.
Primary DB config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(transactionManagerRef = "primaryTransactionManager", entityManagerFactoryRef = "primaryEntityManagerFactory", basePackages = { "com.example.repository.primary" })
public class PrimaryDbConfig {
#Primary
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.primary.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, #Qualifier("primaryDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.domain.primary").persistenceUnit("primary-persistence-unit").build();
}
#Primary
#Bean(name = "primaryTransactionManager")
public PlatformTransactionManager transactionManager(#Qualifier("primaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
And secondary:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "secondaryEntityManagerFactory", transactionManagerRef = "secondaryTransactionManager", basePackages = { "com.example.repository.secondary" })
public class SecondaryDbConfig {
#Bean(name = "secondaryDataSource")
#ConfigurationProperties(prefix = "spring.secondary.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder, #Qualifier("secondaryDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.domain.secondary").persistenceUnit("secondary-persistence-unit").build();
}
#Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager transactionManager(#Qualifier("secondaryEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
There is entity which is loaded with second DB:
#Entity
#Table(name = "users")
public class User {
#OneToMany(mappedBy = "user")
private List<Company> companies;
Using:
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
When I use UserRepository, User is retrieved but when I hit to load companies, I get com.sun.jdi.InvocationException occurred invoking method. - com.sun.jdi.InvocationException occurred invoking method. If I change SecondaryDbConfig to #Primary or add fetch = FetchType.EAGER then companies are retrieved as expected. I have tried adding #Transactional(transactionManager="secondaryTransactionManager") but it didn't help.
What did I miss? What's the proper approach to lazy load entity properties when using not primary database config?
I have resolved this issue by adding #Transactional over #Service class.
I was facing a similar issue. To enable lazy loading of JPA entity with multiple databases, you need to create seperate OpenEntityManagerInViewFilter beans for each database in your DBConfig classes.
I found the solution here:
spring-boot-jpa-multiple-data-sources
Search for the term "Lazy" in the article above.
Try to add #Transactional to your #Service method(s) or controller which retrieved list of company.
Good luck!