디렉토리
1, 소스 코드 분석
많은 특정 응용 프로그램 시나리오, 우리는 동적 데이터 소스를 사용해야합니다. 예를 별도의 읽기 및 쓰기, 및 기타 멀티 테넌트 (multi-tenant) 시나리오하십시오. 봄 부팅 + MyBatis로 + MySQL을 기반으로이 튜토리얼의 경우는 달성했다. 봄은 AbstractRoutingDataSource 구축 된 추상 클래스, 그것은 서로 다른 키에 따라 데이터 소스지도, 반환 서로 다른 복수의 데이터 소스로 구성 할 수 있습니다. 응용 프로그램이 먼저 키를 설정할 수 있도록 AbstractRoutingDataSource 또한 데이터 소스 인터페이스는, 당신은 데이터베이스에 액세스하기 위해 실제 AbstractRoutingDataSource에서 해당 데이터 소스가 지정된 데이터베이스에 대한 액세스를 얻을 수 있기 때문에. 다음에는 프로젝트에서 다중 데이터 소스 스위칭 소스 분석 :
추상 클래스 AbstractRoutingDataSource 반원 다음과 같이;
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
- targetDataSources는 키 매핑 및 데이터베이스 연결을 포함
- defaultTargetDataSource는 기본 연결을 식별
- resolvedDataSources 이 데이터 구조가 매핑 관계에서 구축, 데이터베이스 저장 구조는 데이터 소스와 targetDataSources에 의해 식별됩니다
이 클래스 determineTargetDataSource () 메소드는 getConnection () 메소드를 호출하여 데이터베이스에 연결하는 연결을 만들 수 있습니다.
public Connection getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username, String password) throws SQLException {
return this.determineTargetDataSource().getConnection(username, password);
}
우리는 데이터 소스에 연결된 컨테이너를 봄하기로 결정 determineTargetDataSource () 메소드를 찾습니다. , determineCurrentLookupKey () 메소드는 추상적 인 방법입니다, 우리는이 메소드를 오버라이드 (override)하는 상속 AbstractRoutingDataSource 추상 클래스에 필요합니다. 이 방법은 키를 반환하고, lookupKey에 할당. 값은 데이터 소스의 스위칭 기능을 달성하도록, 상기 키는 키 resolvedDataSources 소스 속성에 대응하는 값에 의해 획득 될 수있다 됨으로써 beanName에 빈에 핵심이다.
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
우리는 AbstractRoutingDataSource InitializingBean 표시 인터페이스와 구현 afterPropertiesSet 방법을 구현하는이 클래스를 찾기 위해 지속적으로 노력하고 있습니다. 빈은 특정 빈에 대해 수행 될 때 afterPropertiesSet 초기화 방법이 실행된다. 데이터 소스 콩이 동적으로 생성되기 때문에, 모든 필요 targetDataSources에 추가 한 다음 빈 업데이트를 알리기 위해 스프링에 대한 () afterPropertiesSet를 호출합니다. 따라서,이 resolvedDataSources targetDataSources의 속성에 대한 키 정보 기억 특성에있어서, 그래서 후속 호출한다.
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
} else {
this.resolvedDataSources = new HashMap(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = this.resolveSpecifiedLookupKey(key);
DataSource dataSource = this.resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
2, 프로젝트 구현
원리를 분석 한 후 우리가 먼저 구성 파일을 수정 단일 데이터 소스와 필드의 나머지 부분과 일치하는 사용자 정의 할 수 있습니다 실제 상황, 마스터와 슬레이브에 따라 구성된 두 개의 데이터 소스를 추가, 동적 데이터 소스 스위칭은 매우 간단 실현 것이 분명하다.
spring:
datasource:
master:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123465
jdbc-url: jdbc:mysql://localhost:3306/theme_weixin?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
slave:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123465
jdbc-url: jdbc:mysql://192.168.101.18:3306/theme_weixin?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
클래스가 시작 추가 (가) DataSourceAutoConfiguration.class = {} 제외 , 비활성화 기본 자동 구성 데이터 소스를. 기본 데이터 소스 구성이 자동으로 spring.datasource를 읽습니다. * 당신이 정의를 위해 해제 할 수 있도록 속성이 데이터 소스를 만듭니다.
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class WeixinApplication {
public static void main(String[] args) {
SpringApplication.run(WeixinApplication.class, args);
}
}
데이터 소스 타입 구성, 데이터 소스 주입 구성 속성 생성 마스터, 슬레이브 데이터 소스를 작성
@Configuration
public class DataSourceConfig {
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
@RefreshScope
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
@RefreshScope
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
속됨 MybatisAutoConfiguration는 데이터 소스 SqlSessionFactory는 상기 복수의 주입된다
@Configuration
public class MyBatisConfig extends MybatisAutoConfiguration {
@Resource(name = "masterDataSource")
private DataSource masterDataSource;
@Resource(name = "slaveDataSource")
private DataSource slaveDataSource;
public MyBatisConfig(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
super(properties, interceptorsProvider, typeHandlersProvider, languageDriversProvider, resourceLoader, databaseIdProvider, configurationCustomizersProvider);
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
// 重载父类 sqlSessionFactory init
return super.sqlSessionFactory(roundRobinDataSourceProxy());
}
private AbstractRoutingDataSource roundRobinDataSourceProxy() {
DynamicDataSource proxy = new DynamicDataSource();
Map<Object, Object> targetDataResources = new HashMap<>(2);
targetDataResources.put(DbContextHolder.DbType.MASTER,masterDataSource);
targetDataResources.put(DbContextHolder.DbType.SLAVE,slaveDataSource);
proxy.setDefaultTargetDataSource(masterDataSource);
proxy.setTargetDataSources(targetDataResources);
proxy.afterPropertiesSet();
return proxy;
}
}
AbstractRoutingDataSource 상속, DynamicDataSource 클래스를 작성, 재 작성 방법 determineCurrentLookupKey
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DbContextHolder.getDbType().key();
}
}
DbContextHolder은 다음과 같습니다 :
public class DbContextHolder {
public enum DbType{
Master("masterDataSource"),
Slave("slaveDataSource");
String beanName;
DbType(String beanName) {
this.beanName = beanName;
}
public String key() {
return this.beanName;
}
}
private static final ThreadLocal<DbType> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDbType(DbType dbType){
if(dbType==null){
throw new NullPointerException();
}else{
CONTEXT_HOLDER.set(dbType);
}
}
public static DbType getDbType(){
return CONTEXT_HOLDER.get()==null? DbType.MASTER:CONTEXT_HOLDER.get();
}
public static void clearDbType() {
CONTEXT_HOLDER.remove();
}
}
데이터 소스에있어서, 통화 전환, 또는 특수 모드 전환을 기초로 할 수있다. 예시적인 데이터 소스 스위칭 :
public String test() {
DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
TabCategory tabCategory=new TabCategory();
tabCategory.setCategoryId(10L);
tabCategoryMapper.insertSelective(tabCategory);
DbContextHolder.clearDbType();
return "success";
}