package com.poyee.datasource; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.github.yulichang.injector.MPJSqlInjector; import com.github.yulichang.interceptor.MPJInterceptor; import com.poyee.datasource.common.DbInitUtil; import com.poyee.datasource.modal.DbConfigModal; import com.poyee.util.ParseUtil; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.io.VFS; import org.apache.ibatis.session.SqlSessionFactory; import org.jetbrains.annotations.Nullable; import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; import javax.sql.DataSource; import java.io.IOException; import java.sql.SQLException; import java.util.*; /** * 多数据源配置 */ @Slf4j @Configuration @MapperScan(basePackages = {"com.poyee.mapper","com.poyee.**.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory") public class DataSourceConfig { @Value("${spring.datasource.dynamic.datasource.master.url}") private String url; // 连接池连接信息 @Value("${spring.datasource.druid.initialSize}") private int initialSize; @Value("${spring.datasource.druid.minIdle}") private int minIdle; @Value("${spring.datasource.druid.maxActive}") private int maxActive; @Value("${spring.datasource.druid.maxWait}") private int maxWait; @Value("${spring.profiles.active:dev}") private String version; static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; @Autowired private Environment env; /** * @param typeAliasesPackage * @return */ private static String setTypeAliasesPackage(String typeAliasesPackage) { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); List allResult = new ArrayList<>(); try { for (String aliasesPackage : typeAliasesPackage.split(",")) { List result = new ArrayList<>(); aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; Resource[] resources = resolver.getResources(aliasesPackage); if (resources != null && resources.length > 0) { MetadataReader metadataReader = null; for (Resource resource : resources) { if (resource.isReadable()) { metadataReader = metadataReaderFactory.getMetadataReader(resource); try { result.add(Class.forName(metadataReader.getClassMetadata() .getClassName()) .getPackage() .getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } if (result.size() > 0) { HashSet hashResult = new HashSet(result); allResult.addAll(hashResult); } } if (allResult.size() > 0) { typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); } else { throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); } } catch (IOException e) { e.printStackTrace(); } return typeAliasesPackage; } /** * 创建数据源 * @return */ @Bean("masterDataSource") @Primary @Qualifier("masterDataSource") @ConfigurationProperties(prefix = "spring.datasource.dynamic.datasource.master") public DruidDataSource dataSource() { //注册时 使用多数据源中 的master数据源 配置 DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } /** * 动态数据源配置 sqlSessionFactory * @return * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage"); String mapperLocations = env.getProperty("mybatis.mapperLocations"); String configLocation = env.getProperty("mybatis.configLocation"); typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); VFS.addImplClass(SpringBootVFS.class); final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dynamicDataSource()); sessionFactory.setTypeAliasesPackage(typeAliasesPackage); sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations)); sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); GlobalConfig globalConfig = new GlobalConfig(); // globalConfig.setMetaObjectHandler(new MybatisMetaObjectHandler()); globalConfig.setSqlInjector(new MPJSqlInjector()); sessionFactory.setGlobalConfig(globalConfig); //加载插件 sessionFactory.setPlugins(mybatisPlusInterceptor(), new MPJInterceptor()); return sessionFactory.getObject(); } @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL)); return interceptor; } /** * 动态数据源配置 * @return */ @Bean(name = "dynamicDataSource") @Qualifier("dynamicDataSource") public DynamicDataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); //配置缺省的数据源 // 默认数据源配置 DefaultTargetDataSource dynamicDataSource.setDefaultTargetDataSource(dataSource()); Map targetDataSources = new HashMap<>(); //额外数据源配置 TargetDataSources targetDataSources.put("master", dataSource()); try { List dbConfigModals = new ArrayList<>(); dbConfigModals = DbInitUtil.checkDbConfig(new DbConfigModal(), "application-"+version+".yml"); if(!CollectionUtils.isEmpty(dbConfigModals)){ dbConfigModals.forEach(tableConfigDto -> { //解析处理数据连接参数 Map env = System.getenv(); String dataSource = tableConfigDto.getDataSource().toUpperCase(); String url = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbUrl()); String username = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbUsername()); String pwd = ParseUtil.parse(ParseUtil.mathStr_1, tableConfigDto.getDbPwd()); if(!Objects.isNull(env)){ url = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_URL")) ? url : env.get(dataSource + "_DATASOURCE_URL"); username = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_USERNAME")) ? username : env.get(dataSource + "_DATASOURCE_USERNAME"); pwd = StringUtils.isBlank(env.get(dataSource + "_DATASOURCE_PASSWORD")) ? pwd : env.get(dataSource + "_DATASOURCE_PASSWORD"); } tableConfigDto.setDbUrl(getDbParams("DATASOURCE_URL:",url)); tableConfigDto.setDbUsername(getDbParams("DATASOURCE_USERNAME:",username)); tableConfigDto.setDbPwd(getDbParams("DATASOURCE_PASSWORD:",pwd)); if(StringUtils.isNotBlank(tableConfigDto.getDataSource())){ dynamicDataSource.testDatasource(tableConfigDto.getDataSource(),tableConfigDto.getDriverClassName(), tableConfigDto.getDbUrl(), tableConfigDto.getDbUsername(), tableConfigDto.getDbPwd()); try { targetDataSources.put(tableConfigDto.getDataSource(), initDataSource(tableConfigDto)); } catch (SQLException e) { log.info(" 注册数据源 SLAVE 失败 "); } } }); } } catch (Exception e) { log.info(" 注册数据源 SLAVE 失败 "); e.printStackTrace(); } dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Nullable private static String getDbParams(String formatStr , String url) { if(StringUtils.isNotBlank(url)){ String[] urls = url.split(formatStr); if(urls.length>1){ url = urls[1]; } } return url; } /** * 初始化数据源 * @param config * @return * @throws SQLException */ private DataSource initDataSource(DbConfigModal config) throws SQLException{ DruidDataSource datasource = new DruidDataSource(); // 基础连接信息 datasource.setUrl(config.getDbUrl()); datasource.setUsername(config.getDbUsername()); datasource.setPassword(config.getDbPwd()); datasource.setDriverClassName(config.getDriverClassName()); // 连接池连接信息 datasource.setInitialSize(initialSize); datasource.setMinIdle(minIdle); datasource.setMaxActive(maxActive); datasource.setMaxWait(maxWait); datasource.setConnectionErrorRetryAttempts(3);//失败后重试次数 datasource.setBreakAfterAcquireFailure(true);//请求失败之后中断 datasource.setPoolPreparedStatements(true); //是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 datasource.setMaxPoolPreparedStatementPerConnectionSize(20); datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//对于耗时长的查询sql,会受限于ReadTimeout的控制,单位毫秒 datasource.setTestOnBorrow(true); //申请连接时执行validationQuery检测连接是否有效,这里建议配置为TRUE,防止取到的连接不可用 datasource.setTestWhileIdle(true);//建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 String validationQuery = "select 1 "; datasource.setValidationQuery(validationQuery); //用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 datasource.setFilters("stat,wall");//属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall datasource.setTimeBetweenEvictionRunsMillis(60000); //配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 datasource.setMinEvictableIdleTimeMillis(180000); //配置一个连接在池中最小生存的时间,单位是毫秒,这里配置为3分钟180000 datasource.setKeepAlive(true); //打开druid.keepAlive之后,当连接池空闲时,池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作,即执行druid.validationQuery指定的查询SQL,一般为select * from dual,只要minEvictableIdleTimeMillis设置的小于防火墙切断连接时间,就可以保证当连接空闲时自动做保活检测,不会被防火墙切断 datasource.setRemoveAbandoned(true); //是否移除泄露的连接/超过时间限制是否回收。 datasource.setRemoveAbandonedTimeout(3600); //泄露连接的定义时间(要超过最大事务的处理时间);单位为秒。这里配置为1小时 datasource.setLogAbandoned(true); ////移除泄露连接发生是是否记录日志 return datasource; } // =================== 事务管理器配置 =================== /** * 主数据源事务管理器 * 用于显式指定 master 数据源的事务 */ @Bean("masterTransactionManager") @Primary public PlatformTransactionManager masterTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 动态数据源事务管理器 * 推荐与 @DS 注解配合使用,能感知数据源切换 */ @Bean("dynamicTransactionManager") public PlatformTransactionManager dynamicTransactionManager( @Qualifier("dynamicDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }