SpringBoot学习:druid动态数据源配置

本人目前做的项目用到了双数据源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,解释如下:

<!--starter的依赖,无需我们在编写配置类的配置,只需在yml中编写需要的配置即可-->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.5</version>
</dependency>
<!--drui源生的依赖,则需要编写配置类,降低了开发的效率。-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>
  • 案例结构:
    在这里插入图片描述

  • pom文件:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.8.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>com.db</groupId>
    <artifactId>mysqldb</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mysqldb</name>
    <description>动态数据源配置</description>

    <properties>
        <mysql.version>5.1.44</mysql.version>
        <mybatis.spring.version>1.3.3</mybatis.spring.version>
        <java.version>1.8</java.version>
        <druid.version>1.1.5</druid.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>${mybatis.spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Druid 数据连接池依赖 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- Junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <!--把文件打包,放在resource目录下。-->
        <resources>
            <resource>
                <directory>${basedir}/src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.yml</include>
                </includes>
            </resource>
            <resource>
                <directory>${basedir}/src/main/resources</directory>
            </resource>
        </resources>
    </build>
</project>
  • application-multiDB.yml:配置1.5.8版本
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
  • application-multiDB.yml:配置2.0.0版本
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

  • application.yml:配置:
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
  • 编写ContextConst类记录数据库名
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());
    }
}

  • 配置AOP动态数据源通知
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();
    }
}

  • springboot入口类
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文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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>
  • 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();
}

  • service接口:
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();
}

  • service实现类
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();
    }
}

  • controller层:
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();
    }
}

  • druid监控界面(如下截图,连接池配置起作用):
    在这里插入图片描述
  • 如下图分别访问两个数据库成功,数据源切换正常:
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190223215648193.png
    在这里插入图片描述
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_26869339/article/details/87897910