博客
关于我
Springboot基于AbstractRoutingDataSource实现多数据源的动态切换
阅读量:162 次
发布时间:2019-02-28

本文共 10995 字,大约阅读时间需要 36 分钟。

为了提高数据库的查询效率,利用数据库主从机制,写走主库,查询走从库。如果只是实现一主一从类似简单的主从模式,可以继承AbstractRoutingDataSource实现读写分离。而不需使用mycat,sharedingJDBC等数据库插件。

分析AbstractRoutingDataSource可知,defaultTargetDataSource,表示默认的数据源;targetDataSources表示配置的所有数据源集合;afterPropertiesSet方法spring bean对象初始化方法,会把targetDataSources和defaultTargetDataSource,设置为resolvedDataSources和resolvedDefaultDataSource。getConnection()获取jdbc的连接,并通过determineTargetDataSource()获取指定的数据源,AbstractRoutingDataSource使用模板类的模式,在父类定义了determineCurrentLookupKey()虚拟方法,获取lookupkey对象;其子类必须实现该方法。源码如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {    /**     *配置的数据源     */	@Nullable	private Map
targetDataSources; /** *默认数据源 */ @Nullable private Object defaultTargetDataSource; ...... /** *spring InitializingBean 实现方法,bean初始化时调用 */ @Override public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } this.resolvedDataSources = new HashMap<>(this.targetDataSources.size()); this.targetDataSources.forEach((key, value) -> { Object lookupKey = resolveSpecifiedLookupKey(key); DataSource dataSource = resolveSpecifiedDataSource(value); this.resolvedDataSources.put(lookupKey, dataSource); }); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource); } } /** *获取jdbc链接时,调用determineTargetDataSource,获取指定的数据 */ @Override public Connection getConnection() throws SQLException { return determineTargetDataSource().getConnection(); } /** *determineCurrentLookupKey方法通过子类自定义实现,获取lookupKey,然后从resolvedDefaultDataSource map对象中获取数据源 */ 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; } /** *子类必须实现的获取lookupKey的方法 */ protected abstract Object determineCurrentLookupKey();}

创建DataSourceAddressEnum枚举类,定义MASTER与SLAVE,路由名称。代码如下:

public enum DataSourceAddressEnum {    /**     * 主数据库     */    MASTER,    /**     * 从数据库     */    SLAVE;}

创建DataSourceContextHolder,使用ThreadLocal,定义每次操作的类型枚举,代码如下:

public class DataSourceContextHolder {    private static final ThreadLocal
CONTEXT_HOLDER = ThreadLocal.withInitial(() -> DataSourceAddressEnum.MASTER); public static void setCurrentDataSource(DataSourceAddressEnum dataSourceAddressEnum) { CONTEXT_HOLDER.set(dataSourceAddressEnum); } public static DataSourceAddressEnum getCurrentDataSource() { return CONTEXT_HOLDER.get(); } public static void removeDataSource() { CONTEXT_HOLDER.remove(); }}

创建RoutingDataSourceWithAddress,继承AbstractRoutingDataSource,实现determineCurrentLookupKey,即实现了可以根据DataSourceAddressEnum枚举类实现数据源的动态路由,代码如下:

public class RoutingDataSourceWithAddress extends AbstractRoutingDataSource {    /**     * @param defaultTargetDataSource 默认的 DataSource     * @param targetDataSources       配置的所有 DataSource     */    public RoutingDataSourceWithAddress(DataSource defaultTargetDataSource, Map
targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); } /** *配置的数据源 */ @Override protected Object determineCurrentLookupKey() { DataSourceAddressEnum routingDataSourceAddressEnum = DataSourceContextHolder.getCurrentDataSource(); if (log.isDebugEnabled()) { log.debug("routing data source address is {}", routingDataSourceAddressEnum.name()); } return routingDataSourceAddressEnum; }}

使用AOP+注解的方式,对指定的方法进行数据源动态切换的控制。创建RoutingDataSource注解,定义需要路由的数据源,创建RoutingDataSourceAOP定义数据源路由的切面操作,代码如下:

/** * DataSource路由注解 **/@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface RoutingDataSource {    /**     * 路由的DataSource地址,默认为MASTER     */    DataSourceAddressEnum value() default DataSourceAddressEnum.MASTER;}/** * RoutingDataSource 的aop拦截 **/@Aspect@Component@Order(10000)@Slf4jpublic class RoutingDataSourceAOP {    @Pointcut("@annotation(com.kuqi.mall.demo.conmon.datasource.RoutingDataSource)|| @within(com.kuqi.mall.demo.conmon.datasource.RoutingDataSource)")    public void routingDataSourcePointcut() {    }    @Around("routingDataSourcePointcut()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {        MethodSignature signature = (MethodSignature) joinPoint.getSignature();        Method method = signature.getMethod();        RoutingDataSource routerDataSource = method.getAnnotation(RoutingDataSource.class);        // 如果没有设置则默认为 MASTER        DataSourceAddressEnum dataSourceAddressEnum = Objects.isNull(routerDataSource) ? DataSourceAddressEnum.MASTER : routerDataSource.value();        DataSourceContextHolder.setCurrentDataSource(dataSourceAddressEnum);        try {            return joinPoint.proceed();        } finally {            DataSourceContextHolder.removeDataSource();        }    }}

创建基于Springboot的自动配置类RoutingDataSourceAutoConfiguration,只要配置了master和slave的属性文件,和mybatis属性文件,就可以自动启动配置。RoutingDataSourceAutoConfiguration源码如下:

@Configuration@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class, DruidDataSource.class})@EnableConfigurationProperties(MybatisProperties.class)public class RoutingDataSourceAutoConfiguration {    /**     * 配置master数据源     */    @Bean(name = "masterDataSource", initMethod = "init", destroyMethod = "close")    @Primary    @ConfigurationProperties(prefix = "spring.datasource.druid.master")    public DataSource masterDataSource() {        DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())                .type(com.alibaba.druid.pool.DruidDataSource.class).build();        return dataSource;    }    /**     * 配置slave数据源     */    @Bean(name = "slaveDataSource", initMethod = "init", destroyMethod = "close")    @ConfigurationProperties(prefix = "spring.datasource.druid.slave")    public DataSource slaveDataSource() {        DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())                .type(com.alibaba.druid.pool.DruidDataSource.class).build();        return dataSource;    }    /**     * 初始化路由DataSource     */    @Bean(name = "routingDataSourceWithAddress")    public DataSource dataSource(            @Autowired @Qualifier("masterDataSource") DataSource masterDataSource,            @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource) {        DataSource defaultTargetDataSource;        Map
targetDataSources = ImmutableMap.of( DataSourceAddressEnum.MASTER, defaultTargetDataSource = masterDataSource, DataSourceAddressEnum.SLAVE, slaveDataSource); return new RoutingDataSourceWithAddress(defaultTargetDataSource, targetDataSources); } /** * 使用SqlSessionFactoryBean配置MyBatis的SqlSessionFactory **/ @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory( @Autowired @Qualifier("routingDataSourceWithAddress") DataSource routingDataSourceWithAddress, @Autowired MybatisProperties mybatisProperties, @Autowired ResourceLoader resourceLoader) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(routingDataSourceWithAddress); // 设置configuration org.apache.ibatis.session.Configuration configuration = mybatisProperties.getConfiguration(); factory.setConfiguration(configuration); // 设置SqlSessionFactory属性 String configLocation; if (StringUtils.isNotBlank(configLocation = mybatisProperties.getConfigLocation())) { factory.setConfigLocation(resourceLoader.getResource(configLocation)); } Resource[] resolveMapperLocations; if (ArrayUtils.isNotEmpty(resolveMapperLocations = mybatisProperties.resolveMapperLocations())) { factory.setMapperLocations(resolveMapperLocations); } String typeHandlersPackage; if (StringUtils.isNotBlank(typeHandlersPackage = mybatisProperties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(typeHandlersPackage); } String typeAliasesPackage; if (StringUtils.isNotBlank(typeAliasesPackage = mybatisProperties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(typeAliasesPackage); } return factory.getObject(); } /** * 使用routingDataSourceWithAddress配置数据库事务 */ @Bean @ConditionalOnMissingBean public DataSourceTransactionManager dataSourceTransactionManager( @Autowired @Qualifier("routingDataSourceWithAddress") DataSource routingDataSourceWithAddress) { return new DataSourceTransactionManager(routingDataSourceWithAddress); } /** * 编程式事务 */ @Bean public TransactionTemplate transactionTemplate( @Autowired @Qualifier("dataSourceTransactionManager") PlatformTransactionManager platformTransactionManager) { return new TransactionTemplate(platformTransactionManager); } @Bean @ConditionalOnMissingBean(RoutingDataSourceAOP.class) public RoutingDataSourceAOP rRoutingDataSourceAOP() { return new RoutingDataSourceAOP(); }}

数据源使用DruidDataSource,详细的配置文件,master和slave数据源,可以配置不同的数据库用来测试,实际开发中,配置为满足数据库主从复制的配置,配置代码如下:

spring:  datasource:    type: com.alibaba.druid.pool.DruidDataSource    druid:      master:        driver-class-name: com.mysql.jdbc.Driver        url: jdbc:mysql://127.0.0.1:3306/kuqi_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai        username: root        password: root      slave:        driver-class-name: com.mysql.jdbc.Driver        url: jdbc:mysql://127.0.0.1:3306/kuqi_mall?autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai        username: root        password: root

在springboot的启动类中,屏蔽DruidDataSourceAutoConfigure自动配置类,就能启动。启动类代码,以及操作示例代码如下:

/** * springboot启动类exclude DruidDataSourceAutoConfigure */@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)@MapperScan(basePackages = {"com.kuqi.mall.demo.dao"})public class DemoApplication {    public static void main(String[] args) {        SpringApplication.run(DemoApplication.class, args);    }}         /**     * 注解定义动态数据源操作     */    @Override    @RoutingDataSource(DataSourceAddressEnum.SLAVE)    public CouponBo getFromSlave(Long id) {        return get(id);    }

完整示例代码,可以参考链接spring-boot-mall项目

转载地址:http://ewyc.baihongyu.com/

你可能感兴趣的文章
MQTT 持久会话与 Clean Session 详解
查看>>
MQTT介绍及与其他协议的比较
查看>>
MQTT工作笔记0007---剩余长度
查看>>
MQTT工作笔记0008---服务质量
查看>>
MQTT工作笔记0009---订阅主题和订阅确认
查看>>
Mqtt搭建代理服务器进行通信-浅析
查看>>
MS COCO数据集介绍
查看>>
MS Edge浏览器“STATUS_INVALID_IMAGE_HASH“兼容性问题
查看>>
ms sql server 2008 sp2更新异常
查看>>
MS SQL查询库、表、列数据结构信息汇总
查看>>
MS UC 2013-0-Prepare Tool
查看>>
MSBuild 教程(2)
查看>>
msbuild发布web应用程序
查看>>
MSB与LSB
查看>>
MSCRM调用外部JS文件
查看>>
MSCRM调用外部JS文件
查看>>
MSEdgeDriver (Chromium) 不适用于版本 >= 79.0.313 (Canary)
查看>>
MsEdgeTTS开源项目使用教程
查看>>
msf
查看>>
MSP430F149学习之路——SPI
查看>>