Springboot使用TheardLocal和aop方式以及使用AbstractRoutingDataSource动态数据源控制完整版demo(包含数据库创建及pom文件)

   这段时间想起以前的做项目的时候,因为基于分布式架构,不同的租户下的用户,在发送请求的时候,都需要切换到对应租户平台下的数据库,由此产生动态切换数据源的概念,结合threadlocal实现此功能,threadlocal主要防止多线程,保证每个线程做操作时候独立处理事务,防止被其它线程串改我现在把该项目的实现这一功能的流程做个简单demo分离出来,给大家做个借鉴

我分别使用

http://localhost:9000/test/test?dbName=order,

http://localhost:9000/test/test?dbName=order1,

http://localhost:9000/test/test?dbName=order2,

http://localhost:9000/test/test?dbName=order3

利用apipostget提交代码,dbName是用来做切换数据源的关键点,实际项目根据大家自己的获取数据源标识做应用,效果图如下

2020-05-13 22:30:37.854  INFO 17412 --- [nio-9000-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-05-13 22:30:37.854  INFO 17412 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-05-13 22:30:37.863  INFO 17412 --- [nio-9000-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 9 ms
当前线程为:http-nio-9000-exec-1当前线程value/数据源名称为:null
当前线程:Thread[http-nio-9000-exec-1,5,main]使用名称为order数据源
使用数据库名order做操作
当前线程为:http-nio-9000-exec-2当前线程value/数据源名称为:null
当前线程:Thread[http-nio-9000-exec-2,5,main]使用名称为order1数据源
使用数据库名order1做操作
2020-05-13 22:30:41.221  INFO 17412 --- [nio-9000-exec-2] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2,order1} inited
当前线程为:http-nio-9000-exec-3当前线程value/数据源名称为:null
当前线程:Thread[http-nio-9000-exec-3,5,main]使用名称为order2数据源
使用数据库名order2做操作
2020-05-13 22:30:44.036  INFO 17412 --- [nio-9000-exec-3] com.alibaba.druid.pool.DruidDataSource   : {dataSource-3,order2} inited
当前线程为:http-nio-9000-exec-5当前线程value/数据源名称为:null
当前线程:Thread[http-nio-9000-exec-5,5,main]使用名称为order3数据源
使用数据库名order3做操作
2020-05-13 22:30:47.337  INFO 17412 --- [nio-9000-exec-5] com.alibaba.druid.pool.DruidDataSource   : {dataSource-4,order3} inited

1.yml文件配置

#######配置参数###########
server:
  port: 9000
defaultdatasource:
  # 使用druid数据源
  type: com.alibaba.druid.pool.DruidDataSource
  druid:
    master:
      #driver-class-name: com.mysql.cj.jdbc.Driver
      name: order
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/order?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: vincent
    first:
      #driver-class-name: com.mysql.cj.jdbc.Driver
      name : order1
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/order1?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: vincent
    second:
      #driver-class-name: com.mysql.cj.jdbc.Driver
      name : order2
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/order2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: vincent
    third:
      name : order3
      #driver-class-name: com.mysql.cj.jdbc.Driver
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/order3?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: vincent
spring:
  application:
    name: testx
  datasource:
    initialization-mode: always
    continue-on-error: true
#mybatis
mybatis-plus:
  #  configuration:mybatis.mapper-locations=classpath:mapper/*.xml
  #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:/mybatis/mappers/obj/*Mapper.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.vincent.testx.entity
  global-config:
    # 数据库相关配置
    db-config:
      #主键类型  AUTO:"数据库ID自增", INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
      id-type: UUID
      #字段策略 IGNORED:"忽略判断",NOT_NULL:"非 NULL 判断"),NOT_EMPTY:"非空判断"
      field-strategy: not_empty
      #驼峰下划线转换
      column-underline: true
      #数据库大写下划线转换
      #capital-mode: true
      # 逻辑删除配置
      logic-delete-value: 0
      logic-not-delete-value: 1
      db-type: mysql
    #刷新mapper 调试神器
    refresh: true



2.配置文件设置数据源对象类
/**
 * 数据源属性对象配置类
 */
@Component
@Data
@ConfigurationProperties(prefix = "defaultdatasource.druid")
public class DBProperties {
    private DruidDataSource master;
    private DruidDataSource first;
    private DruidDataSource second;
    private DruidDataSource third;



//    private DruidDataSource masterSecond;
  //  private DruidDataSource numOne;
}

3.动态数据源初始化操作类

/**
 * 数据源配置初始化操作类
 */
@Configuration
public class DynamicDataSourceConfig {

    @Autowired
    private DBProperties properties;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        //按照目标数据源名称和目标数据源对象的映射存放在Map中
        Map<Object, Object> targetDataSources = new HashMap<>();
        try {
            Object o=properties.getMaster().getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        targetDataSources.put(properties.getMaster().getName(), properties.getMaster());
        targetDataSources.put(properties.getFirst().getName(),properties.getFirst());
        targetDataSources.put(properties.getSecond().getName(),properties.getSecond());
        targetDataSources.put(properties.getThird().getName(),properties.getThird());
        //采用是AbstractRoutingDataSource的对象包装多数据源
        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        //设置当前使用的数据源为master
        DynamicDataSourceContextHolder.setDataSource(properties.getMaster().getName());
        //设置默认的数据源,当拿不到数据源时,使用此配置
        dataSource.setDefaultTargetDataSource(properties.getMaster());
        System.out.println("初始化系统时首次使用数据源为:"+ DynamicDataSourceContextHolder.getDataSource());
        return dataSource;
    }



}

4.动态数据源路由类

/**
 * 动态路由数据源实现类
 */
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
//    private Map<Object, Object> allResolvedDataSources = new HashMap<>();
    //数据源动态获取器
    @Override
    protected Object determineCurrentLookupKey() {

        return DynamicDataSourceContextHolder.getDataSource();
    }

}

5.ThreadLocal操作类

/**
 * 动态数据源持有者,负责利用ThreadLocal存取数据源名称
 */
public  class DynamicDataSourceContextHolder {

    /**
     * 本地线程共享对象
     */
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

    /**
     *数据源存放列表
     */
    public static List<String> dataSourceDbNos = new ArrayList<>();

    /**
     * 选择数据源
     * @param dnName 数据源名称
     */
    public static void setDataSource(String dnName) {
        THREAD_LOCAL.set(dnName);
    }
    /**
     *获取当前数据源
     * @return
     */
    public static String getDataSource() {
        return THREAD_LOCAL.get();
    }
    /**
     * 删除数据源
     */
    public static void removeDataSource() {
        THREAD_LOCAL.remove();
    }


}

6.数据源控制类

/**
 * 数据源控制类
 */
public class DynamicDataSourceSetUtil  {


    /**
     * 存放数据源
     * @param dbName
     */
    public  static  void PutDataSourceIn(String dbName){
        //手动生成新的数据源

        DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
        if(dbName!=null&&!dbName.equals("")){
            System.out.println("当前线程为:"+Thread.currentThread().getName()+
                    "当前线程value/数据源名称为:"+DynamicDataSourceContextHolder.getDataSource());
                //如果当前线程本地缓存为空,则设置其数据源编号
                if(DynamicDataSourceContextHolder.getDataSource()==null){
                    DynamicDataSourceContextHolder.setDataSource(dbName);
                    System.out.println("当前线程:"+Thread.currentThread()+"使用名称为"+DynamicDataSourceContextHolder.getDataSource()+"数据源");
                }
                else{
                    DynamicDataSourceContextHolder.setDataSource(dbName);
                    //选择当前数据库
                    System.out.println("已存在数据源连接,直接切换,切换的数据源为:"+DynamicDataSourceContextHolder.getDataSource());
                }

        }

    }


}

7.AOP手动切入类

/**
 * 手动aop方式切入方式
 */
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {

    //统一切入所有包下,所有后缀名为XXController的类的所有方法
    @Pointcut("execution(* com.vincent.testx..*Controller.*(..))")
    public void Pointcut() {
    }

    //在执行接口前
    @Before("Pointcut()")
    public void process(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        //方法名
        String methodName=signature.getName();
        // 类名
        String serviceName = signature.getDeclaringTypeName();
        // 参数名数组
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String dbName=request.getParameter("dbName");
        //获取参数
        DynamicDataSourceSetUtil.PutDataSourceIn(dbName);

    }

    //执行完切面后,将线程共享中的数据源名称清空
    @After("Pointcut()")
    public void after(JoinPoint joinPoint){

        DynamicDataSourceContextHolder.removeDataSource();
    }
}

8.spring上下文工具类

@Component
public class SpringContextUtil  implements ApplicationContextAware {
    // Spring应用上下文环境
    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringContextUtil.applicationContext=applicationContext;
    }
    /**
     * @return 获取上下文
     */
    public static ApplicationContext getApplicationContext() {
        checkApplicationContext();
        return applicationContext;
    }

    /**
     * 获取bean对象
     *
     * @param name
     * @return Object
     * @throws BeansException
     */
    public static Object getBean(String name) throws BeansException {
        return getApplicationContext().getBean(name);
    }

    private static void checkApplicationContext() {
        if (applicationContext == null)
            throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextUtil");
    }

}

9.user实体类


@Data
public class User {
private int id;
private String name;
private int age;
}

10.userMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {

}

11.userMapper.xml

<?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.vincent.testx.mapper.obj.UserMapper">

</mapper>

12.测试用controller

@Controller
@RequestMapping("/test")
public class testController {
    @Autowired
    private UserMapper userMapper;

    @RequestMapping(value = "/test",method = RequestMethod.GET)
    public  void test(@RequestParam("dbName")String dbName){
        System.out.println("使用数据库名"+dbName+"做操作");
        User user=new User();
        user.setName("vincent_"+ new Random().nextInt(1000));
        user.setAge(new Random().nextInt(30));
        userMapper.insert(user);
    }
}

13.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.vincent</groupId>
	<artifactId>testx</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>testx</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>1.8</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<druid.version>1.1.10</druid.version>
		<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
<!--		<spring-cloud-alibaba-version>2.1.1.RELEASE</spring-cloud-alibaba-version>-->
<!--		<fastjson-version>1.2.68</fastjson-version>-->
	</properties>
	<parent>
		<groupId>com.vincent</groupId>
		<artifactId>spring_cloud_alibaba_dependence</artifactId>
		<version>1.0-SNAPSHOT</version>
<!--		<relativePath>../spring_cloud_alibaba_dependence/pom.xml</relativePath>-->
	</parent>
	<!--<groupId>com.vincent</groupId>-->

	<dependencies>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<!-- loombook-->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<!-- 支持 @ConfigurationProperties 注解 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-configuration-processor</artifactId>
			<optional>true</optional>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<!--		<dependency>-->
<!--			<groupId>org.mybatis</groupId>-->
<!--			<artifactId>mybatis</artifactId>-->
<!--			<version>3.5.2</version>-->
<!--		</dependency>-->
		<!-- 集成mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<!-- spring boot整合druid数据源 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.9</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.0.1</version>
		</dependency>

		<!-- Spring Boot Begin -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>

	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

14.数据库创建测试脚本,我这里用的HeidiSql工具创建的脚本

-- --------------------------------------------------------
-- 主机:                           127.0.0.1
-- 服务器版本:                        5.7.30-log - MySQL Community Server (GPL)
-- 服务器OS:                        Win64
-- HeidiSQL 版本:                  10.2.0.5599
-- --------------------------------------------------------

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;


-- Dumping database structure for order
CREATE DATABASE IF NOT EXISTS `order` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `order`;

-- Dumping structure for table order.user
CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '',
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

-- Data exporting was unselected.


-- Dumping database structure for order1
CREATE DATABASE IF NOT EXISTS `order1` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `order1`;

-- Dumping structure for table order1.user
CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '',
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- Data exporting was unselected.


-- Dumping database structure for order2
CREATE DATABASE IF NOT EXISTS `order2` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `order2`;

-- Dumping structure for table order2.user
CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '',
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

-- Data exporting was unselected.


-- Dumping database structure for order3
CREATE DATABASE IF NOT EXISTS `order3` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `order3`;

-- Dumping structure for table order3.user
CREATE TABLE IF NOT EXISTS `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL DEFAULT '',
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- Data exporting was unselected.

/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值