一、背景

mybatis自身没有做多数据源(包括读写分离)的支持,网上很多现有博文都是C&V产物,实现较老且缺乏对mybatis-spring的良好支持,故简单写过一个springboot的demo,仅依赖配置即可完成对多数据源的支持。

二、使用

1、项目中增加对应的maven依赖:

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.0</version>
</dependency>
<!-- 非必需,方便使用 -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version>
</dependency>

2、将以下两个个文件复制到项目中:

  • 自定义配置映射类
import lombok.Data;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.springframework.beans.factory.support.BeanNameGenerator;import java.io.Serializable;/*** mybatis复合数据源自动配置属性* @author zxf* @date 2020-08-16 20:26:13*/
@Data
class MultiMybatisProperties implements Serializable {/*** dataSource连接池类型*/private Class<? extends javax.sql.DataSource> pool;/*** 连接池驱动类型*/private Class<? extends java.sql.Driver> driver;/*** 连接完整url*/private String url;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 扫描的包路径*/private String mapperLocations;/*** 扫描的mapper接口包集合* @see MapperScan#basePackages()*/private String[] basePackages;/*** 组件命名器* @see MapperScan#nameGenerator()*/private Class<BeanNameGenerator> namingClass;/*** dao映射代理类工厂类型* @see MapperScan#factoryBean()*/private Class<? extends MapperFactoryBean<?>> factoryBean;/*** 是否懒加载* @see MapperScan#lazyInitialization()*/private boolean lazyInitialization;
}
  • 实际逻辑实现类
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultBeanNameGenerator;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.util.Assert;import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;/*** mybatis多数据源扫描注册器* @author zxf* @date 2020-08-16 20:09:23*/
@Slf4j
class MultiMybatisScannerRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {private Environment env;/** 作为模板的默认配置key **/private static final String DEFAULT_KEY = "default";private static final Pattern ID_MATCH = Pattern.compile("^[\\w]+$");@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {log.info("Ready to load properties of multi mybatis configs from environment.");Map<String, MultiMybatisProperties> sources = processConfigs();registerComponents(sources, registry);log.info("Finish to count loading properties of multi mybatis configs from environment:" + sources.size());}/*** 向spring容器中注册对应的相关mybatis组件* 其中MapperScannerConfigurer 参考MapperScannerRegistrar的registerBeanDefinitions的实现* @see org.mybatis.spring.annotation.MapperScannerRegistrar* @param sources 有效的配置集合* @param registry bean注冊器*/private void registerComponents(Map<String, MultiMybatisProperties> sources, BeanDefinitionRegistry registry) {sources.forEach((key, value) -> {Class<? extends BeanNameGenerator> clazz =null == value.getNamingClass() ? DefaultBeanNameGenerator.class : value.getNamingClass();BeanNameGenerator generator = BeanUtils.instantiateClass(clazz);//DataSourceBeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(value.getPool());//builder.addPropertyValue("processPropertyPlaceHolders", true);builder.addPropertyValue("jdbcUrl", value.getUrl());builder.addPropertyValue("username", value.getUsername());builder.addPropertyValue("password", value.getPassword());builder.addPropertyValue("driverClassName", value.getDriver().getCanonicalName());AbstractBeanDefinition definition = builder.getBeanDefinition();String dataSourceName = generator.generateBeanName(definition, registry);registry.registerBeanDefinition(dataSourceName, builder.getBeanDefinition());//sqlSessionFactorybuilder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionFactoryBean.class);builder.addPropertyReference("dataSource", dataSourceName);try {builder.addPropertyValue("mapperLocations", new PathMatchingResourcePatternResolver().getResources(value.getMapperLocations()));} catch (IOException e) {e.printStackTrace();}String SqlSessionFactoryName = generator.generateBeanName(definition, registry);registry.registerBeanDefinition(SqlSessionFactoryName, builder.getBeanDefinition());//DataSourceTransactionManagerbuilder = BeanDefinitionBuilder.genericBeanDefinition(DataSourceTransactionManager.class);builder.addPropertyReference("dataSource", dataSourceName);String transactionManagerName = generator.generateBeanName(definition, registry);registry.registerBeanDefinition(transactionManagerName, builder.getBeanDefinition());//SqlSessionTemplatebuilder = BeanDefinitionBuilder.genericBeanDefinition(SqlSessionTemplate.class);//builder.addPropertyValue("sqlSessionFactory", SqlSessionFactoryName);builder.addConstructorArgReference(SqlSessionFactoryName);String sqlSessionTemplateName = generator.generateBeanName(definition, registry);registry.registerBeanDefinition(sqlSessionTemplateName, builder.getBeanDefinition());//MapperScannerConfigurerbuilder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);builder.addPropertyValue("processPropertyPlaceHolders", true);if (!BeanNameGenerator.class.equals(clazz)) {builder.addPropertyValue("nameGenerator", generator);}if (null != value.getFactoryBean()) {builder.addPropertyValue("mapperFactoryBeanClass", value.getFactoryBean().getCanonicalName());}builder.addPropertyValue("sqlSessionTemplateBeanName", sqlSessionTemplateName);List<String> basePackages = Arrays.stream(value.getBasePackages()).filter(org.springframework.util.StringUtils::hasText).collect(Collectors.toList());builder.addPropertyValue("basePackage",org.springframework.util.StringUtils.collectionToCommaDelimitedString(basePackages));builder.addPropertyValue("lazyInitialization", String.valueOf(value.isLazyInitialization()));registry.registerBeanDefinition(generator.generateBeanName(definition, registry), builder.getBeanDefinition());});}/*** 获取有效的配置集合* @return java.util.List<com.resintec.lilliput.system.config.data.MultiMybatisProperties>*/private Map<String, MultiMybatisProperties> processConfigs(){//首先确保在配置文件中能解析到对应的配置Bindable<Map<String, MultiMybatisProperties>>bindable = Bindable.mapOf(String.class, MultiMybatisProperties.class);BindResult<Map<String, MultiMybatisProperties>>bind = Binder.get(env).bind("mybatis.multi", bindable);Assert.isTrue(null != bind && bind.isBound(),"Failed to load multi mybatis dataSource due to no config found in th environment.");//“default”关键字作为id将标识当前配置为模板属性配置,不作为独立的真实配置来处理Map<String, MultiMybatisProperties> sources, finalSources = sources = bind.get();sources.entrySet().stream().filter(en -> DEFAULT_KEY.equals(en.getKey())).findFirst().ifPresent(temp -> finalSources.values().forEach(s -> {s.setUrl(StringUtils.isBlank(s.getUrl()) ? temp.getValue().getUrl() : s.getUrl());s.setUsername(StringUtils.isBlank(s.getUsername()) ? temp.getValue().getUsername() : s.getUsername());s.setPassword(StringUtils.isBlank(s.getPassword()) ? temp.getValue().getPassword() : s.getPassword());s.setBasePackages(null == s.getBasePackages() ? temp.getValue().getBasePackages() : s.getBasePackages());s.setMapperLocations(StringUtils.isBlank(s.getMapperLocations()) ? temp.getValue().getMapperLocations() : s.getMapperLocations());s.setPool(null == s.getPool() ? temp.getValue().getPool() : s.getPool());s.setDriver(null == s.getDriver() ? temp.getValue().getDriver() : s.getDriver());s.setNamingClass(null == s.getNamingClass() ? temp.getValue().getNamingClass() : s.getNamingClass());s.setFactoryBean(null == s.getFactoryBean() ? temp.getValue().getFactoryBean() : s.getFactoryBean());}));sources = finalSources.entrySet().stream().filter(en -> !DEFAULT_KEY.equals(en.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));//基本校验sources.forEach((key, value) -> Assert.isTrue(ID_MATCH.matcher(key).matches()&& StringUtils.isNotBlank(value.getUrl())&& StringUtils.isNotBlank(value.getUsername())&& StringUtils.isNotBlank(value.getPassword()) && null != value.getBasePackages()&& value.getBasePackages().length != 0&& StringUtils.isNotBlank(value.getMapperLocations())&& null != value.getPool()&& null != value.getDriver()//&& null != s.getNamingClass()//&& null != s.getFactoryBean(), "Failed to load multi mybatis dataSource due to the illegal config:" + value));Assert.isTrue(!sources.isEmpty(),"Failed to load multi mybatis dataSource due to no available config found in th environment.");log.info("Find multi mybatis dataSource config from environment:{}.", sources.keySet());return sources;}@Overridepublic void setEnvironment(Environment environment) {env = environment;}
}

3、在项目的 application.yml或者其他可纳入spring管理的配置中增加以下配置内容(以yml文件为例):

mybatis:multi:db1: #某个数据源的唯一标识;不要重复;“default”将会作为关键字标识此数据源为模板配置(不作为独立的数据源,仅供其他数据源参考的默认设置)pool: com.zaxxer.hikari.HikariDataSource #使用的连接池类型driver: com.mysql.jdbc.Driver #使用的数据库连接驱动username: root #使用的数据库用户名password: root #使用的数据库密码url: jdbc:mysql://127.0.0.1:3306/test1 #使用的数据库urllazy-initialization: true #是否懒加载db2:pool: com.zaxxer.hikari.HikariDataSourcedriver: com.mysql.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/test2lazy-initialization: truedb3:pool: com.zaxxer.hikari.HikariDataSourcedriver: com.mysql.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/test3lazy-initialization: truedb4:pool: com.zaxxer.hikari.HikariDataSourcedriver: com.mysql.jdbc.Driverusername: rootpassword: rooturl: jdbc:mysql://127.0.0.1:3306/test4lazy-initialization: true

三、扩展

1、该demo只是基本示范,在配置映射的类MultiMybatisProperties中只有几个最基本的属性,但是实际项目中dataSource、数据库连接池、mybatis-spring可能会需要更多的配置属性。只要在MultiMybatisProperties中增加对应属性并在MultiMybatisScannerRegistrar类的registerComponents中增加对该属性的解析即可。

2、关于读写分离,建议将读与写相关的mapper与dao放在不同的包下当作两个不同的数据源来处理即可。如果实在不想分包,建议从自己继承org.mybatis.spring.mapper.MapperFactoryBean入手,手动管理org.apache.ibatis.binding.MapperRegistry,在业务代码中通过aop或者ThreadLocal等手段灵活切换需要使用的org.apache.ibatis.binding.MapperMethod,需要处理的内容就比较多了就懒得写。

3、此demo中的属性映射对象MultiMybatisProperties由实现类从environment中读取,先于spring容器初始化完成,故无法使用@ConfigurationProperties来管理,这导致了该对象在yml等配置文件中缺乏对应的属性提示。用户可以将实现类MultiMybatisScannerRegistrar中的属性绑定关系由现在的map改为list,然后在/resources/META-INF/additional-spring-configuration-metadata.json(该文件对map支持不友好)中增加提示即可。

4、本示例支持本地事务回滚,多数据源的事务请自己通过集成seata等现有的成熟解决方案处理,此demo不涉及。

mybatis集成springboot的多数据源最新实现相关推荐

  1. SpringBoot整合:Druid、MyBatis、MyBatis-Plus、多数据源、knife4j、日志、Redis,Redis的Java操作工具类、封装发送电子邮件等等

    SpringBoot笔记 一.SpringBoot 介绍 1.1.SpringBoot简介 SpringBoot 是一个快速开发的框架, 封装了Maven常用依赖.能够快速的整合第三方框架:简化XML ...

  2. springboot添加多数据源连接池并配置Mybatis

    springboot添加多数据源连接池并配置Mybatis 转载请注明出处:https://www.cnblogs.com/funnyzpc/p/9190226.html May 12, 2018  ...

  3. SpringBoot框架与MyBatis集成,连接Mysql数据库

    SpringBoot是一种用来简化新Spring应用初始搭建及开发过程的框架,它使用特定方式来进行配置,使得开发人员不再需要定义样板化的配置.MyBatis是一个支持普通SQL查询.存储和高级映射的持 ...

  4. springboot+mybatis集成自定义缓存ehcache用法笔记

    今天小编给大家整理了springboot+mybatis集成自定义缓存ehcache用法笔记,希望对大家能有所办帮助! 一.ehcache介绍 EhCache 是一个纯Java的进程内缓存管理框架,属 ...

  5. mybatis集成 c3p0数据源

    为什么80%的码农都做不了架构师?>>>    Mybatis集成c3p0数据源 要想集成其他数据源的话,其实可以直接继承UnpooledDataSourceFactory 即可 p ...

  6. SpringBoot实现多数据源(二)【Mybatis插件】

    上一篇文件<SpringBoot实现多数据源(一)[普通版切换]> 二.通过 Mybatis 插件切换数据源 通过 Mybatis 结合插件实现读写分离的数据源 配置Mybatis数据源插 ...

  7. SpringBoot配置多数据源Mybatis/MybatisPlus/mysql

    一.前言 项目开发过程中,单一数据源不能满足开发需求或者需要用到主从数据库的时候,引入多数据源配置在项目中显得尤为必要.下面简单介绍一种在spingboot中结合mybatis针对同类型的数据源mys ...

  8. 第13章 Kotlin 集成 SpringBoot 服务端开发(1)

    第13章 Kotlin 集成 SpringBoot 服务端开发 本章介绍Kotlin服务端开发的相关内容.首先,我们简单介绍一下Spring Boot服务端开发框架,快速给出一个 Restful He ...

  9. 狂神聊 ElasticSearch(IK分词器+Rest+集成SpringBoot+实战爬虫项目+完整代码及资料)

    Bilibili 搜索关注:狂神说 Java(和狂神一起学习,共同进步) 公众号:狂神说(文章日更) 狂神聊 ElasticSearch 版本:ElasticSearch 7.6.1(全网最新了) 6 ...

最新文章

  1. 【ACM】Doubly Linked List(STL list)
  2. Golang copy()函数
  3. 成功解决Exception “unhandled ModuleNotFoundError“No module named ‘face_recognition.cli‘
  4. [LeetCode] Valid Anagram
  5. 使用 Autofill 插件快速提交BUG
  6. 深度学习项目:歌词的自动生成
  7. VB.Net + asp.net的一个web系统,使用SQL2000数据库 现在运行时偶尔会出现一个奇怪现象,一个用户登录时,登录后的界面竟然是另一个用户...
  8. c++ idea 插件_IDEA的基本使用:让你的IDEA有飞一般的感觉
  9. applicationcontext获取bean_如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章...
  10. 甘肃教师学苑2020文章代码_【重磅通知】甘肃省电化教育中心 关于聘任教育信息化研究员的通知...
  11. 集合A和集合B的并运算图示
  12. 由于系统错误 1114:动态连接库(DLL)初始化例程失败。(MySQL ODBC 5.3 Unicode Driver,c:\Program Files(x86)\MySQL\Connector
  13. 未能加载“xxx”程序集
  14. 如何做产品的品牌推广?怎么推广自己的产品?品牌推广怎样做更好
  15. 今天的文章只有一点点
  16. 行业分析网站-网站分析软件-免费网站详细数据分析软件
  17. linux 内核下载,Linux Kernel
  18. php7.0 freetype_php 添加 freetype支持
  19. jquery课后练习
  20. 解决报错“Ignoring ffi-1.12.2 because its extensions are not built. Try: gem pristine ffi --version 1.12”

热门文章

  1. Forethought Future Cup - Elimination Round G. Zoning Restrictions 最大流(最小割)
  2. [干货] 一文看懂numpy.nonzero() 与 numpy.argwhere()非零元素处理
  3. PEP8 -- Python代码样式指南(中文版)
  4. imgaug quokka_Quokka CMS的新功能和Beta版路线图
  5. Hololens2项目基础开发
  6. MacOS Catalina Beta使用体验
  7. PHP webshell、暴力破解
  8. Java语言这些年的发展
  9. 根据性别自动切换用户图标——DAY4
  10. 微信小程序实现:输入手机号点击按钮查询手机号归属地