- 实现
- 1、导入依赖
- 2、配置文件 + 数据源储存
- 3、实现 DynamicDataSource
- 4、实现 AOP 和 动态数据源配置类
- 5、实体类 和 Mapper
- 6、单元测试
- 问题
- 配置文件中找不到相对应的mapper
2、配置文件 + 数据源储存org.springframework.boot spring-boot-starter-jdbc mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-test test org.springframework spring-aop 5.1.5.RELEASE org.aspectj aspectjweaver 1.9.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 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
配置好了之后,创建自定义注解,以便于随时注入,动态切换数据库。
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 query();
}
创建PersonMapper.xml(/resouce/mapper/PersonMapper.xml)
6、单元测试select * from Person
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中添加
src/main/java ***.* src/main/webapp META-INF/resources ****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"));



