SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

这篇具有很好参考价值的文章主要介绍了SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

本文是SpringBoot第27讲,在某些场景下,Springboot需要使用多个数据源,以及某些场景会需要多个数据源的动态切换。本文主要介绍上述场景及 SpringBoot+MyBatis实现多个数据源的方案和示例

1、知识准备

需要了解多数据源出现的场景和对应的多数据源集成思路。

1.1、什么场景会出现多个数据源?

一般而言有如下几种出现多数据源的场景。

  • 场景一:不同的业务涉及的表位于不同的数据库

随着业务的拓展,模块解耦,服务化的拆分等,不同的业务涉及的表会放在不同的数据库中。

  • 例如商品主库、商品审核库、商品附属库需要在一个微服务中使用;

  • daily环境和线上环境数据库、表、列、索引比对,同时需要访问daily环境和线上环境

  • 场景二:主库和从库分离(读写分离)

主从分离等相关知识请参考这篇文章: MySQL第七讲:MySQL分库分表详解

  • 场景三:数据库的分片

数据库的分片相关知识和方案请参考:SpringBoot集成MySQL - 分库分表ShardingJDBC

  • 场景四:多租户隔离

所有数据库表结构一致,只是不同客户的数据放在不同数据库中,通过数据库名对不同客户的数据隔离。这种场景有一个典型的叫法:多租户

PS:除了这种多租户除了用不同的数据库隔离不同客户数据外,还会通过额外的表字段隔离(比如tenant_id字段,不同的tenant_id表示不同的客户),对应的实现方式和案例可以参考 SpringBoot第30讲:SpringBoot集成MySQL - MyBatis-Plus基于字段隔离的多租户

1.2、常见的多数据源的实现思路?

应对上述出现的场景,多数据源方式如何实现呢?

  • 针对场景一:不同的业务涉及的表位于不同的数据库

    • 首先,出现这种场景且在一个模块中设计多数据源时,需要考虑当前架构的合理性,因为从设计的角度而言不同的业务拆分需要对应着不同的服务/模块
    • 其次,我们会考虑不同的package去隔离,不同的数据源放在不同的包下的代码中
  • 针对场景二:主库和从库分离(读写分离)

这种场景下我们叫动态数据源,通常方式使用AOP方式拦截+ThreadLocal切换。

2、简单示例

2.1、分包方式实现

分包方式实现:

1、在publish.properties中配置两个数据库:

# dbsource1
datasource.url=jdbc:mysql://xxx?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
datasource.username=***
datasource.password=***
datasource.minIdle=5
datasource.maxActive=20

# dbsource2
datasource.daily.url=jdbc:mysql://***?useUnicode=true&characterEncoding=UTF-8&useAffectedRows=true&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
datasource.daily.username=***
datasource.daily.password=****
datasource.daily.minIdle=5
datasource.daily.maxActive=10

2、在application.yml中配置好映射关系

Spring:
 datasource1:
    url: ${datasource.url}
    username: ${datasource.username}
    password: ${datasource.password}
    minIdle: ${datasource.minIdle}
    maxActive: ${datasource.maxActive}

  # datasource config 2
  datasource2:
    url: ${datasource.daily.url}
    username: ${datasource.daily.username}
    password: ${datasource.daily.password}
    minIdle: ${datasource.daily.minIdle}
    maxActive: ${datasource.daily.maxActive}

3、建立连个数据源的配置文件:
第一个配置文件:

//表示这个类为一个配置类
@Configuration
// 配置mybatis的接口类放的地方
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper.test01", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class DataSourceConfig1 {
	// 将这个对象放入Spring容器中
	@Bean(name = "test1DataSource")
	// 表示这个数据源是默认数据源
	@Primary
	// 读取application.properties中的配置参数映射成为一个对象
	// prefix表示参数的前缀
	@ConfigurationProperties(prefix = "spring.datasource1")
	public DataSource getDateSource1() {
		return DataSourceBuilder.create().build();
	}
	@Bean(name = "test1SqlSessionFactory")
	// 表示这个数据源是默认数据源
	@Primary
	// @Qualifier表示查找Spring容器中名字为test1DataSource的对象
	public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)
			throws Exception {
		SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
		bean.setDataSource(datasource);
		bean.setMapperLocations(
				// 设置mybatis的xml所在位置
				new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test01/*.xml"));
		return bean.getObject();
	}
	@Bean("test1SqlSessionTemplate")
	// 表示这个数据源是默认数据源
	@Primary
	public SqlSessionTemplate test1sqlsessiontemplate(
			@Qualifier("test1SqlSessionFactory") SqlSessionFactory sessionfactory) {
		return new SqlSessionTemplate(sessionfactory);
	}
}

第二个配置文件:

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper.test02", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class DataSourceConfig2 {
    @Bean(name = "test2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource2")
    public DataSource getDateSource2() {
      	return DataSourceBuilder.create().build();
    }
    @Bean(name = "test2SqlSessionFactory")
    public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource datasource)
        throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/test02/*.xml"));
        return bean.getObject();
    }
    @Bean("test2SqlSessionTemplate")
    public SqlSessionTemplate test2sqlsessiontemplate(
        @Qualifier("test2SqlSessionFactory") SqlSessionFactory sessionfactory) {
      			return new SqlSessionTemplate(sessionfactory);
    		}
}

注意:

  • 1、@Primary这个注解必须要加,因为不加的话spring将分不清楚哪个为主数据源(默认数据源)

  • 2、mapper的接口、xml形式以及dao层都需要两个分开,目录如图:

  • SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

  • 3、bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(“XXXX”)); mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致的,具体看情况吧,注意一下就行,问题不大的)

  • 4、在service层中根据不同的业务注入不同的dao层。

  • 5、如果是主从复制- -读写分离:比如test01中负责增删改,test02中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

  • 6、如果是分布式结构的话,不同模块操作各自的数据库就好,test01包下全是test01业务,test02全是test02业务,但是如果test01中掺杂着test02的编辑操作,这时候将会产生事务问题:即test01中的事务是没法控制test02的事务的,这个问题在之后的博客中会解决。

2.2、针对场景二:主库和从库分离(读写分离)

这种场景下我们叫动态数据源,通常方式使用AOP方式拦截+ThreadLocal切换。(本文的示例主要针对这种场景)

简介: 用这种方式实现多数据源的前提必须要清楚两个知识点:AOP原理和 AbstractRoutingDataSource抽象类。

1、AOP: 不切当的说就是相当于拦截器,只要满足要求的都会被拦截过来,然后进行一些列的操作。具体需要自己去体会。

2、AbstractRoutingDataSource: 这个类是实现多数据源的关键,他的作用就是动态切换数据源,

  • 实质:有多少个数据源就存多少个数据源在targetDataSources(是AbstractRoutingDataSource的一个map类型的属性,其中value为每个数据源,key表示每个数据源的名字)这个属性中,然后根据 determineCurrentLookupKey()这个方法获取当前数据源在map中的key值,然后determineTargetDataSource()方法中动态获取当前数据源,如果当前数据源不存并且默认数据源也不存在就抛出异常。

  • 存在就抛出异常。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
    
  	//多数据源map集合
    private Map<Object, Object> targetDataSources;
    //默认数据源
    private Object defaultTargetDataSource;
    //其实就是targetDataSources,后面的afterPropertiesSet()方法会将targetDataSources 赋值给resolvedDataSources
    private Map<Object, DataSource> resolvedDataSources;
    private DataSource resolvedDefaultDataSource;
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this.targetDataSources = targetDataSources;
    }
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        Object lookupKey = this.determineCurrentLookupKey();
        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        } else {
            return dataSource;
        }
    }
    protected abstract Object determineCurrentLookupKey();
}

具体实现:

1、定义一个动态数据源: 继承AbstractRoutingDataSource 抽象类,并重写determineCurrentLookupKey()方法

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
        return dataBaseType;
    }
}

2、创建一个切换数据源类型的类: ThreadLocal这个知识点可以参考我的博客:JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践 就是为了线程的安全性,每个线程之间不会相互影响。

public class DataSourceType {

    public enum DataBaseType {
        TEST01, TEST02
    }

    // 使用ThreadLocal保证线程安全
    private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();

    // 往当前线程里设置数据源类型
    public static void setDataBaseType(DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            throw new NullPointerException();
        }
        System.err.println("[将当前数据源改为]:" + dataBaseType);
        TYPE.set(dataBaseType);
    }

    // 获取数据源类型
    public static DataBaseType getDataBaseType() {
        DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.TEST01 : TYPE.get();
        System.err.println("[获取当前数据源的类型为]:" + dataBaseType);
        return dataBaseType;
    }

    // 清空数据类型
    public static void clearDataBaseType() {
      	TYPE.remove();
    }
}

3、定义多个数据源: 怎么定义就不多说了,和方法一是一样的,主要是将定义好的多个数据源放在动态数据源中。

@Configuration
@MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory")
public class DataSourceConfig {
	@Primary
	@Bean(name = "test1DataSource")
	@ConfigurationProperties(prefix = "spring.datasource1")
	public DataSource getDateSource1() {
			return DataSourceBuilder.create().build();
	}
	@Bean(name = "test2DataSource")
	@ConfigurationProperties(prefix = "spring.datasource.test2")
	public DataSource getDateSource2() {
			return DataSourceBuilder.create().build();
	}

	@Bean(name = "dynamicDataSource")
	public DynamicDataSource DataSource(@Qualifier("test1DataSource") DataSource test1DataSource,
			@Qualifier("test2DataSource") DataSource test2DataSource) {
      Map<Object, Object> targetDataSource = new HashMap<>();
      targetDataSource.put(DataSourceType.DataBaseType.TEST01, test1DataSource);
      targetDataSource.put(DataSourceType.DataBaseType.TEST02, test2DataSource);
      DynamicDataSource dataSource = new DynamicDataSource();
      dataSource.setTargetDataSources(targetDataSource);
      dataSource.setDefaultTargetDataSource(test1DataSource);
      return dataSource;
	}
	@Bean(name = "SqlSessionFactory")
	public SqlSessionFactory test1SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
			throws Exception {
		SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
		bean.setDataSource(dynamicDataSource);
		bean.setMapperLocations(
				new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
		return bean.getObject();
	}
}

4、定义AOP: 就是不同业务切换不同数据库的入口。如果觉得execution太长不愿意写,就可以定义一个注解来实现。可参考于我的博客:Java基础知识第二讲:Java开发手册/注解/反射/ IO

@Aspect
@Component
public class DataSourceAop {
	@Before("execution(* com.mzd.multipledatasources.service..*.test01*(..))")
	public void setDataSource2test01() {
		System.err.println("test01业务");
		DataSourceType.setDataBaseType(DataBaseType.TEST01);
	}
	
	@Before("execution(* com.mzd.multipledatasources.service..*.test02*(..))")
	public void setDataSource2test02() {
		System.err.println("test02业务");
		DataSourceType.setDataBaseType(DataBaseType.TEST02);
	}
}

整体目录如图:
SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

3、示例源码

todo

Action1:SpringBoot 使用多数据源导致 MyBatis 分页插件失效

背景

现象是gateway 网关 报错FluxOnAssembly$OnAssemblyException,经过排查,发现是分页查询时返回了1000多条数据,导致数据量超出了网关限制,从而抛错。打断点发现MyBatis分页插件无效,MyBatis分页拦截器断点无法进入

情景

1、使用Springboot

2、自定义sqlSession(多数据源)

解决方法:

1、检查分页插件类上是否加注解 @Component

2、在SqlSessionFactoryConfig类注入拦截器

3、在SqlSession配置类中补充如下代码

sqlSessionFactoryBean.setPlugins(new Interceptor[]{pageInterceptor});

注意:设置plugins时必须在sqlSessionFactoryBean.getObject()之前。SqlSessionFactory在生成的时候就会获取plugins,并设置到Configuration中,如果在之后设置则不会注入。

SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源
SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源文章来源地址https://www.toymoban.com/news/detail-488833.html

到了这里,关于SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringBoot 整合 Neo4j、MySQL 多数据源方案(Druid Mybatis DynamicDatasource)

    本文总结了Neo4j和Spring/SpringBoot、Alibaba Druid、Dynamic Datasource、Mybatis等整合方案,对相应配置做了详细说明。 添加Neo4j JDBC Driver依赖 添加application.yml配置 添加Neo4j JDBC Driver + Alibaba Druid依赖 添加application.yml配置 添加Neo4j JDBC Driver、Alibaba Druid、Dynamic DataSource依赖 添加application.y

    2023年04月08日
    浏览(41)
  • 【精·超详细】SpringBoot 配置多个数据源(连接多个数据库)

    目录 1.项目路径 2.pom.xml  引入依赖: 3.application.yml配置文件: 4.两个entity类 5.Conroller 6.两个Service以及两个ServiceImpl  7.两个Mapper及两个Mapper.xml  8.运行Application  然后在浏览器请求 9.查看两个数据库是否有新增数据           总结: 1.pom.xml 引入依赖: dynamic-datasource-spring-b

    2024年02月12日
    浏览(53)
  • springboot+mybatis实现多数据源

    最近做项目碰到了一个需要连4个不同数据库的需求,其中db1、db2表结构都不相同;另外两个数据库same_db_private、same_db_public表结构完全相同,一个对内一个对外,只是从物理上隔离了数据而已。 所以打算通过静态配置包路径的方式来实现db1、db2的操作,并且通过扩展Spring的

    2024年02月09日
    浏览(29)
  • MyBatis整合Springboot多数据源实现

    数据源,实际就是数据库连接池,负责管理数据库连接,在 Springboot 中,数据源通常以一个 bean 的形式存在于 IOC 容器中,也就是我们可以通过依赖注入的方式拿到数据源,然后再从数据源中获取数据库连接。 那么什么是多数据源呢,其实就是 IOC 容器中有多个数据源的 bea

    2023年04月22日
    浏览(47)
  • springboot+mybatis+pgsql多数据源配置

    jdk环境:1.8 配置了双数据源 pgsql+pgsql   第一个配置文件 :PrimaryDataSourceConfig 参数详情 :@Primary //指定你主要的数据源是哪一个 例如:我这里主要数据源是第一个配置文件 所以我的第二个配置文件并没有加这个注解 注意修改: @MapperScan里面的basePackages @ConfigurationProperties里面

    2024年02月03日
    浏览(94)
  • SpringBoot结合MyBatis实现多数据源配置

    SpringBoot框架实现多数据源操作,首先需要搭建Mybatis的运行环境。 由于是多数据源,也就是要有多个数据库,所以,我们创建两个测试数据库,分别是:【sp-demo01】和【sp-demo02】,如下图所示: 具体SQL代码: 创建【sp-demo01】数据库。 创建【sp-demo02】数据库。 MyBatis框架中,

    2024年02月09日
    浏览(32)
  • 若依分离版——配置多数据源(mysql和oracle),实现一个方法操作多个数据源

    目录 一、若依平台配置           二、编写oracle数据库访问的各类文件  三.  一个方法操作多个数据源 一、若依平台配置 1、在ruoyi-admin的pom.xml添加oracle依赖 2、引入ojdbc6.jar包 在ruoyi-admin的resource下创建lib文件夹,将ojdbc6.jar包保存在此目录下 3.  刷新maven 刷新maven保证ruo

    2024年02月05日
    浏览(32)
  • 实战指南,SpringBoot + Mybatis 如何对接多数据源

    MyBatis缓存原理 Mybatis plugin 的使用及原理 MyBatis+Springboot 启动到SQL执行全流程 数据库操作不再困难,MyBatis动态Sql标签解析 从零开始,手把手教你搭建Spring Boot后台工程并说明 Spring框架与SpringBoot的关联与区别 Spring监听器用法与原理详解 Spring事务畅谈 —— 由浅入深彻底弄懂

    2024年02月12日
    浏览(39)
  • 解决多数据源的事务问题 - 基于springboot--mybatis

    在Spring Boot和MyBatis中,我们有时需要在方法中同时使用两个不同的数据库,但使用 @Transactional 注解会变得复杂。这时我们可以用一种更灵活的方法来处理。 想象一下这样的场景:我们有两个数据库,我们希望在一个方法中同时操作它们,但是普通的 @Transactional 注解变得不太

    2024年02月01日
    浏览(34)
  • springBoot-Mybatis-Plus 多数据源切换实现

    前言:本文主要通过AbstractRoutingDataSource,实现根据 http 访问携带的标识动态切换数据源; 1 AbstractRoutingDataSource 介绍: AbstractRoutingDataSource 是 Spring 框架中的一个抽象类,它可以用来实现动态数据源切换。在多数据源场景下,AbstractRoutingDataSource 可以根据不同的请求来动态地选

    2024年02月03日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包