關於boot +druid多數據源內容網上都有,隨便百度一大堆,此文關鍵是在多數據源中動態從數據庫加載數據源,網上大部分內容都是配置兩個數據源使用兩個數據源,現在項目需求兩個數據源存儲基礎數據,業務數據更具業務場景,有多個數據庫,具體不定,比如一個集體公司,下邊每一個子公司都有自己獨立的業務數據庫,那么不能在配置文件中每個都添加,需要在數據庫中維護,同時業務關聯改子公司時需要查詢改子公司的數據。
下面具體內容:
maven - pom.xml配置:包與其他的自己更改
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>xxx</groupId> <artifactId>xxx</artifactId> <version>2.0.1</version> <packaging>jar</packaging> <name>xxx</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.6.RELEASE</version> <relativePath /> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <mybatisplus-spring-boot-starter.version>1.0.4</mybatisplus-spring-boot-starter.version> <velocity.version>1.7</velocity.version> <activiti.version>5.22.0</activiti.version> <mssql-version>4.0</mssql-version> <poi-version>3.15</poi-version> <qcloudsms-version>1.0.6</qcloudsms-version> <lang3-version>3.6</lang3-version> <mybatis-version>3.4.4</mybatis-version> <spring-mybatis-version>1.1.1</spring-mybatis-version> <io-version>2.5</io-version> <shiro-version>1.3.2</shiro-version> <druid-version>1.1.18</druid-version> <fastjson-version>1.2.31</fastjson-version> <configuration-version>1.10</configuration-version> <quartz-version>2.2.1</quartz-version> <swagger2-version>2.6.1</swagger2-version> <extras-shiro-version>1.2.1</extras-shiro-version> <jsoup-version>1.9.2</jsoup-version> </properties> <dependencies> <!-- java poi以及所依賴的包 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>${poi-version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>${poi-version}</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml-schemas</artifactId> <version>${poi-version}</version> </dependency> <!-- aes加密解密包 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>com.github.qcloudsms</groupId> <artifactId>qcloudsms</artifactId> <version>${qcloudsms-version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${lang3-version}</version> </dependency> <!-- 讀寫xml --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> </dependency> <!-- 讀寫xml依賴的包 --> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <!-- <version>1.1-beta-9</version> --> </dependency> <!--mssql驅動--> <dependency> <groupId>com.microsoft.sqlserver</groupId> <artifactId>sqljdbc4</artifactId> <version>${mssql-version}</version> </dependency> <!-- sqlserver驅動 --> <dependency> <groupId>net.sourceforge.jtds</groupId> <artifactId>jtds</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <!-- 移除嵌入式tomcat插件 --> <!-- <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> --> </dependency> <!-- <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency> --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> </dependency> <!--mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis-version}</version> </dependency> <!--mybatis-spring--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${spring-mybatis-version}</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid-version}</version> </dependency> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>${configuration-version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${io-version}</version> </dependency> <!--shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro-version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro-version}</version> </dependency> <!-- shiro ehcache --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro-version}</version> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>${extras-shiro-version}</version> </dependency> <!-- utils --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson-version}</version> </dependency> <!--velocity代碼生成使用模板 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity</artifactId> <version>${velocity.version}</version> </dependency> <!-- ehchache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <!--<dependency> --> <!--<groupId>org.springframework.boot</groupId> --> <!--<artifactId>spring-boot-starter-data-redis</artifactId> --> <!--</dependency> --> <!-- quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz-version}</version> <exclusions> <exclusion> <artifactId>slf4j-api</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <!-- SpringWebSocket依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> </dependency> <!--activiti --> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-engine</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-spring</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-modeler</artifactId> <version>${activiti.version}</version> </dependency> <dependency> <groupId>org.activiti</groupId> <artifactId>activiti-diagram-rest</artifactId> <version>${activiti.version}</version> </dependency> <!--swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>${swagger2-version}</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>${swagger2-version}</version> </dependency> <!-- 添加redis支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- xss過濾組件 --> <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>${jsoup-version}</version> </dependency> <!--<dependency> --> <!--<groupId>org.crazycake</groupId> --> <!--<artifactId>shiro-redis</artifactId> --> <!--<version>2.4.2.1-RELEASE</version> --> <!--</dependency> --> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>public</id> <name>aliyun nexus</name> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>false</enabled> </snapshots> </pluginRepository> </pluginRepositories> </project>
boot 中數據源配置:只給了數據源配置內容,其他配置按自己需求填寫
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver druid: default: url: jdbc:sqlserver://192.168.10.152:1433;DatabaseName=2.0 username: sa password: sa123 connection-error-retry-attempts: 1 break-after-acquire-failure: true two: url: jdbc:sqlserver://192.168.10.152:1433;DatabaseName=-DATA username: sa password: sa123 connection-error-retry-attempts: 0 break-after-acquire-failure: trueinitial-size: 1 minIdle: 3 maxActive: 20 # 配置獲取連接等待超時的時間 maxWait: 60000 # 配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接,單位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一個連接在池中最小生存的時間,單位是毫秒 minEvictableIdleTimeMillis: 30000 validationQuery: select 'x' testWhileIdle: true testOnBorrow: false testOnReturn: false # 打開PSCache,並且指定每個連接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆 filters: stat,wall,slf4j # 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 合並多個DruidDataSource的監控數據 #useGlobalDataSourceStat: true
需要一個dbconfig的配置類:
import com.alibaba.druid.pool.DruidAbstractDataSource; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import com.alibaba.druid.wall.WallConfig; import com.alibaba.druid.wall.WallFilter; import com.xx.common.utils.DataSourceNames; import com.xx.common.utils.SpringContextHolder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration public class DruidDBConfig { public static Map<Object, Object> targetDataSources = new HashMap<>(); @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.default") public DataSource oneDataSource() { DataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.two") public DataSource twoDataSource() { DataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } @Bean @ConfigurationProperties(prefix = "spring.datasource.druid.other") public DataSource otherDataSource() { DataSource dataSource = DruidDataSourceBuilder.create().build(); return dataSource; } /** * 動態創建新的數據源 * * @param name * @param url * @param username * @param password * @param driver * @return */ @ConfigurationProperties(prefix = "spring.datasource") public DruidDataSource createDataSource(String name, String url, String username, String password, String driver) { DruidDataSource source = (DruidDataSource) DruidDataSourceBuilder.create().build(); source.setUrl(url); source.setUsername(username); source.setPassword(password); source.setDriverClassName(driver); targetDataSources.put(name, source); SpringContextHolder.getBean(DynamicDataSource.class).refreshDataSource(); return source; } @Bean @Primary public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource, DataSource otherDataSource) { targetDataSources.put(DataSourceNames.DEFAULT, oneDataSource); targetDataSources.put(DataSourceNames.TWO, twoDataSource); targetDataSources.put(DataSourceNames.OTHER, otherDataSource); // 還有數據源,在targetDataSources中繼續添加 System.out.println("DataSources:" + targetDataSources); return new DynamicDataSource(oneDataSource, targetDataSources); } public Map<Object, Object> getTargetDataSources() { return targetDataSources; } @Bean public ServletRegistrationBean druidServlet() { ServletRegistrationBean reg = new ServletRegistrationBean(); reg.setServlet(new StatViewServlet()); reg.addUrlMappings("/druid/*"); reg.addInitParameter("allow", ""); // 白名單 return reg; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); filterRegistrationBean.addInitParameter("profileEnable", "true"); filterRegistrationBean.addInitParameter("principalCookieName", "USER_COOKIE"); filterRegistrationBean.addInitParameter("principalSessionName", "USER_SESSION"); filterRegistrationBean.addInitParameter("DruidWebStatFilter", "/*"); return filterRegistrationBean; } @Bean public WallFilter wallFilter() { WallFilter wallFilter = new WallFilter(); wallFilter.setConfig(wallConfig()); return wallFilter; } @Bean public WallConfig wallConfig() { WallConfig config = new WallConfig(); config.setMultiStatementAllow(true);// 允許一次執行多條語句 config.setNoneBaseStatementAllow(true);// 允許非基本語句的其他語句 return config; } }
在此配置類中關鍵是 createDataSource() 方法;在此方法中調用了一個方法: SpringContextHolder.getBean(DynamicDataSource.class).refreshDataSource();
繼續上代碼:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import com.xx.common.utils.SpringContextHolder; import javax.sql.DataSource; import java.util.Map; public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); /** * 配置DataSource, defaultTargetDataSource為主數據庫 */ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } public void refreshDataSource() { DruidDBConfig dbConfig = SpringContextHolder.getBean(DruidDBConfig.class); super.setTargetDataSources(dbConfig.getTargetDataSources()); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
標紅的方法是讓druid重新加載數據源,在druid中重新加載數據源原有的數據連接是不會丟失的。
以上兩個方法是核心,其他網上給的都是配置兩個或多個數據源,沒有新建數據源,並讓druid重新加載數據源。
注解:DataSource
import java.lang.annotation.*; @Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default DataSourceNames.DEFAULT; }
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import com.xx.common.annotation.DataSource; import com.xx.common.config.ApplicationContextRegister; import com.xx.common.config.DruidDBConfig; import com.xx.common.config.DynamicDataSource; import com.xx.common.utils.DataSourceNames; import com.xx.common.utils.HttpContextUtils; import com.xx.common.utils.SpringContextHolder; import com.xx.common.utils.StringUtils; import com.xx.system.domain.SiteDO; import com.xx.system.service.SiteService; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.Map; import javax.servlet.http.HttpServletRequest; @Component @Aspect public class DataSourceAspect implements Ordered { @Autowired private SiteService siteService; protected Logger logger = LoggerFactory.getLogger(getClass()); @Override public int getOrder() { return 1; } @Pointcut("@annotation(com.xx.common.annotation.DataSource)") public void dataSourcePointCut() { } @Before("dataSourcePointCut()") public void doBefore(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); logger.info("方法名稱: " + method.getName()); DataSource dataSource = method.getAnnotation(DataSource.class); DruidDBConfig dynamicDataSourceConfig = SpringContextHolder.getBean(DruidDBConfig.class); Map<Object, Object> map = dynamicDataSourceConfig.getTargetDataSources(); // 通過判斷 DataSource 中的值來判斷當前方法應用哪個數據源 String value = dataSource.value(); boolean flag; String name = ""; switch (value) { case DataSourceNames.DEFAULT: flag = true; name = DataSourceNames.DEFAULT; break; case DataSourceNames.TWO: flag = true; name = DataSourceNames.TWO; break; case DataSourceNames.OTHER: flag = true; /** * 動態設定數據源,先着HttpServletRequest的參數中獲取數據源連接id,如果獲取不到從session中獲取, * 這個可以自定義獲取方法,redis,cacheT等都可以 * * 在根據id獲取數據源,如果存在返回直接使用,如果不存在,新建一個數據源,並讓重新加載數據源。 */ Long requestSid = HttpContextUtils.getRequestSid(); name = requestSid.toString(); if (map.get(name) != null) { break; } else { SiteDO site = siteService.get(requestSid); String url = "jdbc:sqlserver://" + site.getDbhost() + ":" + site.getDbport() + ";DatabaseName=" + site.getDbname(); logger.info("數據源切換:" + url); DruidDataSource s = dynamicDataSourceConfig.createDataSource(name, url, site.getDbuser(), site.getDbpwd(), "com.microsoft.sqlserver.jdbc.SQLServerDriver"); } break; default: flag = false; break; } if (!flag) { logger.error("************注意************"); name = DataSourceNames.DEFAULT; logger.info("加載到未知數據源,系統自動設置數據源為默認數據源!"); } DynamicDataSource.setDataSource(name); logger.info("當前數據源: " + name); logger.info("當前數據源: " + ((DruidDataSource) map.get(name)).getUrl()); logger.info("set datasource is " + name); } @After("dataSourcePointCut()") public void doAfter() { logger.info("*********准備清除數據源*********"); DynamicDataSource.clearDataSource(); logger.info("*********數據源清除成功*********"); } }
使用:默認數據源不用管,如果是配置數據源直接使用
import com.xx.common.annotation.DataSource; import com.xx.common.utils.DataSourceNames; import com.xx.demo.domain.User; import com.xx.demo.service.UserService1; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.List; @Controller @RequestMapping public class TestController { @Autowired private UserService1 userService1; /** * 默認的,不用注解也可以 * @return */ @GetMapping("/test/1") @ResponseBody //@DataSource(DataSourceNames.DEFAULT) List<User> test1(){ return userService1.test1(12); } /** * 配置的 * @return */ @GetMapping("/test/2") @ResponseBody @DataSource(DataSourceNames.TWO) List<User> test2(){ return userService1.test2(12); } /** * 數據庫的 * @return */ @GetMapping("/test/3") @ResponseBody @DataSource(DataSourceNames.OTHER) List<User> test3(){ return userService1.test3(12); } }
其他utils:
HttpContextUtils
import javax.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; public class HttpContextUtils { public static HttpServletRequest getHttpServletRequest() { return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); } public static Long getRequestSid() { Long sid = 1L; HttpServletRequest request = getHttpServletRequest(); String parameter = request.getParameter("sid"); if (StringUtils.isEmpty(parameter)) { sid = (Long) request.getSession().getAttribute("sid"); } else { sid = Long.valueOf(parameter); } return sid == null ? 1 : sid; } }
DataSourceNames 此類可以使用枚舉,常量類等。
public interface DataSourceNames { String DEFAULT = "DEFAULT"; String TWO = "TWO"; String OTHER = "OTHER"; }