文章目录
实现
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 切面编程 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
2、配置文件 + 数据源储存
配置文件需要配置多个数据源,用名称指定,这里使用primary
和 secondary
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowMultiQueries=true
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&allowMultiQueries=true
username: root
password: admin
driver-class-name: com.mysql.cj.jdbc.Driver
写一个 ThreadLocal 储存正在使用的数据源,也就意味着在方法使用前后需要重置清空,做到线程隔离的作用
package com.example.demo.test.config;
public class DataSourceType {
// 内部枚举类,用于选择特定的数据类型
public enum DataBaseType{
Primary, Secondary
}
// 使用 ThreadLocal 保证线程安全(隔离)
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType){
if(dataBaseType == null){
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 获取数据类型
public static DataBaseType getDataBaseType(){
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
return dataBaseType;
}
// 清空数据类型
public static void clearDataBaseType(){
TYPE.remove();
}
}
3、实现 DynamicDataSource
继承 AbstractRoutingDataSource ,重写方法,提供数据源
package com.example.demo.test.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
4、实现 AOP 和 动态数据源配置类
首先创建动态数据源配置类
package com.example.demo.test.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan(basePackages = "com.example.demo.**.mapper", sqlSessionFactoryRef = "sqlSessionFactory") // basePackage 接口地址
public class DynamicDataSourceConfig {
// 将这个对象放入Spring容器
@Bean(name = "PrimaryDataSource")
// 表示这个是默认数据源
@Primary
// 读取配置文件的参数,映射为一个对象
// prefix 表示参数的前缀
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource getDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "SecondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource getDataSource1(){
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
@Qualifier("SecondaryDataSource") DataSource secondaryDataSource){
// targetDataSource 集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource = new HashMap<>();
// 设置两个数据库
targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
// 动态切换源
dataSource.setTargetDataSources(targetDataSource);
// 默认是第一个数据源
dataSource.setDefaultTargetDataSource(primaryDataSource);
return dataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/**Mapper.xml"));
return bean.getObject();
}
}
配置好了之后,创建自定义注解,以便于随时注入,动态切换数据库。
package com.example.demo.test.config;
import java.lang.annotation.*;
// 可以在方法,参数,类或接口声明
@Target({
ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
// 注释
@Documented
public @interface DataSource {
// 默认使用的数据库
String value() default "primary";
}
对标记了注解进行 AOP 切面,获取value,将其储存到 ThreadLocal,限定本线程的数据源
package com.example.demo.test.config;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
// 拦截注释
@Before("@annotation(dataSource)")
public void changeDataSource(JoinPoint point, DataSource dataSource){
// 通过注解 获取值(也就是数据源)
String value = dataSource.value();
if(value.equals("primary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
else if(value.equals("secondary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}
// 如果没有值,默认使用主数据库
else{
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
}
@After("@annotation(dataSource)")
public void restoreDataSoucer(JoinPoint point, DataSource dataSource){
// 清空存在的数据源
DataSourceType.clearDataBaseType();
}
}
5、实体类 和 Mapper
创建一个实体类
package com.example.demo.test.pojo;
import lombok.Data;
@Data
public class Person {
private int id;
private String name;
}
创建相应的Mapper
package com.example.demo.test.mapper;
import com.example.demo.test.config.DataSource;
import com.example.demo.test.pojo.Person;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface PersonMapper {
// 使用数据源二
@DataSource("secondary")
List<Person> query();
}
创建PersonMapper.xml(/resouce/mapper/PersonMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis配置文件-->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.test.mapper.PersonMapper">
<select id="query" resultType="com.example.demo.test.pojo.Person">
select * from Person
</select>
</mapper>
6、单元测试
package com.example.demo;
import com.example.demo.test.mapper.PersonMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class TestPersonMapper {
@Autowired
PersonMapper personMapper;
@Test
public void test(){
personMapper.query().forEach(System.out::println);
}
}
结果比对:
// 项目输出
Person(id=1, name=小黄)
Person(id=2, name=小花)
// 数据库
id name
1 小黄
2 小花
问题
配置文件中找不到相对应的mapper
解决方案1:在pom.xml中添加
<builder>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<!-- 把 java目录下的 xml文件编译-->
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.*</include>
</includes>
</resource>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/*.*</include>
</includes>
</resource>
</builder>
解决方案2:配置文件中可能有写错(需要有s)
mapper-location: classpath:mapper/**/**Mapper.xml #错误的写法
mapper-locations: classpath:mapper/**/**Mapper.xml #正确的写法
解决方案3:动态配置的时候写错
// 错误写法(和方案 2 一样,也是因为少了 s )
....getResouce("classpath:mapper/**/**Mapper.xml")
// 正确写法
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/**/**Mapper.xml"));