程序無需重啓 能夠加個過濾器支持未設置數據庫沒法調用 將配置存到property文件,也可存到緩存 設置時須要一個工具類判斷傳入的數據庫是否可鏈接等 import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.tomcat.jdbc.pool.DataSource; import org.apache.tomcat.jdbc.pool.PoolProperties; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLNonTransientConnectionException; /** * 功能:定義動態數據源派生類。從基礎的DataSource派生,動態性本身實現。 * 做者: * 日期:2018-10-13 */ public class DynamicDataSource extends DataSource { protected LoggerUtil logger = LogFactory.getLogger(getClass()); /** * 改寫本方法是爲了在請求不一樣工程的數據時去鏈接不一樣的數據庫。 */ @Override public Connection getConnection() { logger.error("DynamicDataSource getConnection"); String ip = DBIdentifier.getIp(); if (StringUtils.isBlank(ip)) { PropertiesConfiguration conf = null; try { conf = new PropertiesConfiguration("application-test.properties"); ip = String.valueOf(conf.getString("spring.datasource.write.ip")); } catch (org.apache.commons.configuration.ConfigurationException e) { e.printStackTrace(); } } if (org.apache.commons.lang.StringUtils.length(ip) < 8) { return null; } //一、獲取數據源 DataSource dds = DDSHolder.instance().getDDS(ip); //二、若是數據源不存在則建立 if (dds == null) { try { DataSource newDDS = initDDS(ip); DDSHolder.instance().addDDS(ip, newDDS); } catch (IllegalArgumentException | IllegalAccessException e) { logger.error("Init data source fail. projectCode:" + ip); return null; } } dds = DDSHolder.instance().getDDS(ip); Connection connection = null; try { connection = dds.getConnection(); return connection; } catch (SQLNonTransientConnectionException Exception) { return null; } catch (SQLException e) { return null; } catch (Exception e) { return null; } } /** * 以當前數據對象做爲模板複製一份。 * * @return dds * @throws IllegalAccessException * @throws IllegalArgumentException */ private DataSource initDDS(String ip) throws IllegalArgumentException, IllegalAccessException { logger.error("DynamicDataSource initDDS ip:" + ip); DataSource dds = new DataSource(); // 二、複製PoolConfiguration的屬性 PoolProperties property = new PoolProperties(); Field[] pfields = PoolProperties.class.getDeclaredFields(); for (Field f : pfields) { f.setAccessible(true); Object value = f.get(this.getPoolProperties()); try { f.set(property, value); } catch (Exception e) { //有一些static final的屬性不能修改。忽略。 logger.info("Set value fail. attr name:" + f.getName()); continue; } } dds.setPoolProperties(property); // 三、設置數據庫名稱和IP(通常來講,端口和用戶名、密碼都是統一固定的) String url = "jdbc:mysql://" + ip + ":3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&useSSL=true&serverTimezone=Asia/Shanghai"; dds.setUrl(url); return dds; } }
import com.github.pagehelper.PageHelper; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.boot.autoconfigure.SpringBootVFS; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.PropertySource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.TransactionManagementConfigurer; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * 做用:數據源配置類 */ @Configuration @EnableTransactionManagement public class DataSourceConfig implements TransactionManagementConfigurer { @Value("${spring.datasource.type}") private Class<? extends DataSource> dataSourceType; @Value("${datasouce.aliases}") private String typeAliasesPackage; /** * 主數據庫 * @return */ @Primary @Bean(name = "writeDataSource") @ConfigurationProperties(prefix = "spring.datasource.write") public DataSource writeDataSource() { return DataSourceBuilder.create().type(dataSourceType).build(); } @Bean(name="dataSource") @ConfigurationProperties(prefix="spring.datasource.write") public DataSource getDataSource() { DataSourceBuilder builder = DataSourceBuilder.create(); builder.type(DynamicDataSource.class); return builder.build(); } /** * 有多少個從庫就要配置多少個,當前只有一個 * @return */ // @Bean(name = "readDataSource") // @ConfigurationProperties(prefix = "spring.datasource.read") // public DataSource readDataSourceOne() { // return DataSourceBuilder.create().type(dataSourceType).build(); // } /** * 將多個數據源放入數據源路由集合中 * @return */ @Bean("routingDataSource") public RoutingDataSource dataSourceProxy(){ RoutingDataSource proxy = new RoutingDataSource(); Map<Object,Object> dataSourceMap = new HashMap<>(); dataSourceMap.put(TargetDataSource.DYNIC.getCode(),getDataSource()); dataSourceMap.put(TargetDataSource.WRITE.getCode(),writeDataSource()); // dataSourceMap.put(TargetDataSource.READ.getCode(),readDataSourceOne()); proxy.setDefaultTargetDataSource(dataSourceMap.get(TargetDataSource.WRITE.getCode())); //proxy.setDefaultTargetDataSource(dataSourceMap.get(TargetDataSource.DYNIC.getCode())); proxy.setTargetDataSources(dataSourceMap); return proxy; } /** * 初始化SqlSessionFactory,設置掃描的mapper路徑和entity包路徑 * @return */ @Bean public SqlSessionFactory sqlSessionFactory() { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSourceProxy()); bean.setTypeAliasesPackage(typeAliasesPackage); bean.setVfs(SpringBootVFS.class); /** * 設置分頁插件 */ PageHelper pageHelper = new PageHelper(); Properties properties = new Properties(); properties.setProperty("offsetAsPageNum", "true"); properties.setProperty("rowBoundsWithCount", "true"); properties.setProperty("reasonable", "true"); pageHelper.setProperties(properties); bean.setPlugins(new Interceptor[] {pageHelper}); //添加XML目錄 ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); try { bean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*.xml")); return bean.getObject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 事務配置,考慮多數據源狀況下 * @return */ @Bean public PlatformTransactionManager txManager() { return new DataSourceTransactionManager(dataSourceProxy()); } @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return txManager(); } }
package com.mwkj.platform.cloud.application; import com.mwkj.common.log.LogFactory; import com.mwkj.common.log.LoggerUtil; import com.mwkj.common.resp.ResponseMessage; import com.mwkj.common.resp.ResponseMessageCodeEnum; import com.mwkj.common.service.dao.dds.DBIdentifier; import com.mwkj.common.service.dao.ds.DataSourceContextHolder; import com.mwkj.common.util.JsonUtils; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.RequestFacade; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang3.StringUtils; import org.apache.tomcat.util.buf.MessageBytes; import org.springframework.http.HttpStatus; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /** * 功能:在SpringBoot中經過註解註冊的方式簡單的使用Filter * 日期:2018-10-13 */ public class FileterController implements Filter { private static final LoggerUtil logger = LogFactory.getLogger(FileterController.class); @Override public void init(FilterConfig filterConfig) throws ServletException { logger.info("Filter初始化中"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { logger.info("Filter開始進行過濾處理"); DataSourceContextHolder.dynic(); HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; if (StringUtils.equals("/api/http/v1/users", httpServletRequest.getRequestURI()) == false) { String ip = ""; PropertiesConfiguration conf = null; try { conf = new PropertiesConfiguration("application-test.properties"); ip = String.valueOf(conf.getString("spring.datasource.write.ip")); } catch (org.apache.commons.configuration.ConfigurationException e) { e.printStackTrace(); } if (org.apache.commons.lang.StringUtils.length(ip) < 8) { HttpServletResponse response = (HttpServletResponse) servletResponse; ((HttpServletResponse) response).setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json; charset=utf-8"); String json= null; try { json = JsonUtils.writeValueAsString(ResponseMessage.failResponse("數據庫未配置")); } catch (Exception e) { } response.getWriter().print(json); return; } } //調用該方法後,表示過濾器通過原來的url請求處理方法 filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { logger.info("Filter銷燬中"); } }
@RequestMapping(value = "/v1/users", method = RequestMethod.GET) public ResponseMessage queryUser(@RequestParam(value = "ip", required = true) String ip) { DataSourceConfig dataSourceConfig =new DataSourceConfig(); dataSourceConfig.setDriverClass("com.mysql.jdbc.Driver"); dataSourceConfig.setDbName("test"); dataSourceConfig.setUsername(""); dataSourceConfig.setPassword(""); dataSourceConfig.setPort(3306); dataSourceConfig.setIp(ip); String connectInfo = DBConnect.testConnection(dataSourceConfig); if (org.springframework.util.StringUtils.hasText(connectInfo)) { return ResponseMessage.failResponse("沒法鏈接到" + ip + "上面的數據庫"); } DBIdentifier.setIp(ip); PropertiesConfiguration conf = null; try { conf = new PropertiesConfiguration("application-test.properties"); conf.setProperty("spring.datasource.write.ip", ip); conf.save(); } catch (ConfigurationException e) { e.printStackTrace(); } ResponseMessage resp = parkFreeService.findAllList(); return ResponseMessage.successResponse(resp); }
package com.mwkj.common.service.util.db; import java.util.HashMap; import java.util.Map; /** * 日期:2018-10-13 */ public class DataBaseConfig { private static Map<String, String> jdbcUrlMap = new HashMap<String,String>(); static { // com.mysql.jdbc.Driver // net.sourceforge.jtds.jdbc.Driver // com.microsoft.sqlserver.jdbc.SQLServerDriver jdbcUrlMap.put("com.mysql.jdbc.Driver", "jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=UTF-8"); jdbcUrlMap.put("net.sourceforge.jtds.jdbc.Driver", "jdbc:jtds:sqlserver://%s:%s;databaseName=%s"); } private String dbName; private String driverClass; private String ip; private int port; private String username; private String password; public String getJdbcUrl() { String url = jdbcUrlMap.get(driverClass); return String.format(url, ip,port,dbName); } public String getDbName() { return dbName; } public void setDbName(String dbName) { this.dbName = dbName; } public String getDriverClass() { return driverClass; } public void setDriverClass(String driverClass) { this.driverClass = driverClass; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } package com.mwkj.common.service.util.db; /** * 功能: * 日期:2018-10-13 */ public class DataSourceConfig extends DataBaseConfig { private int dcId; private String backUser; public int getDcId() { return dcId; } public void setDcId(int dcId) { this.dcId = dcId; } public String getBackUser() { return backUser; } public void setBackUser(String backUser) { this.backUser = backUser; } } package com.mwkj.common.service.util.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; /** * 功能: * 日期:2018-10-13 */ public class DBConnect { public static Connection getConnection(DataSourceConfig config) throws ClassNotFoundException, SQLException { Class.forName(config.getDriverClass()); return DriverManager.getConnection(config.getJdbcUrl(), config.getUsername(), config.getPassword()); } /** * 測試鏈接,返回錯誤信息,無返回內容說明鏈接成功 * * @param dataSourceConfig * @return 返回錯誤信息,無返回內容說明鏈接成功 */ public static String testConnection(DataSourceConfig dataSourceConfig) { Connection con = null; String ret = null; try { con = DBConnect.getConnection(dataSourceConfig); // 不爲空說明鏈接成功 if (con == null) { ret = dataSourceConfig.getDbName() + "鏈接失敗"; } } catch (ClassNotFoundException e) { ret = dataSourceConfig.getDbName() + "鏈接失敗" + "<br>錯誤信息:" + "找不到驅動" + dataSourceConfig.getDriverClass(); } catch (SQLException e) { ret = dataSourceConfig.getDbName() + "鏈接失敗" + "<br>錯誤信息:" + e.getMessage(); } finally { if (con != null) { try { con.close(); // 關閉鏈接,該鏈接無實際用處 } catch (SQLException e) { e.printStackTrace(); } } } return ret; } } public enum TargetDataSource { WRITE("write", "主庫"), READ("read", "從庫"), DYNIC("dynic", "動態"); final private String code; final private String name; TargetDataSource(String _code, String _name) { this.code = _code; this.name = _name; } public String getCode() { return code; } public String getName() { return name; } public static String getNameByCode(String _code) { for (TargetDataSource item : TargetDataSource.values()) { if (item.getCode().equals(_code)) { return item.getName(); } } return ""; } } import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * 時間:2017-11-30 * 做用:獲取數據源名稱,最終根據這個名稱選擇數據源 */ public class RoutingDataSource extends AbstractRoutingDataSource { /** * 獲取數據源 * @return */ @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getTargetDataSource(); } } @Configuration public class MyBatisMapperScannerConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory"); mapperScannerConfigurer.setBasePackage("com.mwkj.**.dao"); mapperScannerConfigurer.setAnnotationClass(MyBatisRepository.class); return mapperScannerConfigurer; } }
public class DataSourceContextHolder { private static final ThreadLocal<String> dataSourceLocal = new ThreadLocal<String>(); public static ThreadLocal<String> getDataSourceLocal() { return dataSourceLocal; } /** * 從庫當前只有一個後面能夠增長 */ public static void read() { dataSourceLocal.set(TargetDataSource.READ.getCode()); } /** * 主庫只有一個 */ public static void write() { dataSourceLocal.set(TargetDataSource.WRITE.getCode()); } /** * 主庫只有一個 */ public static void dynic() { dataSourceLocal.set(TargetDataSource.DYNIC.getCode()); } public static void setTarget(String key) { dataSourceLocal.set(key); } /** * 獲取當前的數據源名稱 * @return */ public static String getTargetDataSource() { return dataSourceLocal.get(); } public static void clearDataSourceKey(){ dataSourceLocal.remove(); } } public class DBIdentifier { /** * 用不一樣的工程編碼來區分數據庫 */ private static ThreadLocal<String> ips = new ThreadLocal<String>(); public static String getIp() { return ips.get(); } public static void setIp(String ip) { ips.set(ip); } } /** * 功能:動態數據源管理器。 * 日期:2018-10-13 */ public class DDSHolder { /** * 管理動態數據源列表。<工程編碼,數據源> */ private Map<String, DDSTimer> ddsMap = new HashMap<String, DDSTimer>(); /** * 經過定時任務週期性清除不使用的數據源 */ private static Timer clearIdleTask = new Timer(); static { clearIdleTask.schedule(new ClearIdleTimerTask(), 5000, 60 * 1000); }; private DDSHolder() { } /* * 獲取單例對象 */ public static DDSHolder instance() { return DDSHolderBuilder.instance; } /** * 添加動態數據源。 * * @param projectCode 項目編碼 * @param dds dds */ public synchronized void addDDS(String projectCode, DataSource dds) { DDSTimer ddst = new DDSTimer(dds); ddsMap.put(projectCode, ddst); } /** * 查詢動態數據源 * * @param projectCode 項目編碼 * @return dds */ public synchronized DataSource getDDS(String projectCode) { if (ddsMap.containsKey(projectCode)) { DDSTimer ddst = ddsMap.get(projectCode); ddst.refreshTime(); return ddst.getDds(); } return null; } /** * 清除超時無人使用的數據源。 */ public synchronized void clearIdleDDS() { Iterator<Entry<String, DDSTimer>> iter = ddsMap.entrySet().iterator(); for (; iter.hasNext(); ) { Entry<String, DDSTimer> entry = iter.next(); if (entry.getValue().checkAndClose()) { iter.remove(); } } } /** * 單例構件類 * @author elon * @version 2018年2月26日 */ private static class DDSHolderBuilder { private static DDSHolder instance = new DDSHolder(); } } package com.mwkj.common.service.dao.dds; import org.apache.tomcat.jdbc.pool.DataSource; /** * 功能:動態數據源定時器管理。長時間無訪問的數據庫鏈接關閉。 * 日期:2018-10-13 */ public class DDSTimer { /** * 空閒時間週期。超過這個時長沒有訪問的數據庫鏈接將被釋放。默認爲10分鐘。 */ private static long idlePeriodTime = 10 * 60 * 1000; /** * 動態數據源 */ private DataSource dds; /** * 上一次訪問的時間 */ private long lastUseTime; public DDSTimer(DataSource dds) { this.dds = dds; this.lastUseTime = System.currentTimeMillis(); } /** * 更新最近訪問時間 */ public void refreshTime() { lastUseTime = System.currentTimeMillis(); } /** * 檢測數據鏈接是否超時關閉。 * * @return true-已超時關閉; false-未超時 */ public boolean checkAndClose() { if (System.currentTimeMillis() - lastUseTime > idlePeriodTime) { dds.close(); return true; } return false; } public DataSource getDds() { return dds; } } package com.mwkj.common.service.dao.dds; import java.util.TimerTask; /** * 功能:清除空閒鏈接任務。 * 日期:2018-10-13 */ public class ClearIdleTimerTask extends TimerTask { @Override public void run() { DDSHolder.instance().clearIdleDDS(); } } /** * Copyright © 2012-2013 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); */ package com.mwkj.common.service.util.context; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Lazy; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.stereotype.Service; import java.io.IOException; /** * 以靜態變量保存Spring ApplicationContext, 可在任何代碼任何地方任什麼時候候取出ApplicaitonContext. * * @date 2013-5-29 下午1:25:40 */ @Service @Lazy(false) public class ContextUtils implements ApplicationContextAware, DisposableBean { private static ApplicationContext applicationContext = null; private static Logger logger = LoggerFactory.getLogger(ContextUtils.class); /** * 取得存儲在靜態變量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { return applicationContext; } public static String getRootRealPath() { String rootRealPath = ""; try { rootRealPath = getApplicationContext().getResource("").getFile().getAbsolutePath(); } catch (IOException e) { logger.warn("獲取系統根目錄失敗"); } return rootRealPath; } public static String getResourceRootRealPath() { String rootRealPath = ""; try { rootRealPath = new DefaultResourceLoader().getResource("").getFile().getAbsolutePath(); } catch (IOException e) { logger.warn("獲取資源根目錄失敗"); } return rootRealPath; } /** * 從靜態變量applicationContext中取得Bean, 自動轉型爲所賦值對象的類型. */ @SuppressWarnings("unchecked") public static <T> T getBean(String name) { return (T) applicationContext.getBean(name); } /** * 從靜態變量applicationContext中取得Bean, 自動轉型爲所賦值對象的類型. */ public static <T> T getBean(Class<T> requiredType) { return applicationContext.getBean(requiredType); } /** * 清除SpringContextHolder中的ApplicationContext爲Null. */ public static void clearHolder() { if (logger.isDebugEnabled()) { logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext); } applicationContext = null; } /** * 實現ApplicationContextAware接口, 注入Context到靜態變量中. */ @Override public void setApplicationContext(ApplicationContext applicationContext) { if (ContextUtils.applicationContext != null) { logger.info("SpringContextHolder中的ApplicationContext被覆蓋, 原有ApplicationContext爲:" + ContextUtils.applicationContext); } ContextUtils.applicationContext = applicationContext; } @Override public void destroy() throws Exception { ContextUtils.clearHolder(); } }