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。
测试