私は、複数のデータソースを持つに関してはポストをたくさん読んだが、私は複数のデータソースを設定する助けを探していないよと私の状況は少し独特かもしれ感じるのではなく、助けを使用する複数のデータソースを設定します単一ドメイン(エンティティ)オブジェクト。
ユースケースシナリオ
私たちは、各システムが彼の会社の異なる部門を表して、私の組織内のデータを除いて、二つの同一の金融システムを持っています。各部門は、同一のスキーマと完全に独立したデータベースを有しています。私は両方のデータベースをインタフェースする単一のアプリケーションを構築する必要があります。ユーザがログインすると、彼らはへのアクセスを必要とし、そのデータ要求を続ける会社のどの部門を選択しますとき。除算を含むクエリのparamに基づいて、アプリケーションはドメインオブジェクト内の正しいデータソースを選択し、適切なデータを引き戻す必要があります。
Groovyで/ Iは、複数のデータソースを持つ単一のドメインを持つことができましたGrailsの。
例。
static mapping = {
datasources (['datasourceA','datasourceB'])
}
そして、クエリパラメータに基づいて、私は、データソースが使用されることになっていたかを決定することができました。
例
Person."${division.datasource}".findAllByRunId
私はSpringBoot 2.2.0でこれと同じ動作を実現する方法を思ったんだけど?
データベース
Finance_System_A (datasourceA)
- Person:
- Name: John
- ID: 1
Finance_System_B (datasourceB)
- Person:
- Name: Dave
- ID: 1
SpringBootアプリケーション
SpringBoot Person Domain
- Person:
- Name:
- ID:
クエリの例(Grailsのスタイル)
Person.{"datasourceA"}.findById(1) = John
Person.{"datasourceB"}.findById(1) = Dave
私は、このタスクを達成するためにカップルのソリューションを考え出すことができました。
オプション1 - マルチテナンシー
私の意見では、マルチテナントアプローチは、まだ自分のデータベースを持っている各テナントを可能にしながら、クリーンなアプローチであるように思われます。
ディレクトリ構造
org.company.project
- ApplicationMain
|_config
- DatasourceConfiguration
- WebMvcConfig
|_routing
- TenantContext
- TenantInterceptor
- TenantSourceRouter
|_domain
- Person
|_repository
|_ PersonRepository
|_web
-APIController
DatasourceConfiguration
@Configuration
@EnableTransactionManagement
public class DatasourceConfiguration {
@Resource
private Environment env;
@Bean
public DataSource dataSource() {
AbstractRoutingDataSource dataSource = new TenantSourceRouter();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("ALBANY", albanyDatasource());
targetDataSources.put("BUFFALO", buffaloDatasource());
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(albanyDatasource());
return dataSource;
}
public DataSource albanyDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("company.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("company.datasource.albany.jdbc-url"));
dataSource.setUsername(env.getProperty("company.datasource.albany.username"));
dataSource.setPassword(env.getProperty("company.datasource.albany.password"));
return dataSource;
}
public DataSource buffaloDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("company.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("company.datasource.buffalo.jdbc-url"));
dataSource.setUsername(env.getProperty("company.datasource.buffalo.username"));
dataSource.setPassword(env.getProperty("company.datasource.buffalo.password"));
return dataSource;
}
}
ドメインエンティティ - 人
@Entity
public class Person {
@Id
private String id;
private String name;
}
人リポジトリ
public interface PersonRepository extends JpaRepository<Person, String> {
}
TenantContext
public class TenantContext {
private static final ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setCurrentTenant(String tenant) {
Assert.notNull(tenant, "clientDatabase cannot be null");
currentTenant.set(tenant);
}
public static String getClientDatabase() {
return currentTenant .get();
}
public static void clear() {
currentTenant .remove();
}
}
TenantContext
public class TenantSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getClientDatabase();
}
}
TenantInterceptorは -私はあなたではなく、アクションごとコントローラのアクションでこれに対処することよりも、目的のテナント、「ALBANY」または「BUFFALO」とリクエストヘッダ「X-TenantID」を設定しますグローバルインターセプタを追加することにしました。
@Component
public class TenantInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String tenantId = request.getHeader("X-TenantID");
TenantContext.setCurrentTenant(tenantId);
return true;
}
@Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
TenantContext.clear();
}
}
WebMvcConfig -今、私たちはWebMvcとのインターセプタを登録する必要があります
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TenantInterceptor());
}
}
APIController -最後に、私たちは私たちのリポジトリにアクセスします私たちのコントローラを作成します。
@RestController
@RequestMapping("/api")
public class APIController {
@Autowired
private PersonRepository personRepository;
@GetMapping("/{id}")
public Optional<Person> get(@PathVariable String id) {
return personRepository.findById(id);
}
@GetMapping("/")
public List<Person> getAll() {
return personRepository.findAll();
}
}
application.yml
company:
datasource:
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
albany:
jdbc-url: ***
username: ***
password: ***
buffalo:
jdbc-url: ***
username: ***
password: ***
オプション2 - 複数のリポジトリを持つ、より伝統的なマルチテナント
ディレクトリ構造
org.company.project
- ApplicationMain
|_config
- AlbanyDbConfiguration (datasource 1)
- BuffaloDbConfiguration (datasource 2)
|_domain
- Person
|_repository
|_ albany
- PersonRepositoryAlbany (repository for datasource 1)
|_ buffalo
- PersonRepositoryBuffalo (repository for datasource 2)
|_web
-APIController
application.yml
spring:
datasource:
jdbc-url: ***
username: ***
password: ***
buffalo:
datasource:
jdbc-url: ***
username: ***
password: ***
ドメインエンティティ - 人
@Entity
public class Person {
@Id
private String id;
private String name;
}
リポジトリ - PersonRepositoryAlbany *
public interface PersonRepositoryAlbany extends JpaRepository<Person, String>, JpaSpecificationExecutor<Person> {
}
リポジトリ - PersonRepositoryBuffalo *
public interface PersonRepositoryBuffalo extends JpaRepository<Person, String>, JpaSpecificationExecutor<Person> {
}
データソース設定 - AlbanyDbConfiguration
@Configuration
@EnableJpaRepositories(
basePackages = { "org.company.project.repository.albany"},
entityManagerFactoryRef = "albanyEntityManagerFactory",
transactionManagerRef = "albanyTransactionManager")
public class AlbanyDbConfiguration {
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "albanyEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("org.company.project.domain")
.properties(jpaProperties())
.build();
}
public Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<>();
props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
return props;
}
@Primary
@Bean(name = "albanyTransactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("albanyEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
データソース設定 - BuffaloDbConfiguration
@Configuration
@EnableJpaRepositories(
basePackages = { "org.company.project.repository.buffalo"},
entityManagerFactoryRef = "buffaloEntityManagerFactory",
transactionManagerRef = "buffaloTransactionManager")
public class BuffaloDbConfiguration {
@Bean(name = "buffaloDataSource")
@ConfigurationProperties(prefix = "buffalo.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "buffaloEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(EntityManagerFactoryBuilder builder, @Qualifier("buffaloDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("org.company.project.domain")
.properties(jpaProperties())
.build();
}
public Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<>();
props.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
return props;
}
@Bean(name = "buffaloTransactionManager")
public PlatformTransactionManager transactionManager(@Qualifier("buffaloEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
ウェブコントローラ - APIController
@EnableTransactionManagement
@RestController
@RequestMapping("/api")
public class APIController {
@Autowired
private PersonRepositoryAlbany personRepositoryAlbany;
@Autowired
private PersonRepositoryBuffalo personRepositoryBuffalo;
@GetMapping("/albany")
public List<Person> albany() {
return getPersonsAlbany();
}
@GetMapping("/buffalo")
public List<Person> buffalo() {
return getPersonsBuffalo();
}
@Transactional("albanyTransactionManager")
public List<Person> getPersonsAlbany() {
return personRepositoryAlbany.findAll();
}
@Transactional("buffaloTransactionManager")
public List<Person> getPersonsBuffalo() {
return personRepositoryBuffalo.findAll();
}
}