手把手教你springboot优雅的实现多数据源,看这一篇就够了

您所在的位置:网站首页 springboot配置多数据源切换 手把手教你springboot优雅的实现多数据源,看这一篇就够了

手把手教你springboot优雅的实现多数据源,看这一篇就够了

2023-08-14 06:25| 来源: 网络整理| 查看: 265

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

具体实现 数据准备

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NldguE1a-1618405772384)(C:\Users\86159\AppData\Roaming\Typora\typora-user-images\image-20210413154614456.png)] test1数据库准备用户表users,test2数据库准备菜单表menu 在这里插入图片描述 用户表测试数据【张三】,菜单表测试数据【用户列表】

所需依赖 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-web mysql mysql-connector-java com.alibaba druid-spring-boot-starter 1.1.10 org.springframework.boot spring-boot-starter-jdbc com.baomidou mybatis-plus-boot-starter 3.3.1.tmp 配置数据源 #数据源1 spring.datasource.druid.first.driverClassName=com.mysql.jdbc.Driver spring.datasource.druid.first.url=jdbc:mysql://localhost:3306/test1?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.first.username=root spring.datasource.druid.first.password=123456 #数据源2 spring.datasource.druid.second.driverClassName=com.mysql.jdbc.Driver spring.datasource.druid.second.url=jdbc:mysql://localhost:3306/test2?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.second.username=root spring.datasource.druid.second.password=123456

这里添加两个数据源,如果你使用的是oracle,用oracle的配置即可。

ThreadLocal存放数据源

采用ThreadLocal存放数据源,因为ThreadLocal是线程安全类。它类似于map,存储key-value形式的数据,但只能存储一个,当存储两个时,第二个会把第一个覆盖掉。ThreadLocal是线程本地存储,在每个线程中都创建了一个ThreadLocal对象,每个线程可以访问自己内部ThreadLocalMap对象内的value。

/** * @Author: 孙常胜 * @CreateTime: 2021/4/13 09:27 * @Description:线程安全类,存放数据源 */ public class DatabaseContextHolder { private static final ThreadLocal contextHolder = new ThreadLocal(); public static final String FIRST_DS = "first_datasource"; public static final String SECOND_DS = "second_datasource"; /** * 放入 * @param type */ public static void setDataBase(String type){ contextHolder.set(type); } /** * 获取 */ public static String getDataBase(){ return contextHolder.get(); } /** * 清空 */ public static void chearDataSource(){ contextHolder.remove(); } } 配置多数据源 /** * @Author: 孙常胜 * @CreateTime: 2021/4/13 11:29 * @Description:配置多数据源 */ @Configuration public class DynamicDataSourceConfig { /** * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个 * @param statFilter * @return * @throws SQLException */ @Primary @ConfigurationProperties(prefix="spring.datasource.druid.first") @Bean(name = "first_datasource") public javax.sql.DataSource first(Filter statFilter) throws SQLException { DruidDataSource dataSource = new DruidDataSource(); return dataSource; } @ConfigurationProperties(prefix="spring.datasource.druid.second") @Bean(name = "second_datasource") public javax.sql.DataSource second(Filter statFilter) throws SQLException { DruidDataSource dataSource = new DruidDataSource(); return dataSource; } /** * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例) * @param dataSource1 数据源1 * @param dataSource2 数据源2 * @return */ @Bean(name = "dynamicDataSource") public DynamicDataSource dataSource(@Qualifier("first_datasource") javax.sql.DataSource dataSource1, @Qualifier("second_datasource") DataSource dataSource2){ Map dataSourceMap = new HashMap(2); dataSourceMap.put(DatabaseContextHolder.FIRST_DS,dataSource1); dataSourceMap.put(DatabaseContextHolder.SECOND_DS,dataSource2); DynamicDataSource dynamicDataSource=new DynamicDataSource(); //该方法是AbstractRoutingDataSource的方法 dynamicDataSource.setTargetDataSources(dataSourceMap); //根据实际需求,指定默认操作的库 dynamicDataSource.setDefaultTargetDataSource(dataSource1); return dynamicDataSource; } /** * 根据数据源创建SqlSessionFactory * @param ds * @return * @throws Exception */ @Bean public SqlSessionFactory sqlSessionFactory(DynamicDataSource ds) throws Exception { SqlSessionFactoryBean fb = new SqlSessionFactoryBean(); //指定数据源(这个必须有,否则报错) fb.setDataSource(ds); return fb.getObject(); } }

注意:上面指定了setDefaultTargetDataSource默认操作数据库为first_datasource,当方法没有指定数据源会默认操作test1数据库。

继承AbstractRoutingDataSource /** * @Author: 孙常胜 * @CreateTime: 2021/4/13 09:37 * @Description:扩展 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法 * determineCurrentLookupKey() 方法决定使用哪个数据源 */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatabaseContextHolder.getDataBase(); } } 自定义注解+aop实现指定数据源 /** * @Author: 孙常胜 * @CreateTime: 2021/4/13 09:24 * @Description:自定义数据源注解,用于指定数据源 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface DataSource { String value() default "first_datasource"; } /** * @Author: 孙常胜 * @CreateTime: 2021/4/13 10:00 * @Description:切面处理类 * 当有注解时,按照指定的数据源进行操作 * 没有注解时,按照默认的数据源进行操作 * @Aspect 把当前类标识为一个切面供容器读取 */ @Aspect @Component public class DataSourceAspect implements Ordered { /** * 指定切入点 */ @Pointcut("@annotation(com.example.config.datasource.DataSource)") public void dataSourcePointCut(){ } @After("dataSourcePointCut()") public void after(){ DatabaseContextHolder.chearDataSource(); } /** * 环绕通知,包含了五种通知类型 * @return */ @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point){ MethodSignature signature = (MethodSignature) point.getSignature(); Method method=signature.getMethod(); //默认数据源 String defaultDataSource= DatabaseContextHolder.FIRST_DS; //获取该注解 DataSource dataSource=method.getAnnotation(DataSource.class); //存在注解,直接切换该注解对应的数据源 if(dataSource!=null){ defaultDataSource=dataSource.value(); } //切换数据源 DatabaseContextHolder.setDataBase(defaultDataSource); //继续执行方法 Object result=null; try { result=point.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } finally { //最后清除数据源 DatabaseContextHolder.chearDataSource(); } return result; } @Override public int getOrder() { return 1; } } 测试 测试1 默认查询

写一个简单的查询数据接口 在这里插入图片描述 在这里插入图片描述 分别查询用户表和菜单表 在这里插入图片描述 在这里插入图片描述

不写注解,应该默认去查询test1数据库,执行

在这里插入图片描述

可以看到,查询的是test1数据库表的内容,正确。

查询菜单表,不加注解测试

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

结论:可以看到,不加注解,默认还是查询的test1数据库,因为没有这张表,就报错了。可以知道我们配置的默认数据源是生效的。

测试2 指定查询

接下来给MenuController加上注解

在这里插入图片描述

启动,执行

在这里插入图片描述

结论:注解生效,指定了数据源2,可以实现指定数据源来进行数据操作。

测试3 复合查询

上面两种查询,已经可以完成两个库进行分别查询了。但是一般情况下,我们不会这么简单的查询,都是通过一个接口,查询两个库的数据进行相关处理。这里我简单演示一下一个方法操作两个数据库。

增加方法 在这里插入图片描述 在这里插入图片描述 all方法指定数据源2,查询数据 在usersServic.list()方法中增加注解 在这里插入图片描述 表示查询数据源1 执行方法: 在这里插入图片描述 结论:在service层增加注解,可以对方法进行指定数据源操作。 至此,多数据源操作就完成了。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3