springboot + Mybatis 动态切换数据源

springboot+mybatis实现动态切换数据源

目前有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源。

目录结构

在这里插入图片描述

依赖引入

<?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>org.example</groupId>
  <artifactId>mysql_master-slave</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>mysql_master-slave Maven Webapp</name>


  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.4.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>


  <properties>
    <java.version>1.8</java.version>
    <lombok.version>1.18.6</lombok.version>
    <mybatis.version>1.3.2</mybatis.version>
    <lombox.version>1.18.6</lombox.version>
  </properties>


  <dependencies>
    <!-- 添加web启动坐标 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 添加lombok工具坐标 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${
    
    lombok.version}</version>
    </dependency>
    <!-- 添加springboot 测试坐标 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!-- 添加lombox 测试坐标 -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>${
    
    lombox.version}</version>
    </dependency>
    <!-- 添加mybatis依赖坐标 -->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>${
    
    mybatis.version}</version>
    </dependency>
    <!-- 添加mysql驱动器坐标 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <!-- 添加druid数据源坐标 -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-starter</artifactId>
      <version>1.1.10</version>
    </dependency>

  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <!-- 打包时添加这个标签includeSystemScope
          <fork>true</fork-->
          <includeSystemScope>true</includeSystemScope>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

环境配置

数据库

建立两个库,master_test库和slave_test库,分别创建user表,我这里为了方便设置一样的表名和结构。

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8

在这里插入图片描述
在这里插入图片描述

实体:

package com.wd.Entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    
    

    private int id;
    private String name;
}

UserMapper

package com.wd.mapper;

import com.wd.Entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

@Mapper
@Repository
public interface UserMapper {
    
    

    List<User> queryAllWithMaster();

    List<User> queryAllWithSlave();
}

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.wd.mapper.UserMapper">
    <select id="queryAllWithMaster" resultType="com.wd.Entity.User">
        SELECT
            *
        FROM
            user
    </select>

    <select id="queryAllWithSlave" resultType="com.wd.Entity.User">
        SELECT
        *
        FROM
        user
    </select>
</mapper>

application.yml

spring:
  datasource:
      driverClassName: com.mysql.cj.jdbc.Driver
      username: root
      password: 123456
      url: jdbc:mysql://localhost:3306/master_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8

      driverClassName_test : com.mysql.cj.jdbc.Driver
      username_test: root
      password_test: 123456
      url_test: jdbc:mysql://localhost:3306/slave_test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8

mybatis:
  type-aliases-package: com.wd.mapper
  mapper-locations: classpath:mapper/*.xml

切换数据源配置

DatebaseType.java

package com.wd.config.database;

/**
 * DatabaseType.java 是一个枚举类,做一个全局统一的数据源标识,用于区分系统使用的每一个数据源.
 */
public enum DatabaseType {
    
    
    DATASOURCE_MASTER("master_test", "1"),
    DATASOURCE_SlAVE("slave_test", "2");

    DatabaseType(String name, String value){
    
    
        this.name = name;
        this.value = value;
    }

    private String name;
    private String value;

    public String getName(){
    
    
        return name;
    }

    public String getValue(){
    
    
        return value;
    }
}

DatabaseContextHolder.java

package com.wd.config.database;

/**
 * DatabaseContextHolder.java 类的目的是创建一个线程安全的存储DatabaseType的容器,用于在使用数据源的时候,将DatabaseType作为数据源的键.
 */
public class DatabaseContextHolder {
    
    
    private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();

    public static void setDatabaseType(DatabaseType type){
    
    
        contextHolder.set(type);
    }

    public static DatabaseType getDatabaseType(){
    
    
        return contextHolder.get();
    }
}

DynamicDataSource.java

DynamicDataSource.java,继承AbstractRoutingDataSource抽象类.重写determineCurrentLookupKey()方法.该方法的作用是返回一个lookUpKey,这个lookUpKey是数据源的一个标识.可以通过looUpKey在resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource.此处重写determineCurrentLookupKey()方法的目的,是将我们定义的DatabaseType作为lookUpKey使用.

package com.wd.config.database;

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

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

MybatisConfig

package com.wd.config.mybatis;

import com.alibaba.druid.pool.DruidDataSource;
import com.wd.config.database.DatabaseContextHolder;
import com.wd.config.database.DatabaseType;
import com.wd.config.database.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
@MapperScan(basePackages="com.wd.mapper", sqlSessionFactoryRef="sessionFactory")
public class MybatisConfig {
    
    
    @Autowired
    Environment environment;

    //读取配置文件中配置的数据源信息,将其映射到本实体类的对应属性.

    // Master
    @Value("${spring.datasource.driverClassName}")
    private String dbDriver;

    @Value("${spring.datasource.url}")
    private String dbUrl;

    @Value("${spring.datasource.username}")
    private String dbUsername;

    @Value("${spring.datasource.password}")
    private String dbPassword;

    // Slave
    @Value("${spring.datasource.driverClassName_test}")
    private String dbDriver_test;

    @Value("${spring.datasource.url_test}")
    private String dbUrl_test;

    @Value("${spring.datasource.username_test}")
    private String dbUsername_test;

    @Value("${spring.datasource.password_test}")
    private String dbPassword_test;


    @Bean(name="dataSourceMaster")
    public DataSource dataSourceMaster() throws Exception{
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(dbDriver);
        dataSource.setUrl(dbUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);
        return dataSource;
    }


    @Bean(name="dataSourceSlave")
    public DataSource dataSourceSlave() throws Exception{
    
    
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(dbDriver_test);
        dataSource.setUrl(dbUrl_test);
        dataSource.setUsername(dbUsername_test);
        dataSource.setPassword(dbPassword_test);
        return dataSource;
    }

    /**
     * 创建动态数据源
     * @throws Exception
     * @Primary 该注解表示在同一个接口有多个类可以注入的时候,默认选择哪个,而不是让@Autowired报错
     */
    @Bean(name="dynamicDataSource")
    @Primary
    public DynamicDataSource DataSource(@Qualifier("dataSourceMaster") DataSource dataSourceMaster,
                                        @Qualifier("dataSourceSlave") DataSource dataSourceSlave){
    
    
        //将已知的所有数据源信息放进 AbstractRoutingDataSource 类的 targetDataSource 属性中.
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put(DatabaseType.DATASOURCE_MASTER, dataSourceMaster);
        targetDataSource.put(DatabaseType.DATASOURCE_SlAVE, dataSourceSlave);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSource);
        //设置默认数据源为 Master库 数据源
        dataSource.setDefaultTargetDataSource(dataSourceMaster);
        return dataSource;
    }

    /**
     * 根据当前数据源创建SqlSessionFactory
     * @throws Exception
     */
    @Bean(name="sessionFactory")
    public SqlSessionFactory sessionFactory(@Qualifier("dynamicDataSource")DynamicDataSource dataSource) throws Exception{
    
    
        SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactoryBean.setMapperLocations(resolver.getResources(environment.getProperty("mybatis.mapper-locations")));    //*Mapper.xml位置
        return sessionFactoryBean.getObject();
    }

    /**
     * 手动修改数据源
     * @param environment
     */
    public static void setDataSourceByEnvironment(DatabaseType environment){
    
    
        DatabaseContextHolder.setDatabaseType(environment);
    }

}

Controller

package com.wd.controller;

import com.wd.Entity.User;
import com.wd.config.database.DatabaseType;
import com.wd.config.mybatis.MybatisConfig;
import com.wd.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {
    
    
    @Autowired
    private UserMapper userMapper;

    @GetMapping("/{name}/list")
    public List<User> list(@PathVariable("name") String name) {
    
    
        System.out.println(name);
        if (name.equals("master")) {
    
    
            MybatisConfig.setDataSourceByEnvironment(DatabaseType.DATASOURCE_MASTER);
            return userMapper.queryAllWithMaster();
        } else {
    
    
            MybatisConfig.setDataSourceByEnvironment(DatabaseType.DATASOURCE_SlAVE);
            return userMapper.queryAllWithSlave();
        }
    }

}

启动类

package com.wd;

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

@SpringBootApplication(exclude= {
    
    DataSourceAutoConfiguration.class})
@MapperScan(basePackages = "com.wd.mapper")
public class ApplicationStartApp {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(ApplicationStartApp.class,args);
    }

}

注意:一定要在springboot启动类上的@SpringBootApplication注解中加入exclude属性,用于屏蔽springboot对数据源的自动装配类DataSourceAutoConfiguration。

测试

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44374871/article/details/112651055
今日推荐