DataSourceConfig.java 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. package com.poyee.datasource;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
  4. import com.baomidou.mybatisplus.annotation.DbType;
  5. import com.baomidou.mybatisplus.core.config.GlobalConfig;
  6. import com.baomidou.mybatisplus.core.toolkit.StringUtils;
  7. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  8. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
  9. import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
  10. import com.github.yulichang.injector.MPJSqlInjector;
  11. import com.github.yulichang.interceptor.MPJInterceptor;
  12. import com.poyee.datasource.common.DbInitUtil;
  13. import com.poyee.datasource.modal.DbConfigModal;
  14. import com.poyee.util.ParseUtil;
  15. import lombok.extern.slf4j.Slf4j;
  16. import org.apache.ibatis.io.VFS;
  17. import org.apache.ibatis.session.SqlSessionFactory;
  18. import org.jetbrains.annotations.Nullable;
  19. import org.mybatis.spring.annotation.MapperScan;
  20. import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
  21. import org.springframework.beans.factory.annotation.Autowired;
  22. import org.springframework.beans.factory.annotation.Qualifier;
  23. import org.springframework.beans.factory.annotation.Value;
  24. import org.springframework.boot.context.properties.ConfigurationProperties;
  25. import org.springframework.boot.jdbc.DataSourceBuilder;
  26. import org.springframework.context.annotation.Bean;
  27. import org.springframework.context.annotation.Configuration;
  28. import org.springframework.context.annotation.Primary;
  29. import org.springframework.core.env.Environment;
  30. import org.springframework.core.io.DefaultResourceLoader;
  31. import org.springframework.core.io.Resource;
  32. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  33. import org.springframework.core.io.support.ResourcePatternResolver;
  34. import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
  35. import org.springframework.core.type.classreading.MetadataReader;
  36. import org.springframework.core.type.classreading.MetadataReaderFactory;
  37. import org.springframework.jdbc.datasource.DataSourceTransactionManager;
  38. import org.springframework.transaction.PlatformTransactionManager;
  39. import org.springframework.util.ClassUtils;
  40. import org.springframework.util.CollectionUtils;
  41. import javax.sql.DataSource;
  42. import java.io.IOException;
  43. import java.sql.SQLException;
  44. import java.util.*;
  45. /**
  46. * 多数据源配置
  47. */
  48. @Slf4j
  49. @Configuration
  50. @MapperScan(basePackages = {"com.poyee.mapper","com.poyee.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory")
  51. public class DataSourceConfig {
  52. @Value("${spring.datasource.dynamic.datasource.master.url}")
  53. private String url;
  54. // 连接池连接信息
  55. @Value("${spring.datasource.druid.initialSize}")
  56. private int initialSize;
  57. @Value("${spring.datasource.druid.minIdle}")
  58. private int minIdle;
  59. @Value("${spring.datasource.druid.maxActive}")
  60. private int maxActive;
  61. @Value("${spring.datasource.druid.maxWait}")
  62. private int maxWait;
  63. @Value("${spring.profiles.active:dev}")
  64. private String version;
  65. static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
  66. @Autowired
  67. private Environment env;
  68. /**
  69. * @param typeAliasesPackage
  70. * @return
  71. */
  72. private static String setTypeAliasesPackage(String typeAliasesPackage) {
  73. ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
  74. MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
  75. List<String> allResult = new ArrayList<>();
  76. try {
  77. for (String aliasesPackage : typeAliasesPackage.split(",")) {
  78. List<String> result = new ArrayList<>();
  79. aliasesPackage =
  80. ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
  81. Resource[] resources = resolver.getResources(aliasesPackage);
  82. if (resources != null && resources.length > 0) {
  83. MetadataReader metadataReader = null;
  84. for (Resource resource : resources) {
  85. if (resource.isReadable()) {
  86. metadataReader = metadataReaderFactory.getMetadataReader(resource);
  87. try {
  88. result.add(Class.forName(metadataReader.getClassMetadata()
  89. .getClassName())
  90. .getPackage()
  91. .getName());
  92. } catch (ClassNotFoundException e) {
  93. e.printStackTrace();
  94. }
  95. }
  96. }
  97. }
  98. if (result.size() > 0) {
  99. HashSet<String> hashResult = new HashSet<String>(result);
  100. allResult.addAll(hashResult);
  101. }
  102. }
  103. if (allResult.size() > 0) {
  104. typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
  105. } else {
  106. throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
  107. }
  108. } catch (IOException e) {
  109. e.printStackTrace();
  110. }
  111. return typeAliasesPackage;
  112. }
  113. /**
  114. * 创建数据源
  115. * @return
  116. */
  117. @Bean("masterDataSource")
  118. @Primary
  119. @Qualifier("masterDataSource")
  120. @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master")
  121. public DruidDataSource dataSource() {
  122. //注册时 使用多数据源中 的master数据源 配置
  123. DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
  124. return dataSource;
  125. }
  126. /**
  127. * 动态数据源配置 sqlSessionFactory
  128. * @return
  129. * @throws Exception
  130. */
  131. @Bean
  132. public SqlSessionFactory sqlSessionFactory() throws Exception {
  133. String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
  134. String mapperLocations = env.getProperty("mybatis.mapperLocations");
  135. String configLocation = env.getProperty("mybatis.configLocation");
  136. typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
  137. VFS.addImplClass(SpringBootVFS.class);
  138. final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
  139. sessionFactory.setDataSource(dynamicDataSource());
  140. sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
  141. sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
  142. sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
  143. GlobalConfig globalConfig = new GlobalConfig();
  144. // globalConfig.setMetaObjectHandler(new MybatisMetaObjectHandler());
  145. globalConfig.setSqlInjector(new MPJSqlInjector());
  146. sessionFactory.setGlobalConfig(globalConfig);
  147. //加载插件
  148. sessionFactory.setPlugins(mybatisPlusInterceptor(), new MPJInterceptor());
  149. return sessionFactory.getObject();
  150. }
  151. @Bean
  152. public MybatisPlusInterceptor mybatisPlusInterceptor(){
  153. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  154. interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
  155. return interceptor;
  156. }
  157. /**
  158. * 动态数据源配置
  159. * @return
  160. */
  161. @Bean(name = "dynamicDataSource")
  162. @Qualifier("dynamicDataSource")
  163. public DynamicDataSource dynamicDataSource() {
  164. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  165. //配置缺省的数据源
  166. // 默认数据源配置 DefaultTargetDataSource
  167. dynamicDataSource.setDefaultTargetDataSource(dataSource());
  168. Map<Object, Object> targetDataSources = new HashMap<>();
  169. //额外数据源配置 TargetDataSources
  170. targetDataSources.put("master", dataSource());
  171. try {
  172. List<DbConfigModal> dbConfigModals = new ArrayList<>();
  173. dbConfigModals = DbInitUtil.checkDbConfig(new DbConfigModal(), "application-"+version+".yml");
  174. if(!CollectionUtils.isEmpty(dbConfigModals)){
  175. dbConfigModals.forEach(tableConfigDto -> {
  176. //解析处理数据连接参数
  177. Map<String, String> env = System.getenv();
  178. String dataSource = tableConfigDto.getDataSource().toUpperCase();
  179. String url = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbUrl());
  180. String username = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbUsername());
  181. String pwd = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbPwd());
  182. if(!Objects.isNull(env)){
  183. url = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_URL")) ? url : env.get(dataSource + "_DATASOURCE_URL");
  184. username = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_USERNAME")) ? username : env.get(dataSource + "_DATASOURCE_USERNAME");
  185. pwd = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_PASSWORD")) ? pwd : env.get(dataSource + "_DATASOURCE_PASSWORD");
  186. }
  187. tableConfigDto.setDbUrl(getDbParams("DATASOURCE_URL:",url));
  188. tableConfigDto.setDbUsername(getDbParams("DATASOURCE_USERNAME:",username));
  189. tableConfigDto.setDbPwd(getDbParams("DATASOURCE_PASSWORD:",pwd));
  190. if(StringUtils.isNotBlank(tableConfigDto.getDataSource())){
  191. dynamicDataSource.testDatasource(tableConfigDto.getDataSource(),tableConfigDto.getDriverClassName(), tableConfigDto.getDbUrl(), tableConfigDto.getDbUsername(), tableConfigDto.getDbPwd());
  192. try {
  193. targetDataSources.put(tableConfigDto.getDataSource(), initDataSource(tableConfigDto));
  194. } catch (SQLException e) {
  195. log.info(" 注册数据源 SLAVE 失败 ");
  196. }
  197. }
  198. });
  199. }
  200. } catch (Exception e) {
  201. log.info(" 注册数据源 SLAVE 失败 ");
  202. e.printStackTrace();
  203. }
  204. dynamicDataSource.setTargetDataSources(targetDataSources);
  205. return dynamicDataSource;
  206. }
  207. @Nullable
  208. private static String getDbParams(String formatStr , String url) {
  209. if(StringUtils.isNotBlank(url)){
  210. String[] urls = url.split(formatStr);
  211. if(urls.length>1){
  212. url = urls[1];
  213. }
  214. }
  215. return url;
  216. }
  217. /**
  218. * 初始化数据源
  219. * @param config
  220. * @return
  221. * @throws SQLException
  222. */
  223. private DataSource initDataSource(DbConfigModal config) throws SQLException{
  224. DruidDataSource datasource = new DruidDataSource();
  225. // 基础连接信息
  226. datasource.setUrl(config.getDbUrl());
  227. datasource.setUsername(config.getDbUsername());
  228. datasource.setPassword(config.getDbPwd());
  229. datasource.setDriverClassName(config.getDriverClassName());
  230. // 连接池连接信息
  231. datasource.setInitialSize(initialSize);
  232. datasource.setMinIdle(minIdle);
  233. datasource.setMaxActive(maxActive);
  234. datasource.setMaxWait(maxWait);
  235. datasource.setConnectionErrorRetryAttempts(3);//失败后重试次数
  236. datasource.setBreakAfterAcquireFailure(true);//请求失败之后中断
  237. datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
  238. datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
  239. datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒
  240. datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用
  241. datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
  242. String validationQuery = "select 1 ";
  243. datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
  244. datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
  245. datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
  246. datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000
  247. datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断
  248. datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。
  249. datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时
  250. datasource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志
  251. return datasource;
  252. }
  253. // =================== 事务管理器配置 ===================
  254. /**
  255. * 主数据源事务管理器
  256. * 用于显式指定 master 数据源的事务
  257. */
  258. @Bean("masterTransactionManager")
  259. @Primary
  260. public PlatformTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
  261. return new DataSourceTransactionManager(dataSource);
  262. }
  263. /**
  264. * 动态数据源事务管理器
  265. * 推荐与 @DS 注解配合使用,能感知数据源切换
  266. */
  267. @Bean("dynamicTransactionManager")
  268. public PlatformTransactionManager dynamicTransactionManager( @Qualifier("dynamicDataSource") DataSource dataSource) {
  269. return new DataSourceTransactionManager(dataSource);
  270. }
  271. }