程序员社区

SpringBoot笔记(三):基于注解,实现多数据源切换

       本文讲解:SpringBoot + Druid 连接池 + Transcation(事务),基于注解方式,来完成多数据源的切换。该Demo只需要修改一下.yml文件中的MySQL部分,即可运行。


结构图如下:

SpringBoot笔记(三):基于注解,实现多数据源切换插图


demo-config模块

1.自定义注解

package com.example.demo02.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Ds {
   String value();
   //String value() default "ds1";  //此处可默认某个数据源
}

2.切面编写

package com.example.demo02.aspect;

import com.example.demo02.annotation.Ds;
import com.example.demo02.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataSourceAspect {

    @Pointcut("@annotation(com.example.demo02.annotation.Ds)")
    public void pointcutConfig(){

    }

    @Before("pointcutConfig()")
    public void before(JoinPoint joinPoint){
        //获得当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        //获得访问的方法名
        String methodName = joinPoint.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)joinPoint.getSignature()).getParameterTypes();

        String dataSource = null;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(Ds.class)) {
                Ds annotation = method.getAnnotation(Ds.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 切换数据源
        DataSourceContextHolder.setDataSource(dataSource);
    }

    @After("pointcutConfig()")
    public void after(JoinPoint joinPoint){
        DataSourceContextHolder.clearDataSource();
    }
}

3.config详细配置

  1.Druid连接池配置 DruidConfig

package com.example.demo02.config;

import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
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;

/**
 * Druid连接池配置文件
 */
@Configuration
public class DruidConfig {

    //配置 Druid 的监控
    /**
     * 1.配置一个管理后台的Servlet
     */
    @Bean
    public ServletRegistrationBean startViewServlet(){
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        // IP白名单
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
        // IP黑名单(共同存在时,deny优先于allow)
        servletRegistrationBean.addInitParameter("deny","127.0.0.1");
        //控制台管理用户
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","123456");
        //是否能够重置数据
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    /**
     * 配置一个 Web 监控的 Filter
     * @return
     */
    @Bean
    public FilterRegistrationBean statFilter(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //添加过滤规则
        filterRegistrationBean.addUrlPatterns("/*");
        //忽略过滤的格式
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

  2.DynamicDataSource类

package com.example.demo02.config;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

  3.DataSourceContextHolder类

package com.example.demo02.config;

import org.apache.log4j.Logger;

public class DataSourceContextHolder {

    private static Logger LOG = Logger.getLogger(DataSourceContextHolder.class);

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    //设置数据源名称
    public static void setDataSource(String dataSource){
        LOG.info("切换到{"+dataSource+"}数据源");
        contextHolder.set(dataSource);
    }

    public static String getDataSource(){
        return contextHolder.get();
    }

    //清除数据源
    public static void clearDataSource(){
        contextHolder.remove();
    }
}

  4.多数据源配置 DataSourceConfig类

package com.example.demo02.config;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
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;
import java.util.Map;

/**
 * 多数据源配置
 */
@Configuration
public class DataSourceConfig {

    /**
     *  数据源1
     */
    @Bean(name = "ds1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.ds1")
    public DataSource Ds1(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源2
     */
    @Bean(name = "ds2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.ds2")
    public DataSource Ds2(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源切换: 通过AOP在不同数据源之间动态切换
     */
    @Primary
    @Bean
    public DataSource dynamicDataSource(){

        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        //设置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(Ds1());
        //配置多数据源
        Map<Object,Object> dsMap = new HashMap<>();
        dsMap.put("ds1",Ds1());
        dsMap.put("ds2",Ds2());

        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    /**
     * 配置@Transactional注解事务
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

4.demo-config模块 pom依赖

<dependencies>
    <!-- springboot-aop -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <!-- springboot-jdbc -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!--springboot整合log4j-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-log4j</artifactId>
        <version>1.3.8.RELEASE</version>
    </dependency>

    <!-- druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.20</version>
    </dependency>

</dependencies>

demo-web模块

1.Controller层

package com.example.demo02.controller;

import com.example.demo02.entity.User;
import com.example.demo02.service.IUserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import java.util.List;

@Controller
@RequestMapping("user")
public class UserController {

    @Resource(name = "userService")
    public IUserService userService;

    @RequestMapping("findDs1AllUser")
    @ResponseBody
    public List<User> findDs1AllUser(){
        List<User> user = this.userService.findDs1AllUser();
        return user;
    }

    @RequestMapping("findDs2AllUser")
    @ResponseBody
    public List<User> findDs2AllUser(){
        List<User> user = this.userService.findDs2AllUser();
        return user;
    }
}

  2.Service层

package com.example.demo02.service.impl;

import com.example.demo02.annotation.Ds;
import com.example.demo02.dao.UserDao;
import com.example.demo02.entity.User;
import com.example.demo02.service.IUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;

@Service("userService")
public class UserServiceImpl implements IUserService {

    @Resource(name = "userDao")
    public UserDao userDao;

    @Override
    @Ds("ds1")
    @Transactional
    public List<User> findDs1AllUser() {

        User user1 = new User();
        user1.setId("123");
        user1.setName("B");
        user1.setAge("56");
        user1.setAddress("北京市");
        userDao.insertUserToDs1(user1);

        List<User> users = userDao.findDs1AllUser();
        return users;
    }

    @Override
    @Ds("ds2")
    public List<User> findDs2AllUser() {
        List<User> user = userDao.findDs2AllUser();
        return user;
    }

}

  3.dao层

package com.example.demo02.dao;

import com.example.demo02.entity.User;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository("userDao")
public interface UserDao{

    List<User> findDs1AllUser();

    List<User> findDs2AllUser();

    void insertUserToDs1(User user);
}

  4.entity实体类

package com.example.demo02.entity;

public class User {

    private String id;

    private String name;

    private String age;

    private String address;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

  5.SpringBoot入口启动类

package com.example.demo02;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * springboot入口类,此类需要在所有用到的package上层
 * exclude = {DataSourceAutoConfiguration.class}
 * 禁用springboot默认加载的application.properties单数据源配置
 */
@MapperScan("com.example.demo02.dao")
@EnableTransactionManagement //开启事务
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Demo02Application {

    public static void main(String[] args) {
        SpringApplication.run(Demo02Application.class, args);
    }

}

  6.MyBatis映射SQL文件 

<?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.example.demo02.dao.UserDao">

    <insert id="insertUserToDs1" parameterType="com.example.demo02.entity.User">
        insert into test_user(id,name,age,address)
        value (#{id},#{name},#{age},#{address})
    </insert>

    <!-- 查询DS1所有user -->
    <select id="findDs1AllUser" resultType="com.example.demo02.entity.User">
        select
            id,
            name,
            age,
            address
        from
            m_user
    </select>

    <!-- 查询DS2所有user -->
    <select id="findDs2AllUser" resultType="com.example.demo02.entity.User">
        select
            id,
            name,
            age,
            address
        from
            d_user
    </select>

</mapper>

  7.application.yml 配置文件

server:
  port: 8088

spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      ds1:
        url: jdbc:mysql://192.168.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver

        filters: stat,wall #配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙
        initial-size: 1 #初始化大小
        min-idle: 1 #最小连接数
        max-active: 20 #最大连接数
        max-wait: 60000 #获取连接等待超时时间
        time-between-eviction-runs-millis: 60000 #间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
        min-evictable-idle-time-millis: 30000 #一个连接在池中最小生存的时间,单位是毫秒
        validation-query: SELECT 'x' #测试语句是否执行正确
        test-while-idle: true #指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
        test-on-borrow: false #借出连接时不要测试,否则很影响性能
        test-on-return: false
        pool-prepared-statements: false #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
        max-pool-prepared-statement-per-connection-size: 20 #与Oracle数据库PSCache有关,在druid下可以设置的比较高
      ds2:
         url: jdbc:mysql://192.168.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8
        username: root
        password: 123456
        driver-class-name: com.mysql.jdbc.Driver

        filters: stat,wall #配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙
        initial-size: 1 #初始化大小
        min-idle: 1 #最小连接数
        max-active: 20 #最大连接数
        max-wait: 60000 #获取连接等待超时时间
        time-between-eviction-runs-millis: 60000 #间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
        min-evictable-idle-time-millis: 30000 #一个连接在池中最小生存的时间,单位是毫秒
        validation-query: SELECT 'x' #测试语句是否执行正确
        test-while-idle: true #指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
        test-on-borrow: false #借出连接时不要测试,否则很影响性能
        test-on-return: false
        pool-prepared-statements: false #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
        max-pool-prepared-statement-per-connection-size: 20 #与Oracle数据库PSCache有关,在druid下可以设置的比较高

mybatis:
  mapper-locations: classpath:sqlmaps/*.xml
  type-aliases-package: com.example.demo02.entity

logging:
  level:
    com.example.demo02.dao: debug

  8.log4j.properties 配置文件

### #配置根Logger ###
log4j.rootLogger=info,stdout

### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p - %m%n

log4j.logger.com.report.dao=debug

  9.demo-web模块 pom依赖

<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>demo-config</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </dependency>
    <!-- mysql驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.34</version>
    </dependency>

    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>

</dependencies>

10.最外层 pom 依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

测试

       分别使用链接 http://localhost:8088/user/findDs2AllUser   和  http://localhost:8088/user/findDs1AllUser 访问,通过日志可以看到数据源已经成功切换

SpringBoot笔记(三):基于注解,实现多数据源切换插图1


SpringBoot 基于注解,实现多数据源切换,介绍到此为止

如果本文对你有所帮助,那就给我点个赞呗 ^_^ 

End

赞(0) 打赏
未经允许不得转载:IDEA激活码 » SpringBoot笔记(三):基于注解,实现多数据源切换

一个分享Java & Python知识的社区