프로덕션 비즈니스에서는 일부 작업이 장시간 쿼리 작업을 수행합니다. 실시간 요구 사항이 높지 않은 경우 이러한 쿼리 SQL을 분리하여 데이터베이스에서 쿼리하여 기본 데이터베이스에 대한 애플리케이션 부담을 줄이려고 합니다.
한 가지 솔루션은 구성 파일에 여러 데이터 소스를 구성한 다음 구성 클래스를 통해 데이터 소스와 매퍼 관련 스캐닝 구성을 얻는 것입니다. 각 데이터 소스는 서로 다른 매퍼 스캐닝 위치를 구성한 다음 어떤 데이터 소스가 필요한지 확인합니다. 원하는 매퍼 인터페이스를 삽입하세요. 이 방법은 비교적 간단합니다. 매퍼 스캔 위치를 통해 데이터 소스를 구분하는 기능입니다.
가능한 해결책은 기본 데이터 소스를 미리 설정하고, 다른 여러 데이터 소스를 동시에 정의한 다음, aop를 사용하여 데이터 소스를 전환하는 주석을 구현하는 것입니다. AbstractRoutingDataSource 클래스를 상속하는 것이 이 솔루션 구현의 핵심입니다. 이것이 이 글의 초점이다.
AbstractRoutingDataSource에서 여러 데이터 소스를 동적으로 전환하는 핵심 논리는 프로그램이 실행될 때 데이터 소스가 AbstractRoutingDataSource를 통해 프로그램에 동적으로 짜여져 데이터 소스를 유연하게 전환한다는 것입니다.
AbstractRoutingDataSource를 기반으로 여러 데이터 소스를 동적으로 전환하면 읽기와 쓰기를 분리할 수 있습니다. 논리는 다음과 같습니다.
/** * Retrieve the current target DataSource. Determines the * {@link #determineCurrentLookupKey() current lookup key}, performs * a lookup in the {@link #setTargetDataSources targetDataSources} map, * falls back to the specified * {@link #setDefaultTargetDataSource default target DataSource} if necessary. * @see #determineCurrentLookupKey() */ protected DataSource determineTargetDataSource() { Assert.notNull(this.resolvedDataSources, "DataSource router not initialized"); Object lookupKey = determineCurrentLookupKey(); 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 + "]"); } return dataSource; } /** * Determine the current lookup key. This will typically be * implemented to check a thread-bound transaction context. * <p>Allows for arbitrary keys. The returned key needs * to match the stored lookup key type, as resolved by the * {@link #resolveSpecifiedLookupKey} method. */ @Nullable protected abstract Object determineCurrentLookupKey();
추상 메서드인determinCurrentLookupKey
이 예는 주로
com.alibaba.druid;tk.mybatis
에 의존합니다. 데이터 소스를 연결할 클래스를 정의합니다. TheadLocal을 사용하여 각 스레드가 선택한 데이터 소스의 플래그(키)를 저장합니다
@Slf4j public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceIds = new ArrayList<String>(); public static void setDataSourceType(String dataSourceType) { log.info("设置当前数据源为{}",dataSourceType); contextHolder.set(dataSourceType); } public static String getDataSourceType() { return contextHolder.get() ; } public static void clearDataSourceType() { contextHolder.remove(); } public static boolean containsDataSource(String dataSourceId){ log.info("list = {},dataId={}", JSON.toJSON(dataSourceIds),dataSourceId); return dataSourceIds.contains(dataSourceId); } }
Inherit
AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
메인 데이터베이스 마스터 및 슬레이브 데이터베이스 슬레이브를 구성합니다(생략). 데이터 소스 구성을 단순화할 수 있습니다
@Configuration @tk.mybatis.spring.annotation.MapperScan(value = {"com.server.dal.dao"}) @ConditionalOnProperty(name = "java.druid.datasource.master.url") public class JavaDruidDataSourceConfiguration { private static final Logger logger = LoggerFactory.getLogger(JavaDruidDataSourceConfiguration.class); @Resource private JavaDruidDataSourceProperties druidDataSourceProperties; @Primary @Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close") @ConditionalOnMissingBean(name = "masterDataSource") public DruidDataSource javaReadDruidDataSource() { DruidDataSource result = new DruidDataSource(); try { // result.setName(druidDataSourceProperties.getName()); result.setUrl(druidDataSourceProperties.getUrl()); result.setUsername(druidDataSourceProperties.getUsername()); result.setPassword(druidDataSourceProperties.getPassword()); result.setConnectionProperties( "config.decrypt=false;config.decrypt.key=" + druidDataSourceProperties.getPwdPublicKey()); result.setFilters("config"); result.setMaxActive(druidDataSourceProperties.getMaxActive()); result.setInitialSize(druidDataSourceProperties.getInitialSize()); result.setMaxWait(druidDataSourceProperties.getMaxWait()); result.setMinIdle(druidDataSourceProperties.getMinIdle()); result.setTimeBetweenEvictionRunsMillis(druidDataSourceProperties.getTimeBetweenEvictionRunsMillis()); result.setMinEvictableIdleTimeMillis(druidDataSourceProperties.getMinEvictableIdleTimeMillis()); result.setValidationQuery(druidDataSourceProperties.getValidationQuery()); result.setTestWhileIdle(druidDataSourceProperties.isTestWhileIdle()); result.setTestOnBorrow(druidDataSourceProperties.isTestOnBorrow()); result.setTestOnReturn(druidDataSourceProperties.isTestOnReturn()); result.setPoolPreparedStatements(druidDataSourceProperties.isPoolPreparedStatements()); result.setMaxOpenPreparedStatements(druidDataSourceProperties.getMaxOpenPreparedStatements()); if (druidDataSourceProperties.isEnableMonitor()) { StatFilter filter = new StatFilter(); filter.setLogSlowSql(druidDataSourceProperties.isLogSlowSql()); filter.setMergeSql(druidDataSourceProperties.isMergeSql()); filter.setSlowSqlMillis(druidDataSourceProperties.getSlowSqlMillis()); List<Filter> list = new ArrayList<>(); list.add(filter); result.setProxyFilters(list); } } catch (Exception e) { logger.error("数据源加载失败:", e); } finally { result.close(); } return result; } }
마스터-슬레이브 데이터베이스의 Bean 이름에 주의하세요
DynamicDataSource를 구성하여
targetDataSources를 구성하여 데이터 소스의 k-v 쌍을 저장합니다.
defaultTargetDataSource를 기본 데이터로 저장합니다. source
트랜잭션 관리자 및 SqlSe ssionFactoryBean을 구성하세요
@Configuration public class DynamicDataSourceConfig { private static final String MAPPER_LOCATION = "classpath*:sqlmap/dao/*Mapper.xml"; @Bean(name = "dynamicDataSource") public DynamicDataSource dynamicDataSource(@Qualifier("masterDataSource") DruidDataSource masterDataSource, @Qualifier("slaveDataSource") DruidDataSource slaveDataSource) { Map<Object, Object> targetDataSource = new HashMap<>(); DynamicDataSourceContextHolder.dataSourceIds.add("masterDataSource"); targetDataSource.put("masterDataSource", masterDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("slaveDataSource"); targetDataSource.put("slaveDataSource", slaveDataSource); DynamicDataSource dataSource = new DynamicDataSource(); dataSource.setTargetDataSources(targetDataSource); dataSource.setDefaultTargetDataSource(masterDataSource); return dataSource; } @Primary @Bean(name = "javaTransactionManager") @ConditionalOnMissingBean(name = "javaTransactionManager") public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource druidDataSource) { return new DataSourceTransactionManager(druidDataSource); } @Bean(name = "sqlSessionFactoryBean") public SqlSessionFactoryBean myGetSqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { sqlSessionFactoryBean.setMapperLocations(resolver.getResources(MAPPER_LOCATION)); } catch (IOException e) { e.printStackTrace(); } sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } }
주석을 정의하여 데이터 소스
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { String value(); }
aspect의 비즈니스 로직을 지정하세요. 트랜잭션을 시작하기 전에 실행을 보장하기 위해 순서를 지정하는 데 주의하세요.
@Aspect @Slf4j @Order(-1) @Component public class DataSourceAop { @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) { String dsId = targetDataSource.value(); if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) { log.error("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + point.getSignature()); } else { log.info("UseDataSource : {} > {}" + targetDataSource.value() + point.getSignature()); DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value()); } } @After("@annotation(targetDataSource)") public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) { log.info("RevertDataSource : {} > {}"+targetDataSource.value()+point.getSignature()); DynamicDataSourceContextHolder.clearDataSourceType(); } }
Pom.xml과 application.yml은 위에서 생략
사용예
@Resource private ShopBillDOMapper shopBillDOMapper; //使用默认数据源 public ShopBillBO queryTestData(Integer id){ return shopBillDOMapper.getByShopBillId(id); } //切换到指定的数据源 @TargetDataSource("slaveDataSource") public ShopBill queryTestData2(Integer id){ return shopBillDOMapper.getByShopBillId(id); }
다른 결과가 반환되면 성공입니다!
위 내용은 SpringBoot가 AbstractRoutingDataSource를 기반으로 여러 데이터 소스의 동적 전환을 구현하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!