DynamicDataSourceUtil.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. package jnpf.database.util;
  2. import cn.hutool.core.bean.BeanUtil;
  3. import cn.hutool.core.text.StrPool;
  4. import com.alibaba.druid.pool.DruidDataSource;
  5. import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
  6. import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
  7. import com.baomidou.dynamic.datasource.creator.druid.DruidConfig;
  8. import com.baomidou.dynamic.datasource.ds.ItemDataSource;
  9. import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
  10. import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
  11. import com.baomidou.dynamic.datasource.enums.DdConstants;
  12. import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
  13. import jnpf.config.ConfigValueUtil;
  14. import jnpf.database.model.entity.DbLinkEntity;
  15. import jnpf.database.source.impl.DbOracle;
  16. import jnpf.exception.DataException;
  17. import jnpf.util.LockObjectUtil;
  18. import jnpf.util.StringUtil;
  19. import jnpf.util.TenantHolder;
  20. import lombok.Setter;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.apache.commons.collections4.map.LRUMap;
  23. import org.springframework.beans.BeanUtils;
  24. import org.springframework.beans.factory.annotation.Qualifier;
  25. import org.springframework.stereotype.Component;
  26. import javax.sql.DataSource;
  27. import java.sql.Connection;
  28. import java.sql.SQLException;
  29. import java.util.Optional;
  30. /**
  31. * 动态切换数据源, 配合DynamicDatasource数据源
  32. *
  33. * @author JNPF开发平台组
  34. * @user N
  35. * @copyright 引迈信息技术有限公司
  36. * @date 2022/9/26 20:57
  37. */
  38. @Setter
  39. @Slf4j
  40. @Component
  41. public class DynamicDataSourceUtil {
  42. private static int MAX_DATASOURCE_COUNT = 300;
  43. //最多保存三百个数据源, 按使用率淘汰
  44. private static LRUMap<String, DbLinkEntity> linksProperties = new LRUMap(MAX_DATASOURCE_COUNT);
  45. public static DataSourceUtil dataSourceUtil;
  46. public static DynamicRoutingDataSource dynamicRoutingDataSource;
  47. public static DynamicDataSourceProperties dynamicDataSourceProperties;
  48. private static DefaultDataSourceCreator defaultDataSourceCreator;
  49. private static ConfigValueUtil configValueUtil;
  50. public DynamicDataSourceUtil(@Qualifier("dataSourceSystem") DataSource dynamicRoutingDataSource
  51. , DynamicDataSourceProperties dynamicDataSourceProperties
  52. , DefaultDataSourceCreator defaultDataSourceCreator
  53. , ConfigValueUtil configValueUtil
  54. , DataSourceUtil dataSourceUtil) {
  55. DynamicDataSourceUtil.dynamicRoutingDataSource = (DynamicRoutingDataSource) dynamicRoutingDataSource;
  56. DynamicDataSourceUtil.dynamicDataSourceProperties = dynamicDataSourceProperties;
  57. DynamicDataSourceUtil.defaultDataSourceCreator = defaultDataSourceCreator;
  58. DynamicDataSourceUtil.configValueUtil = configValueUtil;
  59. DynamicDataSourceUtil.dataSourceUtil = new DataSourceUtil();
  60. BeanUtils.copyProperties(dataSourceUtil, DynamicDataSourceUtil.dataSourceUtil);
  61. }
  62. /**
  63. * 创建并切换至远程数据源
  64. *
  65. * @param userName
  66. * @param password
  67. * @param url
  68. * @throws DataException
  69. * @throws SQLException
  70. */
  71. public static DbLinkEntity switchToDataSource(String userName, String password, String url, String dbType) throws DataException, SQLException {
  72. String tenantId = Optional.ofNullable(TenantHolder.getDatasourceId()).orElse("");
  73. String dbKey = tenantId + userName + password + url;
  74. DbLinkEntity dbLinkEntity = new DbLinkEntity();
  75. dbLinkEntity.setId(dbKey);
  76. dbLinkEntity.setUserName(userName);
  77. dbLinkEntity.setPassword(password);
  78. dbLinkEntity.setUrl(url);
  79. if (StringUtil.isEmpty(dbType)) {
  80. dbLinkEntity.setDbType(DbTypeUtil.getDb(url).getJnpfDbEncode());
  81. } else {
  82. dbLinkEntity.setDbType(dbType);
  83. }
  84. switchToDataSource(dbLinkEntity);
  85. return dbLinkEntity;
  86. }
  87. /**
  88. * 创建并切换至远程数据源
  89. * dbLinkEntity 为空不或ID为空切换为默认数据源 多数据源层级 +1
  90. *
  91. * @param dbLinkEntity
  92. * @throws DataException
  93. */
  94. public static void switchToDataSource(DbLinkEntity dbLinkEntity) throws DataException, SQLException {
  95. //切换目标为系统主库 数据源层级+1
  96. if (dbLinkEntity == null || StringUtil.isEmpty(dbLinkEntity.getId()) || "0".equals(dbLinkEntity.getId())) {
  97. if(TenantHolder.isRemote()){
  98. //租户指定数据源
  99. String dbKey = TenantHolder.getDatasourceId() + StrPool.DASHED + DdConstants.MASTER;
  100. DynamicDataSourceContextHolder.push(dbKey);
  101. }else {
  102. DynamicDataSourceContextHolder.push(null);
  103. }
  104. //DataSourceContextHolder.addDynamicDatasourceLevel();
  105. return;
  106. }
  107. String tenantId = Optional.ofNullable(TenantHolder.getDatasourceId()).orElse("");
  108. String dbKey = tenantId + dbLinkEntity.getId();
  109. String removeKey = null;
  110. boolean insert = true;
  111. synchronized (LockObjectUtil.addLockKey(dbKey)) {
  112. if (dynamicRoutingDataSource.getDataSources().containsKey(dbKey)) {
  113. synchronized (linksProperties) {
  114. if (linksProperties.get(dbKey).equals(dbLinkEntity)) {
  115. insert = false;
  116. }
  117. }
  118. }
  119. if (insert) {
  120. DataSource ds = createDataSource(dbLinkEntity);
  121. //单独设置动态切换的数据源参数
  122. if (ds instanceof ItemDataSource) {
  123. if (((ItemDataSource) ds).getRealDataSource() instanceof DruidDataSource) {
  124. //运行中创建的连接, 30分钟空闲后不保留
  125. ((DruidDataSource) ((ItemDataSource) ds).getRealDataSource()).setMinIdle(0);
  126. // ((DruidDataSource)((ItemDataSource)ds).getRealDataSource()).setTimeBetweenConnectErrorMillis(DruidAbstractDataSource.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS);
  127. }
  128. }
  129. dynamicRoutingDataSource.addDataSource(dbKey, ds);
  130. synchronized (linksProperties) {
  131. if (linksProperties.size() == MAX_DATASOURCE_COUNT) {
  132. removeKey = linksProperties.firstKey();
  133. }
  134. linksProperties.put(dbKey, dbLinkEntity);
  135. }
  136. }
  137. }
  138. /*if(dbKey.contains("_")){
  139. //分组数据源
  140. dbKey = dbKey.split("_")[0];
  141. }*/
  142. DynamicDataSourceContextHolder.push(dbKey);
  143. // DataSourceContextHolder.addDynamicDatasourceLevel();
  144. if (removeKey != null) {
  145. try {
  146. dynamicRoutingDataSource.removeDataSource(removeKey);
  147. } catch (Exception e) {
  148. log.error("移除数据源失败:{}", e.getMessage());
  149. }
  150. }
  151. }
  152. /**
  153. * 移除当前设置的远程数据源, 清除上次清除之后切换的所有数据源
  154. * 需要先调用 switchToDataSource切换数据源
  155. */
  156. public static void clearSwitchDataSource() {
  157. DynamicDataSourceContextHolder.poll();
  158. // for (int i = 0; i < DataSourceContextHolder.getDynamicDatasourceLevel(); i++) {
  159. // DynamicDataSourceContextHolder.poll();
  160. // }
  161. // DataSourceContextHolder.clearDynamicDatasourceLevel();
  162. }
  163. /**
  164. * 获取当前数据源的数据链接(切库后的)
  165. * 用完之后一定要关闭
  166. *
  167. * @return
  168. * @throws SQLException
  169. */
  170. public static Connection getCurrentConnection() throws SQLException {
  171. return dynamicRoutingDataSource.getConnection();
  172. }
  173. /**
  174. * 创建数据源
  175. *
  176. * @param dbLinkEntity
  177. * @param <T>
  178. * @return
  179. * @throws DataException
  180. * @throws SQLException
  181. */
  182. public static <T extends DataSourceUtil> DataSource createDataSource(T dbLinkEntity) throws DataException, SQLException {
  183. return createDataSource(dbLinkEntity, dbLinkEntity.getUrl());
  184. }
  185. /**
  186. * 创建数据源
  187. *
  188. * @param dbLinkEntity
  189. * @param url 覆盖自动生成的连接地址
  190. * @param <T>
  191. * @return
  192. * @throws DataException
  193. * @throws SQLException
  194. */
  195. public static <T extends DataSourceUtil> DataSource createDataSource(T dbLinkEntity, String url) throws DataException {
  196. DataSourceProperty dataSourceProperty = createDataSourceProperty(dbLinkEntity, url);
  197. DataSource ds = defaultDataSourceCreator.createDataSource(dataSourceProperty);
  198. return ds;
  199. }
  200. public static <T extends DataSourceUtil> DataSourceProperty createDataSourceProperty(T dbLinkEntity, String url){
  201. if (StringUtil.isEmpty(url)) {
  202. url = ConnUtil.getUrl(dbLinkEntity, dbLinkEntity.getDbName());
  203. }
  204. DataSourceProperty dataSourceProperty = new DataSourceProperty();
  205. dataSourceProperty.setUsername(dbLinkEntity.getAutoUsername());
  206. dataSourceProperty.setPassword(dbLinkEntity.getAutoPassword());
  207. dataSourceProperty.setUrl(url);
  208. dataSourceProperty.setDriverClassName(DbTypeUtil.getDb(dbLinkEntity).getDriver());
  209. DruidConfig druidConfig = BeanUtil.copyProperties(dynamicDataSourceProperties.getDruid(), DruidConfig.class);
  210. dataSourceProperty.setLazy(true);
  211. druidConfig.setBreakAfterAcquireFailure(true);
  212. //不同库设置检查SQL语句,SqlServer, Mysql, Oracle, Postgre已有默认SQL检查器
  213. if (druidConfig.getValidationQuery() == null && (DbTypeUtil.checkKingbase(dbLinkEntity) || DbTypeUtil.checkDM(dbLinkEntity))) {
  214. druidConfig.setValidationQuery("select 1;");
  215. }
  216. // oracle参数处理
  217. if (DbTypeUtil.checkOracle(dbLinkEntity)) {
  218. druidConfig.setConnectionProperties(DbOracle.setConnProp("Default", dbLinkEntity.getUserName(), dbLinkEntity.getPassword()));
  219. }
  220. dataSourceProperty.setDruid(druidConfig);
  221. return dataSourceProperty;
  222. }
  223. /**
  224. * 当前是否是主库环境
  225. * @return
  226. */
  227. public static boolean isPrimaryDataSoure() {
  228. String dsKey = DynamicDataSourceContextHolder.peek();
  229. return StringUtil.isEmpty(dsKey) || dynamicDataSourceProperties.getPrimary().equals(dsKey);
  230. }
  231. /**
  232. * 获取主库数据源类型
  233. * @return
  234. */
  235. public static String getPrimaryDbType() {
  236. return dataSourceUtil.getDbType();
  237. }
  238. public static DataSourceUtil getDataSourceUtil(){
  239. return dataSourceUtil;
  240. }
  241. public static boolean containsLink(String key) {
  242. return linksProperties.containsKey(key);
  243. }
  244. }