Springboot项目实现Mysql多数据源切换的完整实例 |
您所在的位置:网站首页 › druiddatasource多数据源 › Springboot项目实现Mysql多数据源切换的完整实例 |
Springboot项目实现Mysql多数据源切换的完整实例 一、分析AbstractRoutingDataSource抽象类源码 关注import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource以下变量 @Nullable private Map @Nullable private Object defaultTargetDataSource; // 默认目标数据源 @Nullable private Map @Nullable private DataSource resolvedDefaultDataSource; // 解析的默认数据源 这两组变量是相互对应的,在熟悉多实例数据源切换代码的不难发现,当有多个数据源的时候,一定要指定一个作为默认的数据源; 在这里也同理,当同时初始化多个数据源的时候,需要显式的调用setDefaultTargetDataSource方法指定一个作为默认数据源; 我们需要关注的是Map targetDataSources是暴露给外部程序用来赋值的,而resolvedDataSources是程序内部执行时的依据,因此会有一个赋值的操作; 根据这段源码可以看出,每次执行时,都会遍历targetDataSources内的所有元素并赋值给resolvedDataSources;这样如果我们在外部程序新增一个新的数据源,都会添加到内部使用,从而实现数据源的动态加载。 继承该抽象类的时候,必须实现一个抽象方法:protected abstract Object determineCurrentLookupKey(),该方法用于指定到底需要使用哪一个数据源。 二、实现多数据源切换和动态数据源加载 A - 配置文件信息 application.yml文件 server: port: 18080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource druid: # 主数据源 master-db: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.223.129:13306/test_master_db?characterEncoding=utf-8 username: josen password: josen # 从数据源 slave-db: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://192.168.223.129:13306/test_slave_db?characterEncoding=utf-8 username: josen password: josen mybatis: mapper-locations: classpath:mapper/*.xml logging: path: ./logs/mydemo20201105.log level: com.josen.mydemo20201105: debug maven 依赖
org.springframework.boot spring-boot-starter-aop
com.alibaba druid-spring-boot-starter 1.1.10
org.springframework.boot spring-boot-starter-web
org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3
mysql mysql-connector-java runtime
org.projectlombok lombok true
B - 编码实现 1、创建一个DynamicDataSource类,继承AbstractRoutingDataSource抽象类,实现determineCurrentLookupKey方法,通过该方法指定当前使用哪个数据源; 2、 在DynamicDataSource类中通过ThreadLocal维护一个全局数据源名称,后续通过修改该名称实现动态切换; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.Map; /** * @ClassName DynamicDataSource * @Description 设置动态数据源 * @Author Josen * @Date 2020/11/5 14:28 **/ public class DynamicDataSource extends AbstractRoutingDataSource { // 通过ThreadLocal维护一个全局唯一的map来实现数据源的动态切换 private static final ThreadLocal contextHolder = new ThreadLocal(); public DynamicDataSource(DataSource defaultTargetDataSource, Map super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } /** * 指定使用哪一个数据源 */ @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } } 3、创建DynamicDataSourceConfig类,引入application.yaml配置的多个数据源信息,构建多个数据源; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @ClassName DynamicDataSourceConfig * @Description 引入动态数据源,构建数据源 * @Author Josen * @Date 2020/11/5 14:23 **/ @Configuration @Component public class DynamicDataSourceConfig { /** * 读取application配置,构建master-db数据源 */ @Bean @ConfigurationProperties("spring.datasource.druid.master-db") public DataSource myMasterDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 读取application配置,构建slave-db数据源 */ @Bean @ConfigurationProperties("spring.datasource.druid.slave-db") public DataSource mySlaveDataSource(){ return DruidDataSourceBuilder.create().build(); } /** * 读取application配置,创建动态数据源 */ @Bean @Primary public DynamicDataSource dataSource(DataSource myMasterDataSource, DataSource mySlaveDataSource) { Map targetDataSources.put("master-db",myMasterDataSource); targetDataSources.put("slave-db", mySlaveDataSource); // myMasterDataSource=默认数据源 // targetDataSources=目标数据源(多个) return new DynamicDataSource(myMasterDataSource, targetDataSources); } } 4、 创建MyDataSource自定义注解,后续AOP通过该注解作为切入点,通过获取使用该注解存入不同的值动态切换指定的数据源; import java.lang.annotation.*; /** * 自定义数据源注解 * 在需要切换数据源的Service层方法上添加此注解,指定数据源名称 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyDataSource { String name() default ""; } 5、 创建DataSourceAspectAOP切面类,以MyDataSource注解为切入点作拓展。在执行被@MyDataSource注解的方法时,获取该注解传入的name,并切换到指定数据源执行,执行完成后切换回默认数据源; import com.josen.mydemo20201105.annotation.MyDataSource; import com.josen.mydemo20201105.datasource.DynamicDataSource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.lang.reflect.Method; /** * @ClassName DataSourceAspect * @Description Aop切面类配置 * @Author Josen * @Date 2020/11/5 14:35 **/ @Aspect @Component @Slf4j public class DataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class); /** * 设置切入点 * 只有调用@MyDataSource注解的方法才会触发around */ @Pointcut("@annotation(com.josen.mydemo20201105.annotation.MyDataSource)") public void dataSourcePointCut() { } /** * 截取使用MyDataSource注解的方法,切换指定数据源 * 环绕切面:是(前置&后置&返回&异常)通知的结合体,更像是动态代理的整个过程 * @param point */ @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); logger.info("execute DataSourceAspect around=========>"+method.getName()); // 1. 获取自定义注解MyDataSource,查看是否配置指定数据源名称 MyDataSource dataSource = method.getAnnotation(MyDataSource.class); if(dataSource == null){ // 1.1 使用默认数据源 DynamicDataSource.setDataSource("master-db"); }else { // 1.2 使用指定名称数据源 DynamicDataSource.setDataSource(dataSource.name()); logger.info("使用指定名称数据源=========>"+dataSource.name()); } try { return point.proceed(); } finally { // 后置处理 - 恢复默认数据源 DynamicDataSource.clearDataSource(); } } } 6、 配置启动类 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.josen.mydemo.mapper") public class MydemoApplication { public static void main(String[] args) { SpringApplication.run(MydemoApplication.class, args); } } 7、 到这里基本上已经完成,剩下的就是测试是否正确切换数据源了; Mapper层 @Repository public interface PermissionMapper { List findAll(); }
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
select * from t_permission;
Service层 @Service public class PermissionService { @Autowired private PermissionMapper permissionMapper; // 切换从库数据源 @MyDataSource(name = "slave-db") public List findSlaveAll(){ return permissionMapper.findAll(); } // 默认数据源 public List findAll(){ return permissionMapper.findAll(); } } Controller层 @RestController @RequestMapping("/permission") public class PermissionController { @Autowired private PermissionService permissionService; // 测试获取默认master-db数据 @GetMapping("/master") public List handlerFindAll() { List list = permissionService.findAll(); return list; } // 测试获取指定slave-db数据 @GetMapping("/slave") public List handlerFindAll2() { List list = permissionService.findSlaveAll(); return list; } } C - 测试数据源切换 Mysql数据 接口返回数据 Demo源码地址:https://gitee.com/taco-gigigi/multiple-data-sources 总结 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |