不考虑事务,需要事务的同学出门左转 ⬅️

看此篇之前建议先看前两篇,同志们
SpringBoot的@Enable系列注解
SpringBoot EnableAutoConfiguration和Configuration
这一篇可以不看 配置的使用方式太乱了
Mybatis多数据源配置
请原谅我也当了一次标题党,这个应该算不上Starter,毕竟还是需要手动填 @Enable 注解的.

使用方式

先看看写完后的使用方式

  1. import top.huzhurong.boot.multi.datasource.export.EnableMultiDatasource;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
  5. @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
  6. @EnableMultiDatasource//自定义注解
  7. public class WebApplication {
  8. public static void main(String[] args) {
  9. SpringApplication.run(WebApplication.class);
  10. }
  11. }

properties 配置

  1. #first
  2. raycloud.multi.datasource.datasource-properties.first.username=root
  3. raycloud.multi.datasource.datasource-properties.first.password=chenshun
  4. raycloud.multi.datasource.datasource-properties.first.url=jdbc:mysql://127.0.0.1:3306/project_manager?useUnicode=true&characterEncoding=utf-8&autoReconnect=true
  5. raycloud.multi.datasource.datasource-properties.first.base-packages=com.raycloud.test.web.first
  6. raycloud.multi.datasource.datasource-properties.first.location-pattern=classpath*:/mapper/first/*.xml
  7. #second
  8. raycloud.multi.datasource.datasource-properties.second.username=root
  9. raycloud.multi.datasource.datasource-properties.second.password=chenshun
  10. raycloud.multi.datasource.datasource-properties.second.url=jdbc:mysql://127.0.0.1:3306/sentinel?useUnicode=true&characterEncoding=utf-8&autoReconnect=true
  11. raycloud.multi.datasource.datasource-properties.second.base-packages=com.raycloud.test.web.second
  12. raycloud.multi.datasource.datasource-properties.second.location-pattern=classpath*:/mapper/second/*.xml

分别对应了第一个和第二个数据源. 使用方式也及其简单.
image.png
这样就配置完成了。 真实的调用如下
image.png
bingo,完美完成,只需要配置一下就可以直接食用了. 前提是需要规划好package和mapper.xml文件的路径. 想必不是什么大问题.

源代码

一开始我是直接使用Starter的方式来做的,等到写完的时候,发现这个方式行不通,Starter的加载比较快,此时properties文件尚未读取完毕,意味着获取不到配置文件,败北
个人水平有限,最后不得不使用 @Enable 注解的方式来进行引用.

导入多数据源注册

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import({MultiDatasourceRegister.class})
  5. public @interface EnableMultiDatasource {
  6. }

对多数据源进行处理,分别注入

  • 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) {

    1. Assert.notNull(this.defaultListableBeanFactory, "defaultListableBeanFactory 不能为空");
    2. Assert.notNull(this.environment, "environment 不能为空");
    3. Map<String, PropertyBean> datasourceProperties = ((DataSourcePropertyBean)Binder.get(this.environment).bindOrCreate("raycloud.multi.datasource", DataSourcePropertyBean.class)).getDatasourceProperties();
    4. Iterator var4 = datasourceProperties.entrySet().iterator();
    5. while(var4.hasNext()) {
    6. Entry<String, PropertyBean> entry = (Entry)var4.next();
    7. String datasourceName = (String)entry.getKey();
    8. PropertyBean dataSourceProperty = (PropertyBean)entry.getValue();
    9. BeanDefinitionBuilder sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(RayDruidDataSource.class);
    10. sqlSessionTemplate.setInitMethodName("init");
    11. sqlSessionTemplate.setDestroyMethodName("close");
    12. this.defaultListableBeanFactory.registerBeanDefinition(datasourceName, sqlSessionTemplate.getBeanDefinition());
    13. sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class);
    14. sqlSessionTemplate.addPropertyReference("dataSource", datasourceName);
    15. Resource[] resources;
    16. try {
    17. resources = (new PathMatchingResourcePatternResolver()).getResources((String)Objects.requireNonNull(dataSourceProperty.getLocationPattern(), "mybatis Mapper.xml扫描路径不能为空"));
    18. } catch (IOException var14) {
    19. throw new RuntimeException(var14.getMessage());
    20. }
    21. logger.info("[数据源:{} 加载resource文件][个数:{}]", datasourceName, resources.length);
    22. Resource[] var10 = resources;
    23. int var11 = resources.length;
    24. for(int var12 = 0; var12 < var11; ++var12) {
    25. Resource resource = var10[var12];
    26. if (logger.isDebugEnabled()) {
    27. logger.debug("数据源:{} ,加载文件:{}", datasourceName, resource.toString());
    28. }
    29. }
    30. sqlSessionTemplate.addPropertyValue("mapperLocations", resources);
    31. this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "SqlSessionFactory"), sqlSessionTemplate.getBeanDefinition());
    32. sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(DataSourceTransactionManager.class);
    33. sqlSessionTemplate.addConstructorArgReference(datasourceName);
    34. this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "PlatformTransactionManager"), sqlSessionTemplate.getBeanDefinition());
    35. sqlSessionTemplate = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionTemplate.class);
    36. sqlSessionTemplate.addConstructorArgReference(String.format("%s%s", datasourceName, "SqlSessionFactory"));
    37. this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "SqlSessionTemplate"), sqlSessionTemplate.getBeanDefinition());
    38. String basePackages = (String)Objects.requireNonNull(dataSourceProperty.getBasePackages(), "basePackages 不能为空");
    39. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    40. builder.addPropertyValue("sqlSessionTemplateBeanName", datasourceName + "SqlSessionTemplate");
    41. builder.addPropertyValue("basePackage", Collections.singletonList(basePackages));
    42. builder.setLazyInit(false);
    43. this.defaultListableBeanFactory.registerBeanDefinition(String.format("%s%s", datasourceName, "MapperScannerConfigurer"), builder.getBeanDefinition());
    44. }

    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {

    1. this.defaultListableBeanFactory = (DefaultListableBeanFactory)beanFactory;

    }

    public void setEnvironment(Environment environment) {

    1. this.environment = environment;

    } }

  1. 经历这一步,dataSource是注入进去了,但是这个时候配置文件还是没有注入进去的,我们需要在处理 `BeanPostProcessor` 的过程中,将配置信息注入<br />这里的核心就是2个步骤,因为注入Bean的过程和注入属性的过程不同步,所以必须将
  2. - 注入Bean
  3. - 注入属性
  4. 2个过程分离,注入Bean的过程在@Enable中处理,而注入属性的过程则在 `BeanPostProcessor` 中进行处理.
  5. ```java
  6. public class MultiDatasourceBeanFactoryPostProcessor implements BeanPostProcessor, EnvironmentAware, BeanFactoryAware {
  7. public static final Logger logger = LoggerFactory.getLogger(MultiDatasourceBeanFactoryPostProcessor.class);
  8. private static final String PREFIX = "raycloud.multi.datasource";
  9. private Map<String, DataSourcePropertyBean.PropertyBean> datasourceProperties;
  10. private BeanFactory beanFactory;
  11. @Override
  12. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  13. if (bean instanceof RayDruidDataSource) {
  14. RayDruidDataSource druidDataSource = (RayDruidDataSource) bean;
  15. druidDataSource.setName(beanName);
  16. final DataSourcePropertyBean.PropertyBean propertyBean = datasourceProperties.get(beanName);
  17. if (Objects.isNull(propertyBean)) {
  18. throw new IllegalStateException(String.format("根据【%s】获取datasource失败", beanName));
  19. }
  20. druidDataSource.setUsername(propertyBean.getUsername());
  21. druidDataSource.setUrl(propertyBean.getUrl());
  22. if (StringUtils.hasText(propertyBean.getXy())) {
  23. logger.info("加载坐标,datasource:{} ,xy:{}", beanName, propertyBean.getXy());
  24. final DbInfoRequest dbInfoRequest =
  25. this.beanFactory.getBean("dbInfoRequest", DbInfoRequest.class);
  26. druidDataSource.setDbInfoRequest(dbInfoRequest);
  27. druidDataSource.setDiamondCoord(propertyBean.getXy());
  28. } else {
  29. druidDataSource.setPassword(propertyBean.getPassword());
  30. }
  31. druidDataSource.setMinIdle(propertyBean.getMinIdle());
  32. druidDataSource.setInitialSize(propertyBean.getInitialSize());
  33. druidDataSource.setMaxActive(propertyBean.getMaxActive());
  34. druidDataSource.setMaxWait(propertyBean.getMaxActive());
  35. if (druidDataSource.getMaxWait() > -1) {
  36. druidDataSource.setUseUnfairLock(true);
  37. }
  38. druidDataSource.setPoolPreparedStatements(false);
  39. druidDataSource.setValidationQuery(propertyBean.getValidationQuery());
  40. druidDataSource.setTestWhileIdle(propertyBean.getTestWhileIdle());
  41. druidDataSource.setTestOnBorrow(propertyBean.getTestOnBorrow());
  42. druidDataSource.setTestOnReturn(propertyBean.getTestOnReturn());
  43. return druidDataSource;
  44. }
  45. return bean;
  46. }
  47. @Override
  48. public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
  49. this.beanFactory = beanFactory;
  50. }
  51. @Override
  52. public void setEnvironment(Environment environment) {
  53. datasourceProperties = Binder.get(environment).bindOrCreate(PREFIX,
  54. DataSourcePropertyBean.class).getDatasourceProperties();
  55. }
  56. }

到这一步就基本可用了,不过为了提供给使用者在使用上提供一些便利,可以使用 ConfigurationProperties 增加一个提示,效果如下,不过这个是可有可无的,没有这个咋也能用
image.png

  1. @ConfigurationProperties(prefix = "raycloud.multi.datasource")
  2. public class DataSourcePropertyBean {
  3. private final Map<String, PropertyBean> datasourceProperties = new HashMap<>();
  4. public Map<String, PropertyBean> getDatasourceProperties() {
  5. return datasourceProperties;
  6. }
  7. @Getter
  8. @Setter
  9. public static class PropertyBean {
  10. /**
  11. * @see DruidDataSource#getUsername()
  12. */
  13. private String username;
  14. /**
  15. * @see DruidDataSource#getPassword()
  16. */
  17. private String password;
  18. /**
  19. * @see DruidDataSource#getUrl()
  20. */
  21. private String url;
  22. /**
  23. * @see RayDruidDataSource#getDiamondCoord()
  24. */
  25. private String xy;
  26. /**
  27. * @see DruidDataSource#getDriverClassName()
  28. */
  29. private String driverClassName = "com.mysql.jdbc.Driver";
  30. /**
  31. * @see DruidDataSource#getMinIdle()
  32. */
  33. private Integer minIdle = 5;
  34. /**
  35. * @see DruidDataSource#getInitialSize()
  36. */
  37. private Integer initialSize = 5;
  38. /**
  39. * @see DruidDataSource#getMaxActive()
  40. */
  41. private Integer maxActive = 10;
  42. /**
  43. * @see DruidDataSource#getMaxWait()
  44. */
  45. private Integer maxWait = 60000;
  46. /**
  47. * @see DruidDataSource#getValidationQuery()
  48. */
  49. private String validationQuery = "SELECT 'x'";
  50. /**
  51. * @see DruidDataSource#isTestWhileIdle()
  52. */
  53. private Boolean testWhileIdle = true;
  54. /**
  55. * @see DruidDataSource#isTestOnBorrow()
  56. */
  57. private Boolean testOnBorrow = false;
  58. /**
  59. * @see DruidDataSource#isTestOnReturn()
  60. */
  61. private Boolean testOnReturn = false;
  62. /**
  63. * 选择dbInfoRequest的zk地址,默认是不需要的
  64. */
  65. private String dbInfoZk;
  66. /**
  67. * 选择dbInfoRequest的dubbo版本,默认是 2.0.0_DB
  68. */
  69. private String dbInfoRequestVersion = "2.0.0_DB";
  70. /**
  71. * mybatis mapper文件扫描路径
  72. * 例如:classpath*:/mapper/dms/*Mapper.xml
  73. */
  74. private String locationPattern;
  75. /**
  76. * @see MapperScan#basePackages()
  77. */
  78. private String basePackages;
  79. }
  80. }

到这里到没有啦

总结

介绍了多数据源如何使用,以及如何写这个多数据源的 Starter ,因为注入Bean的过程和注入属性的过程不在一个阶段,所以不得不使用2个阶段来进行分离. 最终达成注入Datasource的结果. 一套下来,后来使用多数据源就舒服了.