本人目前做的项目用到了双数据源memsql和mysql,双数据源的配置并不是我配置的,故而自己私下配置了SpringBoot+druid的动态数据源。
SpringBoot用的是1.5.8版本,本人尝试过用过很多版本,但只有1.5.8版本连接池监控中才能监控到,其余版本不是报错就是连接池不生效,Druid用的是1.1.5版本
后来由于需要集成java8新特性中java.time这个包,故而把SpringBoot提升到2.0.0,(druid提升到1.1.10版本)而原来的配置需要修改。迫于无奈,只好修改成2.0.0支持的yml文件形式。
依赖用的是druid-spring-boot-starter
,并不是druid
,解释如下:
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.5version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.9version>
dependency>
<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.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.5.8.RELEASEversion>
<relativePath/>
parent>
<groupId>com.dbgroupId>
<artifactId>mysqldbartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mysqldbname>
<description>动态数据源配置description>
<properties>
<mysql.version>5.1.44mysql.version>
<mybatis.spring.version>1.3.3mybatis.spring.version>
<java.version>1.8java.version>
<druid.version>1.1.5druid.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.spring.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
<resources>
<resource>
<directory>${basedir}/src/main/javadirectory>
<includes>
<include>**/*.propertiesinclude>
<include>**/*.xmlinclude>
<include>**/*.ymlinclude>
includes>
resource>
<resource>
<directory>${basedir}/src/main/resourcesdirectory>
resource>
resources>
build>
project>
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
master: #数据源1
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/master?useSSL=false&characterEncoding=utf8
username: root
password: root
cluster: #数据源2
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/cluster?useSSL=false&characterEncoding=utf8
username: root
password: root
druid:
# 连接池的配置信息
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000 # 配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
# 配置DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: "/*"
exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
# 配置DruidStatViewServlet
stat-view-servlet:
url-pattern: "/druid/*"
# IP白名单(没有配置或者为空,则允许所有访问)
allow: 127.0.0.1,192.168.163.1
# IP黑名单 (存在共同时,deny优先于allow)
deny: 192.168.1.73
# 禁用HTML页面上的“Reset All”功能
reset-enable: false
# 登录名
login-username: root
# 登录密码
login-password: root
spring:
datasource:
druid:
master:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/master?useSSL=false&characterEncoding=utf8
username: root
password: root
# 连接池的配置信息
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000 # 配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
maxPoolPreparedStatementPerConnectionSize: 20
cluster:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/cluster?useSSL=false&characterEncoding=utf8
username: root
password: root
# 连接池的配置信息
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000 # 配置获取连接等待超时的时间
timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true # 打开PSCache,并且指定每个连接上PSCache的大小
maxPoolPreparedStatementPerConnectionSize: 20
server:
port: 9999
spring:
application:
name: multi-DB
profiles:
active: multiDB
mybatis:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: com.db.mysqldb.model
logging:
file: log/log.txt
level: #低版本的springboot日志配置有所不同
com.db.mysqldb.mapper: debug
root: info
package com.db.mysqldb.common;
/**
* @Auther: hs
* @Date: 2019/2/23 16:25
* @Description:
*/
public interface ContextConst {
enum DataSourceType{
MASTER,CLUSTER
}
}
package com.db.mysqldb.config.multiDB;
import lombok.extern.slf4j.Slf4j;
/**
* @Auther: hs
* @Date: 2019/2/23 16:41
* @Description:数据源持有类
*/
@Slf4j
public class DataSourceContextHolder {
/**
* CONTEXT_HOLDER代表一个可以存放String类型的ThreadLocal对象,
* 此时任何一个线程可以并发访问这个变量,
* 对它进行写入、读取操作,都是线程安全的。
* 比如一个线程通过CONTEXT_HOLDER.set(“aaaa”);将数据写入ThreadLocal中,
* 在任何一个地方,都可以通过CONTEXT_HOLDER.get();将值获取出来。
* 这里写入的就是数据库名,
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<String>();
public static void setDataSource(String dbType){
log.info("切换到["+dbType+"]数据源");
CONTEXT_HOLDER.set(dbType);
}
public static String getDataSource(){
return CONTEXT_HOLDER.get();
}
public static void clearDataSource(){
CONTEXT_HOLDER.remove();
}
}
package com.db.mysqldb.config.multiDB;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @Auther: hs
* @Date: 2019/2/23 16:38
* @Description:数据源路由实现类
* AbstractRoutingDataSource(每执行一次数据库,动态获取DataSource)
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
package com.db.mysqldb.config.multiDB;
import com.db.mysqldb.common.ContextConst;
import java.lang.annotation.*;
/**
* @Auther: hs
* @Date: 2019/2/23 16:30
* @Description:
* 此注解主要用在service实现类方法上
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDateSouce {
ContextConst.DataSourceType value() default ContextConst.DataSourceType.MASTER;
}
package com.db.mysqldb.config.multiDB;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.db.mysqldb.common.ContextConst;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
/**
* @Auther: hs
* @Date: 2019/2/23 16:09
* @Description:动态数据源配置类
*/
@Configuration
public class MultiDataSourceConfig {
@Bean
//2.0.0版本只需要修改pom文件中的依赖,yml的配置,和@ConfigurationProperties注解引入yml文件配置,其余不变
//@ConfigurationProperties(prefix = "spring.datasource.druid.master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DruidDataSource masterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
//@ConfigurationProperties(prefix = "spring.datasource.druid.cluster")
@ConfigurationProperties(prefix = "spring.datasource.cluster")
public DruidDataSource clusterDataSource(){
return DruidDataSourceBuilder.create().build();
}
@Primary
@Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//配置默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
//配置多数据源这里的key一定要是string类型,枚举类型并不支持,所以用到枚举中name()方法转成string,或者用toString方法。
HashMap<Object, Object> dataSourceMap = new HashMap();
dataSourceMap.put(ContextConst.DataSourceType.MASTER.name(),masterDataSource());
dataSourceMap.put(ContextConst.DataSourceType.CLUSTER.name(),clusterDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事务
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
package com.db.mysqldb.config.multiDB;
import com.db.mysqldb.common.ContextConst;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Auther: hs
* @Date: 2019/2/23 17:00
* @Description:动态数据源通知
*/
@Component
@Aspect
@Order(-1) //保证在@Transactional之前执行,必须加上,不然无法分辨是哪个数据源在执行事务
@Slf4j
public class DynamicDataSourceAspect {
@Before("execution(* com.db.*.service..*.*(..))")
public void before(JoinPoint point){
try {
TargetDateSouce annotationOfClass = point.getTarget().getClass().getAnnotation(TargetDateSouce.class);
String methodName = point.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
TargetDateSouce methodAnnotation = method.getAnnotation(TargetDateSouce.class);
methodAnnotation = methodAnnotation == null ? annotationOfClass:methodAnnotation;
ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() !=null ? methodAnnotation.value() :ContextConst.DataSourceType.MASTER ;
DataSourceContextHolder.setDataSource(dataSourceType.name());
} catch (NoSuchMethodException e) {
log.error("error",e);
}
}
@After("execution(* com.db.*.service..*.*(..))")
public void after(JoinPoint point){
DataSourceContextHolder.clearDataSource();
}
}
package com.db.mysqldb;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* springboot入口类,此类需要在所有用到的package上层
* exclude = {DataSourceAutoConfiguration.class}
* 禁用springboot默认加载的application.properties单数据源配置
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@MapperScan("com.db.mysqldb.mapper")
public class MysqldbApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(MysqldbApplication.class, args);
}
@Override
public void run(String... strings) throws Exception {
}
}
<mapper namespace="com.db.mysqldb.mapper.MultiMapper">
<resultMap id="findCityMap" type="City">
<id column="id" property="id"/>
<result column="province_id" property="provinceId"/>
<result column="city_name" property="cityName"/>
<result column="description" property="description"/>
resultMap>
<select id="findCityforList" resultMap="findCityMap">
select * from city
select>
<resultMap id="findUserMap" type="User">
<id column="id" property="id"/>
<result column="user_name" property="userName"/>
<result column="description" property="description"/>
resultMap>
<select id="findUserforList" resultMap="findUserMap">
select * from user
select>
mapper>
package com.db.mysqldb.mapper;
import com.db.mysqldb.model.City;
import com.db.mysqldb.model.User;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @Auther: hs
* @Date: 2019/2/23 17:57
* @Description:
*/
@Repository
public interface MultiMapper {
List<City> findCityforList();
List<User> findUserforList();
}
package com.db.mysqldb.service;
import com.db.mysqldb.model.City;
import com.db.mysqldb.model.User;
import java.util.List;
/**
* @Auther: hs
* @Date: 2019/2/23 18:15
* @Description:
*/
public interface MultiService {
List<City> findCityforList();
List<User> findUserforList();
}
package com.db.mysqldb.service.impl;
import com.db.mysqldb.common.ContextConst;
import com.db.mysqldb.config.multiDB.TargetDateSouce;
import com.db.mysqldb.mapper.MultiMapper;
import com.db.mysqldb.model.City;
import com.db.mysqldb.model.User;
import com.db.mysqldb.service.MultiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Auther: hs
* @Date: 2019/2/23 18:16
* @Description:
*/
@Service
public class MultiServiceImpl implements MultiService {
@Autowired
private MultiMapper multiMapper;
@Override
@TargetDateSouce(ContextConst.DataSourceType.CLUSTER)
public List<City> findCityforList() {
return multiMapper.findCityforList();
}
@Override
@TargetDateSouce(ContextConst.DataSourceType.MASTER)
public List<User> findUserforList() {
return multiMapper.findUserforList();
}
}
package com.db.mysqldb.controller;
import com.db.mysqldb.model.City;
import com.db.mysqldb.model.User;
import com.db.mysqldb.service.MultiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @Auther: hs
* @Date: 2019/2/23 18:22
* @Description:
*/
@RestController
public class MultiController {
private final MultiService MULTI_SERVICE;
@Autowired
public MultiController(MultiService multiService) {
this.MULTI_SERVICE = multiService;
}
@GetMapping("/MultiCity")
public List<City> findCityforList() {
return MULTI_SERVICE.findCityforList();
}
@GetMapping("/MultiUser")
public List<User> findUserforList() {
return MULTI_SERVICE.findUserforList();
}
}