不考虑事务,需要事务的同学出门左转 ⬅️
看此篇之前建议先看前两篇,同志们
SpringBoot的@Enable系列注解
SpringBoot EnableAutoConfiguration和Configuration
这一篇可以不看 配置的使用方式太乱了
Mybatis多数据源配置
请原谅我也当了一次标题党,这个应该算不上Starter,毕竟还是需要手动填 @Enable 注解的.
使用方式
先看看写完后的使用方式
import top.huzhurong.boot.multi.datasource.export.EnableMultiDatasource;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})@EnableMultiDatasource//自定义注解public class WebApplication {public static void main(String[] args) {SpringApplication.run(WebApplication.class);}}
properties 配置
#firstraycloud.multi.datasource.datasource-properties.first.username=rootraycloud.multi.datasource.datasource-properties.first.password=chenshunraycloud.multi.datasource.datasource-properties.first.url=jdbc:mysql://127.0.0.1:3306/project_manager?useUnicode=true&characterEncoding=utf-8&autoReconnect=trueraycloud.multi.datasource.datasource-properties.first.base-packages=com.raycloud.test.web.firstraycloud.multi.datasource.datasource-properties.first.location-pattern=classpath*:/mapper/first/*.xml#secondraycloud.multi.datasource.datasource-properties.second.username=rootraycloud.multi.datasource.datasource-properties.second.password=chenshunraycloud.multi.datasource.datasource-properties.second.url=jdbc:mysql://127.0.0.1:3306/sentinel?useUnicode=true&characterEncoding=utf-8&autoReconnect=trueraycloud.multi.datasource.datasource-properties.second.base-packages=com.raycloud.test.web.secondraycloud.multi.datasource.datasource-properties.second.location-pattern=classpath*:/mapper/second/*.xml
分别对应了第一个和第二个数据源. 使用方式也及其简单. 
这样就配置完成了。 真实的调用如下
bingo,完美完成,只需要配置一下就可以直接食用了. 前提是需要规划好package和mapper.xml文件的路径. 想必不是什么大问题.
源代码
一开始我是直接使用Starter的方式来做的,等到写完的时候,发现这个方式行不通,Starter的加载比较快,此时properties文件尚未读取完毕,意味着获取不到配置文件,败北
个人水平有限,最后不得不使用 @Enable 注解的方式来进行引用.
导入多数据源注册
@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import({MultiDatasourceRegister.class})public @interface EnableMultiDatasource {}
对多数据源进行处理,分别注入
- DruidDataSource
- SqlSessionFactoryBean
- DataSourceTransactionManager
- SqlSessionTemplate
MapperScannerConfigurer ```java public class MultiDatasourceRegister implements ImportBeanDefinitionRegistrar, BeanFactoryAware, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(MultiDatasourceRegister.class); private DefaultListableBeanFactory defaultListableBeanFactory; private Environment environment;
public MultiDatasourceRegister() { }
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(this.defaultListableBeanFactory, "defaultListableBeanFactory 不能为空");Assert.notNull(this.environment, "environment 不能为空");Map<String, PropertyBean> datasourceProperties = ((DataSourcePropertyBean)Binder.get(this.environment).bindOrCreate("raycloud.multi.datasource", DataSourcePropertyBean.class)).getDatasourceProperties();Iterator var4 = datasourceProperties.entrySet().iterator();while(var4.hasNext()) {Entry<String, PropertyBean> entry = (Entry)var4.next();String datasourceName = (String)entry.getKey();PropertyBean dataSourceProperty = (PropertyBean)entry.getValue();BeanDefinitionBuilder sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(RayDruidDataSource.class);sqlSessionTemplate.setInitMethodName("init");sqlSessionTemplate.setDestroyMethodName("close");this.defaultListableBeanFactory.registerBeanDefinition(datasourceName, sqlSessionTemplate.getBeanDefinition());sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class);sqlSessionTemplate.addPropertyReference("dataSource", datasourceName);Resource[] resources;try {resources = (new PathMatchingResourcePatternResolver()).getResources((String)Objects.requireNonNull(dataSourceProperty.getLocationPattern(), "mybatis Mapper.xml扫描路径不能为空"));} catch (IOException var14) {throw new RuntimeException(var14.getMessage());}logger.info("[数据源:{} 加载resource文件][个数:{}]", datasourceName, resources.length);Resource[] var10 = resources;int var11 = resources.length;for(int var12 = 0; var12 < var11; ++var12) {Resource resource = var10[var12];if (logger.isDebugEnabled()) {logger.debug("数据源:{} ,加载文件:{}", datasourceName, resource.toString());}}sqlSessionTemplate.addPropertyValue("mapperLocations", resources);this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "SqlSessionFactory"), sqlSessionTemplate.getBeanDefinition());sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(DataSourceTransactionManager.class);sqlSessionTemplate.addConstructorArgReference(datasourceName);this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "PlatformTransactionManager"), sqlSessionTemplate.getBeanDefinition());sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionTemplate.class);sqlSessionTemplate.addConstructorArgReference(String.format("%s%s", datasourceName, "SqlSessionFactory"));this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "SqlSessionTemplate"), sqlSessionTemplate.getBeanDefinition());String basePackages = (String)Objects.requireNonNull(dataSourceProperty.getBasePackages(), "basePackages 不能为空");BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("sqlSessionTemplateBeanName", datasourceName + "SqlSessionTemplate");builder.addPropertyValue("basePackage", Collections.singletonList(basePackages));builder.setLazyInit(false);this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "MapperScannerConfigurer"), builder.getBeanDefinition());}
}
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.defaultListableBeanFactory = (DefaultListableBeanFactory)beanFactory;
}
public void setEnvironment(Environment environment) {
this.environment = environment;
} }
经历这一步,dataSource是注入进去了,但是这个时候配置文件还是没有注入进去的,我们需要在处理 `BeanPostProcessor` 的过程中,将配置信息注入<br />这里的核心就是2个步骤,因为注入Bean的过程和注入属性的过程不同步,所以必须将- 注入Bean- 注入属性这2个过程分离,注入Bean的过程在@Enable中处理,而注入属性的过程则在 `BeanPostProcessor` 中进行处理.```javapublic class MultiDatasourceBeanFactoryPostProcessor implements BeanPostProcessor, EnvironmentAware, BeanFactoryAware {public static final Logger logger = LoggerFactory.getLogger(MultiDatasourceBeanFactoryPostProcessor.class);private static final String PREFIX = "raycloud.multi.datasource";private Map<String, DataSourcePropertyBean.PropertyBean> datasourceProperties;private BeanFactory beanFactory;@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {if (bean instanceof RayDruidDataSource) {RayDruidDataSource druidDataSource = (RayDruidDataSource) bean;druidDataSource.setName(beanName);final DataSourcePropertyBean.PropertyBean propertyBean = datasourceProperties.get(beanName);if (Objects.isNull(propertyBean)) {throw new IllegalStateException(String.format("根据【%s】获取datasource失败", beanName));}druidDataSource.setUsername(propertyBean.getUsername());druidDataSource.setUrl(propertyBean.getUrl());if (StringUtils.hasText(propertyBean.getXy())) {logger.info("加载坐标,datasource:{} ,xy:{}", beanName, propertyBean.getXy());final DbInfoRequest dbInfoRequest =this.beanFactory.getBean("dbInfoRequest", DbInfoRequest.class);druidDataSource.setDbInfoRequest(dbInfoRequest);druidDataSource.setDiamondCoord(propertyBean.getXy());} else {druidDataSource.setPassword(propertyBean.getPassword());}druidDataSource.setMinIdle(propertyBean.getMinIdle());druidDataSource.setInitialSize(propertyBean.getInitialSize());druidDataSource.setMaxActive(propertyBean.getMaxActive());druidDataSource.setMaxWait(propertyBean.getMaxActive());if (druidDataSource.getMaxWait() > -1) {druidDataSource.setUseUnfairLock(true);}druidDataSource.setPoolPreparedStatements(false);druidDataSource.setValidationQuery(propertyBean.getValidationQuery());druidDataSource.setTestWhileIdle(propertyBean.getTestWhileIdle());druidDataSource.setTestOnBorrow(propertyBean.getTestOnBorrow());druidDataSource.setTestOnReturn(propertyBean.getTestOnReturn());return druidDataSource;}return bean;}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {this.beanFactory = beanFactory;}@Overridepublic void setEnvironment(Environment environment) {datasourceProperties = Binder.get(environment).bindOrCreate(PREFIX,DataSourcePropertyBean.class).getDatasourceProperties();}}
到这一步就基本可用了,不过为了提供给使用者在使用上提供一些便利,可以使用 ConfigurationProperties 增加一个提示,效果如下,不过这个是可有可无的,没有这个咋也能用
@ConfigurationProperties(prefix = "raycloud.multi.datasource")public class DataSourcePropertyBean {private final Map<String, PropertyBean> datasourceProperties = new HashMap<>();public Map<String, PropertyBean> getDatasourceProperties() {return datasourceProperties;}@Getter@Setterpublic static class PropertyBean {/*** @see DruidDataSource#getUsername()*/private String username;/*** @see DruidDataSource#getPassword()*/private String password;/*** @see DruidDataSource#getUrl()*/private String url;/*** @see RayDruidDataSource#getDiamondCoord()*/private String xy;/*** @see DruidDataSource#getDriverClassName()*/private String driverClassName = "com.mysql.jdbc.Driver";/*** @see DruidDataSource#getMinIdle()*/private Integer minIdle = 5;/*** @see DruidDataSource#getInitialSize()*/private Integer initialSize = 5;/*** @see DruidDataSource#getMaxActive()*/private Integer maxActive = 10;/*** @see DruidDataSource#getMaxWait()*/private Integer maxWait = 60000;/*** @see DruidDataSource#getValidationQuery()*/private String validationQuery = "SELECT 'x'";/*** @see DruidDataSource#isTestWhileIdle()*/private Boolean testWhileIdle = true;/*** @see DruidDataSource#isTestOnBorrow()*/private Boolean testOnBorrow = false;/*** @see DruidDataSource#isTestOnReturn()*/private Boolean testOnReturn = false;/*** 选择dbInfoRequest的zk地址,默认是不需要的*/private String dbInfoZk;/*** 选择dbInfoRequest的dubbo版本,默认是 2.0.0_DB*/private String dbInfoRequestVersion = "2.0.0_DB";/*** mybatis mapper文件扫描路径* 例如:classpath*:/mapper/dms/*Mapper.xml*/private String locationPattern;/*** @see MapperScan#basePackages()*/private String basePackages;}}
到这里到没有啦
总结
介绍了多数据源如何使用,以及如何写这个多数据源的 Starter ,因为注入Bean的过程和注入属性的过程不在一个阶段,所以不得不使用2个阶段来进行分离. 最终达成注入Datasource的结果. 一套下来,后来使用多数据源就舒服了.
