不考虑事务,需要事务的同学出门左转 ⬅️
看此篇之前建议先看前两篇,同志们
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 配置
#first
raycloud.multi.datasource.datasource-properties.first.username=root
raycloud.multi.datasource.datasource-properties.first.password=chenshun
raycloud.multi.datasource.datasource-properties.first.url=jdbc:mysql://127.0.0.1:3306/project_manager?useUnicode=true&characterEncoding=utf-8&autoReconnect=true
raycloud.multi.datasource.datasource-properties.first.base-packages=com.raycloud.test.web.first
raycloud.multi.datasource.datasource-properties.first.location-pattern=classpath*:/mapper/first/*.xml
#second
raycloud.multi.datasource.datasource-properties.second.username=root
raycloud.multi.datasource.datasource-properties.second.password=chenshun
raycloud.multi.datasource.datasource-properties.second.url=jdbc:mysql://127.0.0.1:3306/sentinel?useUnicode=true&characterEncoding=utf-8&autoReconnect=true
raycloud.multi.datasource.datasource-properties.second.base-packages=com.raycloud.test.web.second
raycloud.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` 中进行处理.
```java
public 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;
@Override
public 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;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public 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
@Setter
public 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的结果. 一套下来,后来使用多数据源就舒服了.