问题描述
我正在尝试使用两个数据源设置一个 Spring Boot 项目。 第一个数据源是 H2 数据库,第二个是 MapRepository。 两个存储库将共享相同的实体。
我可以设法设置一个包含两个 H2 数据库的项目,但是当我尝试设置一个 MapRepository 而不是第二个 H2 数据源时,我收到以下错误:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__,| / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.0)
2021-01-12 10:57:16.610 INFO 26672 --- [ main] ch.getonline.springtestapp.App : Starting App using Java 15.0.1 on nbbetina1 with PID 26672 (C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes started by BetinaHiestand in C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app)
2021-01-12 10:57:16.612 INFO 26672 --- [ main] ch.getonline.springtestapp.App : The following profiles are active: dev
2021-01-12 10:57:17.070 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found,entering strict repository configuration mode!
2021-01-12 10:57:17.070 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Map repositories in DEFAULT mode.
2021-01-12 10:57:17.092 INFO 26672 --- [ main] .RepositoryConfigurationExtensionSupport : Spring Data Map - Could not safely identify store assignment for repository candidate interface ch.getonline.springtestapp.storage.repositories.map.MapRepository. If you want this repository to be a Map repository,consider extending one of the following types with your repository: org.springframework.data.keyvalue.repository.keyvalueRepository.
2021-01-12 10:57:17.092 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 15 ms. Found 0 Map repository interfaces.
2021-01-12 10:57:17.094 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found,entering strict repository configuration mode!
2021-01-12 10:57:17.094 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-01-12 10:57:17.111 INFO 26672 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 14 ms. Found 1 JPA repository interfaces.
2021-01-12 10:57:17.654 INFO 26672 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-01-12 10:57:17.661 INFO 26672 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-01-12 10:57:17.661 INFO 26672 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.39]
2021-01-12 10:57:17.758 INFO 26672 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-01-12 10:57:17.758 INFO 26672 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1105 ms
2021-01-12 10:57:17.976 INFO 26672 --- [ main] o.s.b.a.h2.H2ConsoleAutoConfiguration : H2 console available at '/dbadmin'. Database available at 'jdbc:h2:mem:db1dev'
2021-01-12 10:57:18.058 INFO 26672 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-01-12 10:57:18.099 INFO 26672 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.4.23.Final
2021-01-12 10:57:18.198 INFO 26672 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-01-12 10:57:18.324 INFO 26672 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Hibernate:
drop table if exists "BasicEntity" CASCADE
Hibernate:
create table "BasicEntity" (
"DNA" binary not null,"id" varchar(255),"type" varchar(255),primary key ("DNA")
)
2021-01-12 10:57:18.759 INFO 26672 --- [ main] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-01-12 10:57:18.765 INFO 26672 --- [ main] j.LocalContainerEntityManagerfactorybean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.787 INFO 26672 --- [ main] ch.getonline.springtestapp.App : SpringTestApplication is starting...
2021-01-12 10:57:18.931 WARN 26672 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appContext': Unsatisfied dependency expressed through field 'entityStorage'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'entityStorageHandler' defined in file [C:\Users\BetinaHiestand\eclipse20-workspace\spring-test-app\target\classes\ch\getonline\springtestapp\storage\handlers\EntityStorageHandler.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.NoSuchBeanDeFinitionException: No qualifying bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
2021-01-12 10:57:18.931 INFO 26672 --- [ main] j.LocalContainerEntityManagerfactorybean : Closing JPA EntityManagerFactory for persistence unit 'default'
2021-01-12 10:57:18.933 INFO 26672 --- [ main] o.apache.catalina.core.StandardService : Stopping service [Tomcat]
2021-01-12 10:57:18.944 INFO 26672 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-01-12 10:57:18.956 ERROR 26672 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION Failed TO START
***************************
Description:
Parameter 2 of constructor in ch.getonline.springtestapp.storage.handlers.EntityStorageHandler required a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' that Could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'ch.getonline.springtestapp.storage.repositories.map.MapRepository' in your configuration.
我已经尝试添加 ComponentScan 并向 MapRepository 添加存储库注释,但无法弄清楚为什么没有为其创建 bean。两个存储库都在单独的包中,这些包被设置为 EnableMapRepositories/EnableJpaRepositories 注释的 basePackages。对于 sqlRepository,我创建了一个包含驱动程序属性等的配置类。我不确定 MapRepositories 是否也需要这样的东西,并且找不到有关它的有用文档。
我对 Spring Boot 并没有真正的经验,因此第一个问题是是否可以进行这样的设置?如果是,我应该如何配置它?
应用启动:
@SpringBootApplication
@ComponentScan (basePackages = {"ch.getonline.springtestapp"})
@EntityScan("ch.getonline.springtestapp.entity.types")
@EnableMapRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.map")
@EnableJpaRepositories(basePackages = "ch.getonline.springtestapp.storage.repositories.sql",entityManagerFactoryRef = "sqlDatabaseEntityManager",transactionManagerRef = "sqlDatabaseTransactionManager")
public class App {
// Logger setup (Per class)
private static final Logger log = LoggerFactory.getLogger(App.class);
/*
* Application start
*/
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(App.class,args);
System.out.println("App context in main: " + ctx.getdisplayName());
}
地图存储库:
package ch.getonline.springtestapp.storage.repositories.map;
import org.springframework.stereotype.Repository;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
@Repository("mapRepository")
public interface MapRepository extends EntityRepository {
}
实体存储库:
package ch.getonline.springtestapp.storage.repositories;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.norepositoryBean;
import ch.getonline.springtestapp.entity.types.BasicEntity;
@norepositoryBean
public interface EntityRepository extends CrudRepository<BasicEntity,Long>{
//Entity findByUuid(UUID id);
}
我试图访问两个存储库的StorageHandler:
package ch.getonline.springtestapp.storage.handlers;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import ch.getonline.springtestapp.AppContext;
import ch.getonline.springtestapp.entity.attribute.Attribute;
import ch.getonline.springtestapp.entity.types.BasicEntity;
import ch.getonline.springtestapp.storage.StorageHandler;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
import ch.getonline.springtestapp.storage.repositories.map.MapRepository;
import ch.getonline.springtestapp.storage.repositories.sql.sqlRepository;
/** Entity Storage
* <br>
*
* - Coordinates saving,loading,updating of Entities over different Repositories
*
*
* @author sigi
*
*/
@Component
public class EntityStorageHandler implements StorageHandler<BasicEntity,Long> {
// Logger
private static final Logger log = LoggerFactory.getLogger(EntityStorageHandler.class);
private final AppContext app;
private final Map<String,EntityRepository> repos;
EntityStorageHandler(AppContext app,sqlRepository sqlRepo,MapRepository mapRepo) {
this.app = app;
this.repos = new HashMap<String,EntityRepository>();
this.repos.put("sql",sqlRepo);
this.repos.put("map",mapRepo);
}
//StorageHandler start hook
public void run(String... args) throws Exception {
//Print all configs for the key app in the config
StringBuilder appConfig = new StringBuilder();
for(Entry<String,Object> entry : this.app.getConfig().entrySet()) {
appConfig.append("\nkey: " + entry.getKey() + " value: " + entry.getValue());
}
log.info(appConfig.toString());
//Write demo Entity into db
BasicEntity e1 = new BasicEntity();
e1.setId("1");
e1.setType("Type1");
this.repos.get("sql").save(e1);
BasicEntity e2 = new BasicEntity();
e2.setId("2");
e2.setType("Type2");
this.repos.get("sql").save(e2);
BasicEntity e3 = new BasicEntity();
e3.setId("3");
e3.setType("Type3");
this.repos.get("map").save(e2);
}
}
基本实体:
package ch.getonline.springtestapp.entity.types;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.springframework.data.keyvalue.annotation.KeySpace;
import ch.getonline.springtestapp.entity.GenericEntity;
/**
* Basic Entity Implementation
*
* @author sigi
*
*/
@Entity
@KeySpace("basicEntities")
public class BasicEntity extends GenericEntity {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
UUID DNA;
}
sql 配置:
package ch.getonline.springtestapp.configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerfactorybean;
import org.springframework.orm.jpa.vendor.HibernateJpavendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
@Configuration
public class sqlConfiguration {
@Autowired
private Environment env;
public sqlConfiguration() {
super();
}
@Primary
@Bean
public LocalContainerEntityManagerfactorybean sqlDatabaseEntityManager() {
final LocalContainerEntityManagerfactorybean em = new LocalContainerEntityManagerfactorybean();
em.setDataSource(sqlDataSource());
em.setPackagesToScan("ch.getonline.springtestapp.entity.types");
final HibernateJpavendorAdapter vendorAdapter = new HibernateJpavendorAdapter();
em.setJpavendorAdapter(vendorAdapter);
final HashMap<String,Object> properties = new HashMap<String,Object>();
properties.put("hibernate.hbm2ddl.auto",env.getProperty("spring.jpa.hibernate.ddl-auto"));
properties.put("hibernate.dialect",env.getProperty("spring.jpa.database-platform"));
properties.put("hibernate.jdbc.batch_size",env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size"));
properties.put("hibernate.order_inserts",env.getProperty("spring.jpa.properties.hibernate.order_inserts"));
properties.put("hibernate.order_updates",env.getProperty("spring.jpa.properties.hibernate.order_updates"));
properties.put("hibernate.jdbc.batch_versioned_data",env.getProperty("spring.jpa.properties.hibernate.jdbc.batch_versioned_data"));
properties.put("hibernate.generate_statistics",env.getProperty("spring.jpa.properties.hibernate.generate_statistics"));
properties.put("hibernate.id.new_generator_mappings",env.getProperty("spring.jpa.properties.hibernate.id.new_generator_mappings"));
properties.put("hhibernate.cache.use_second_level_cache",env.getProperty("spring.jpa.properties.hibernate.cache.use_second_level_cache"));
properties.put("hibernate.globally_quoted_identifiers",env.getProperty("spring.jpa.properties.hibernate.globally_quoted_identifiers"));
properties.put("hibernate.format_sql",env.getProperty("spring.jpa.properties.hibernate.format_sql"));
properties.put("hibernate.show_sql",env.getProperty("spring.jpa.properties.hibernate.show_sql"));
properties.put("hibernate.use_sql_comments",env.getProperty("spring.jpa.properties.hibernate.use_sql_comments"));
properties.put("hibernate.type",env.getProperty("spring.jpa.properties.hibernate.type"));
properties.put("hibernate.naming.physical-strategy",env.getProperty("spring.jpa.hibernate.naming.physical-strategy"));
em.setJpaPropertyMap(properties);
return em;
}
@Primary
@Bean
public DataSource sqlDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("spring.datasource.driverClassName"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
@Primary
@Bean
public PlatformTransactionManager sqlDatabaseTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(sqlDatabaseEntityManager().getobject());
return transactionManager;
}
}
sql 存储库
package ch.getonline.springtestapp.storage.repositories.sql;
import ch.getonline.springtestapp.storage.repositories.EntityRepository;
public interface sqlRepository extends EntityRepository {
}
应用程序.yml
#Debug mode
debug: false
#External config
spring:
#Basic setup
profiles.active: dev
config:
import: optional:classpath:config/app.properties,optional:classpath:config/config.yml
#Localization
messages:
basename: config.i18n.messages
#db
h2:
console:
path: /dbadmin
enabled: true
settings:
web-allow-others: true
datasource:
username: inmemory
password: inmemory
driverClassName: org.h2.Driver
port: 8080
#jpa
jpa:
hibernate:
ddl-auto: create
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
id:
new_generator_mappings: true
cache:
use_second_level_cache: true
order_inserts: true
order_updates: true
globally_quoted_identifiers: true
generate_statistics: false
show_sql: true
format_sql: true
use_sql_comments: true
type: trace
jdbc:
batch_size: 500
batch_versioned_data: false
tmp:
use_jdbc_Metadata_defaults: false
database-platform: org.hibernate.dialect.H2Dialect
解决方法
正如我在评论中所写的那样,将两个不同的数据源与一个 Spring Boot 服务一起使用是很不寻常的。但在某些情况下可能需要这样做,以下是如何以干净的方式实现它:
请记住,我的回答主要来自Baeldung tutorial
考虑拥有这样的数据源:
spring.datasource.jdbcUrl = [url]
spring.datasource.username = [username]
spring.datasource.password = [password]
现在,我们要添加第二个,最好使用相同的语法:
spring.second-datasource.jdbcUrl = [url]
spring.second-datasource.username = [username]
spring.second-datasource.password = [password]
要同时使用这两个配置,我们只需使用 Datasource
Bean 创建两个配置类 - 注意前缀注释:
@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
}
您可以仅创建一个配置类并在其中提供两个 bean。
有关详细信息,请参阅 Spring JPA – Multiple Databases