网站解析ip地址阿里云搜索
一:实现
方式一:继承AbstractRoutingDataSource使用自定义注解实现
环境:springboot3 + MyBatis3 + mysql-connector8
DataSourceKeyEnum枚举类
有几个数据源就配置几个枚举类,和数据源数量一一对应
class DataSourceKeyEnum{DEFAULT,SLAVE
}
DataSourceAnnotation注解
用来标记在Service方法或Mapper上,用于辅助AOP切换数据源操作。
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceAnnotation {DataSourceKeyEnum value() default DataSourceKeyEnum.DEFAULT;}
AbstractRoutingDataSource 的作用
AbstractRoutingDataSource的核心功能是根据用户定义的规则,动态决定在特定的操作中使用哪个数据源。这通常是通过重写determineCurrentLookupKey
方法来实现的,该方法用于确定当前线程应该使用哪个数据源的键。
public class DynamicDataSource extends AbstractRoutingDataSource {/*** MyBatis内部根据实现AbstractRoutingDataSource类的determineCurrentLookupKey实现数据源的动态路由*/@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}/*** 静态内部类,用于从线程中获取当前要使用的数据源*/public static class DynamicDataSourceContextHolder {private static final ThreadLocal<DataSourceKeyEnum> contextHolder = new ThreadLocal<>();public static DataSourceKeyEnum getDataSourceType() {return contextHolder.get();}public static void setDataSourceType(DataSourceKeyEnum dataSourceKey) {if (dataSourceKey == null) {dataSourceKey = DataSourceKeyEnum.DEFAULT;}contextHolder.set(dataSourceKey);}public static void clearDataSourceType() {contextHolder.remove();}}
}
DataSourceConfig数据源配置类
环境配置
spring.datasource.default.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.default.jdbcUrl=jdbc:mysql://xxxxxx/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
spring.datasource.default.username=root
spring.datasource.default.password=rootspring.datasource.slave.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.slave.jdbcUrl=jdbc:mysql://xxxxxx/mybatis?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.slave.username=root
spring.datasource.slave.password=rootmapper.identity=MYSQL
mapper.wrap-keyword= `{0}`
mybatis.type-aliases-package=com.example.simplejobdemo.entity
mybatis.mapper-locations=classpath:mapper/*.xml
##mybatis.mapper-locations=classpath:mapper/base/**/*.xml,mapper/common/**/*.xml,mapper/core/**/*.xml,workflow/*.xml,com/sitech/pgcenter/mapper/core/*.xml,base/**/*.xmlmybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis.configuration.jdbc-type-for-null=null
创建数据源,对于动态数据源一定要加上 @Primary注解。
@Configuration
public class DataSourceConfig {@Bean@ConfigurationProperties(prefix = "spring.datasource.default")public DataSource dataSourceDefault() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.slave")public DataSource dataSourceSlave() {return DataSourceBuilder.create().build();}/*** 动态数据源* @param dataSourceDefault 默认数据源* @param dataSourceSlave 从数据源* @return*/@Bean@Primarypublic DataSource dataSource(DataSource dataSourceDefault,DataSource dataSourceSlave) {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceKeyEnum.DEFAULT, dataSourceDefault);targetDataSources.put(DataSourceKeyEnum.SLAVE, dataSourceSlave);DynamicDataSource dataSource = new DynamicDataSource();dataSource.setTargetDataSources(targetDataSources);dataSource.setDefaultTargetDataSource(dataSourceSlave);return dataSource;}}
DataSourceAspect切面类
@Aspect
@Component
public class DataSourceAspect {//切点@Pointcut("@annotation(com.example.simplejobdemo.config.datasourceconf.DataSourceAnnotation)")public void pointCut() {}@Around(value = "pointCut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();DataSourceAnnotation dataSourceAnnotation = method.getAnnotation(DataSourceAnnotation.class);DataSourceKeyEnum dataSourceKeyEnum = dataSourceAnnotation.value();if(dataSourceKeyEnum == null) {dataSourceKeyEnum = DataSourceKeyEnum.DEFAULT;}DynamicDataSource.DynamicDataSourceContextHolder.setDataSourceType(dataSourceKeyEnum);try {return joinPoint.proceed();}finally {DynamicDataSource.DynamicDataSourceContextHolder.clearDataSourceType();}}}
二:原理
AbstractRoutingDataSource类如何影响正在执行mybatis执行sql语句的数据源选择?
每个sql执行时,调用DataSource接口的getConnection()方法获取数据库的连接,实际执行是会使用AbstractRoutingDataSource类的getConnection()选取指定数据的连接(AbstractRoutingDataSource实现了DataSource接口),这就是项目中每个执行sql可以使用AbstractRoutingDataSource类中指定数据源的原因。
这里getConnection方法调用时的核心方法determineTargetDataSourc()
核心方法是determineCurrentLookupKey(),通过这个方法调用获取业务指定的的map中key值,获取resolvedDataSources中指定key的数据源 。
determineCurrentLookupKey()的实现在自定义的子类方法中,自定义的子类方法中,将key值业务定义使用的是本地线程栈技术
运用本地线程栈,可以在线程中手动或者自动(拦截器)将key值设定到本地线程栈对象contextHolder中,当sql执行时会,对调用AbstractRoutingDataSource的getConnection(),再调用determineTargetDataSource(),子类中determineCurrentLookupKey()最终决定了从本地线程栈对象contextHolder获取当前数据源
如何初始化所有数据源?
继续看AbstractRoutingDataSource类的核心方法determineTargetDataSourc()
AbstractRoutingDataSource类本身有个四个核心属性:
//用来通过配置文件指定所有的key值和value数据源
private Map<Object, Object> targetDataSources;
//用来通过配置文件指定默认数据源
private Object defaultTargetDataSource;
//在afterPropertiesSet方法将defaultTargetDataSource中转为resolvedDefaultDataSource,这个是后面存储默认数据源
private DataSource resolvedDefaultDataSource;
//在afterPropertiesSet方法将targetDataSources中转为resolvedDataSources,这个是后面存储使用的名称-数据源映射
private Map<Object, DataSource> resolvedDataSources;
其中resolvedDataSources又是如何初始化的?
AbstractRoutingDataSource类实现了InitializingBean接口,所以此方法在这个bean初始化时执行afterPropertiesSet方法。afterPropertiesSet方法将配置文件中注入的targetDataSources和defaultTargetDataSource 转换为了后续可以使用的resolvedDefaultDataSource(默认数据源)和resolvedDataSources(存储使用的名称-数据源映射)
而targetDataSources的初始化,就是我们通过配置实现的