☰
Spring Boot+MyBatis多数据源硬核配置:配置类绑定绕开AOP
2026/9/30 7:30:34 网站建设 项目流程

简介:面向SpringBoot+MyBatis开发场景,这份PDF聚焦于多数据源的最简解决方案,服务于需要连接多个数据库的Java工程。针对主从读写分离、分库支撑业务等实际需求,从降低配置复杂度的角度出发,给出不依赖JPA、也不借助AOP动态切换的思路,而是基于@Configuration与@MapperScan完成数据源、SqlSessionFactory、事务管理器及SqlSessionTemplate的逐层装配,并展示test1主库、test2从库在application.properties中的参数写法。内容还覆盖Mapper接口与XML文件的目录拆分、@Primary主库指定等关键细节,便于读者直接对照复现。该资源为1个PDF文件,压缩包大小54KB,结构紧凑、聚焦核心代码,目前已有2356人学习,适合正在配置双数据源的中级Java工程师参考。通过文档可快速掌握双数据源配置的完整链路,避免因主库未指定或SqlSessionTemplate注入错误导致的常见报错,提升开发与调试效率。

1. springboot+mybatis多数据源:绕开AOP切换,用配置类硬解

先说结论:网上搜springboot+mybatis多数据源,十篇有八篇在讲AbstractRoutingDataSource配合AOP动态切换数据源,剩下一篇讲JPA。但我项目里遇到的是业务分库——用户库、订单库、日志库是不同业务域,Mapper固定属于某一个库,根本不需要运行时切换。折腾下来发现,最稳的反而是看起来最笨的方案:每个数据源写一套独立的DataSource配置类,用@MapperScan把不同的Mapper包绑到不同的SqlSessionTemplate上。这套方案代码重复,但逻辑直白,没有切面、没有ThreadLocal、没有@DS注解,任何人接手都能半小时看懂。这个资源就是springboot+mybatis多数据源的最简落地写法,适合做分库、读写分离背景不复杂、只想快速让两个库跑起来的项目,也适合面试时把多数据实现讲清楚。

2. 多数据源三种方案:AOP切换、JPA、配置级绑定怎么选

2.1 AbstractRoutingDataSource动态切换的原理和代价

先看最流行的动态切换方案是怎么工作的。Spring提供一个AbstractRoutingDataSource,它本身是一个DataSource代理,内部维护一个Map<Object, DataSource>目标数据源集合,再通过determineCurrentLookupKey()返回一个key,运行时从map里取真正的DataSource。AOP方案的套路一般是:定义一个@DataSource(name = "test1")注解,切面在方法执行前把name塞进ThreadLocal,执行后再清理,determineCurrentLookupKey()从ThreadLocal拿key。

这套方案的优点是写业务代码时不需要关心底层走哪个库,同一个Mapper接口可以被多个数据源共用,适合主从读写分离场景——读多写少,切到备库读、主库写。但代价很现实:

第一,事务边界和切换时机是玄学。Spring事务管理器在开启事务时就会从数据源拿连接,@Transactional包住的方法如果内部切换数据源,连接已经被绑定到事务上,切了也白切,SQL还是走旧连接。网上很多人贴@DataSource注解切换成功,是因为根本没有事务包裹,或者数据源已经被代理了一层才勉强能用,一旦加上@Transactional就翻车。

第二,切面本身需要维护ThreadLocal的清理,漏清理或者异常路径没走干净,下一次请求会串DataSource。生产环境出现这种情况,排查成本极高,因为错误SQL可能在一两个小时后才出现。

第三,多数据源事务回滚没法做。动态切换的数据源本质上还是单事务管理器,跨库操作无法同时回滚,这和配置级方案遇到的问题一样,但动态切换的代码复杂度反而把这个问题掩盖了。

2.2 JPA多数据源方案的适用边界

网上另有一批资料是拿Spring Data JPA做多数据源,思路和MyBatis类似:定义两个@EntityScan分包的EntityManagerFactory,再用@EnableJpaRepositories分别指到不同包。这个方案的优点是把Repository抽象层做得很干净,但问题是JPA本身对复杂SQL、动态SQL的支持比MyBatis弱一截,很多做分库的项目是从老MyBatis系统迁移过来的,历史mapper xml一大堆,迁JPA等于重写数据访问层,成本根本扛不住。

再者,JPA多数据源的配置并不比MyBatis简单,EntityManagerFactory、TransactionManager、PersistenceUnit三件套照样要写两遍,省掉的代码量非常有限。如果项目里已经有mybatis的依赖和xml,没必要为了多数据源把整个ORM层换掉,老老实实继续用mybatis更稳。

2.3 配置级绑定:三套Bean各建一次,Mapper按包归属

这份资源的方案本质是把“数据源-会话工厂-事务管理器-SqlSessionTemplate”四个Bean为一组,每组独立创建,再通过@MapperScan的basePackages和sqlSessionTemplateRef两个参数,把不同包下的Mapper接口绑定到对应的SqlSessionTemplate上。从工程视角看,这个方案有几个关键优势:

一是没有运行时切换,一个Mapper从出生就确定属于哪个数据源,不存在串库的路径。二是每个数据源都是完整独立的SqlSessionFactory,事务管理器各管各的,单库事务完全正常。三是排查问题简单——数据源配置类就两个,翻一遍就能看到test1和test2的连线关系。

下表把三者对比一下。

对比项AOP动态切换JPA多数据源配置级绑定(本方案)
核心机制运行时按key切DataSource多套EntityManagerFactory按包路由多套SqlSessionTemplate按包绑定
适合场景主从读写分离、同结构库切换纯JPA项目分库MyBatis项目分库、不同结构库
事务处理单事务管理器,跨库回滚难每库独立,跨库回滚难每库独立,跨库回滚难
代码复杂度切面+注解+ThreadLocal实体扫描配置两套配置类重复写两遍
串库风险有无无
上手难度中高中低

2.4 为什么说这套方案更适合中小项目分库

我自己的项目是业务分库模式:test1是基础数据,test2是业务流水,两个库的表结构完全不同,接口天然归属于某个库。这种情况下动态切换根本没有意义——我总不可能在一个方法里同时查询两份数据还要求它们同源。配置级绑定反而把结构理得很清楚:com.neo.mapper.test1包下面的Mapper永远不会碰test2的连接,新人看代码的时候很直观地知道去哪找数据源配置。

代价是要接受代码冗余。每个数据源一套相同的四段式Bean注入,数据源多了配置类会膨胀,这是方案的天花板。好在绝大多数分库项目也就是两三个库,写两遍配置类并不算负担,相比AOP方案的隐藏问题,这点重复代码完全值得。

3. 配置文件与目录规划:两条数据源怎么拆才不乱

3.1 pom依赖:少而精,别引入多余的东西

这套方案的依赖非常简单,核心就是mybatis的starter和mysql驱动(或对应数据库驱动),不需要引入额外的多数据源框架。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>

这里mybatis-spring-boot-starter版本按你自己Spring Boot版本匹配:Boot 2.5以上用2.3.x,Boot 3.x用3.0.x。如果数据库是MySQL 8,驱动版本会自动随Boot管理,不用特意指定。

3.2 application.properties里两条数据源的写法

配置文件是整套方案的入口,数据源命名规则是spring.datasource.xxx开头,后面跟driverClassName、url、username、password。这份资源里test1是主库,test2是次库。

mybatis.config-locations=classpath:mybatis/mybatis-config.xml spring.datasource.test1.driverClassName = com.mysql.jdbc.Driver spring.datasource.test1.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 spring.datasource.test1.username = root spring.datasource.test1.password = root spring.datasource.test2.driverClassName = com.mysql.jdbc.Driver spring.datasource.test2.url = jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8 spring.datasource.test2.username = root spring.datasource.test2.password = root

注意几个细节。第一,driverClassName这里写的MySQL 5的驱动com.mysql.jdbc.Driver,MySQL 8环境应该改成com.mysql.cj.jdbc.Driver,否则启动会报ClassNotFound。第二,url里useUnicode=true&characterEncoding=utf-8是防止中文乱码的标准参数,但&在properties文件里不用转义,在yml里要写成&amp;,这个很多人第一次踩。第三,mybatis.config-locations指定了全局mybatis配置文件的路径,如果不需要额外配置,这一行可以不写。

如果项目用的application.yml,写法等价于下面这样:

spring: datasource: test1: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 username: root password: root test2: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8 username: root password: root

Spring Boot的@ConfigurationProperties(prefix = "spring.datasource.test1")绑定数据源属性时,driverClassName和driver-class-name是兼容的,url和jdbc-url也是relaxed binding关系,选一套风格用到底就行,不要混写。

3.3 Mapper目录分库:Java包和XML必须同构

多数据源配置类里的@MapperScan是按包扫描的,所以dao接口必须按库分包。这份资源的约定是:

com.neo.mapper.test1 test1库的Mapper接口 com.neo.mapper.test2 test2库的Mapper接口 classpath:mybatis/mapper/test1/*.xml test1库的XMLStatement classpath:mybatis/mapper/test2/*.xml test2库的XMLStatement

XML文件放在resources/mybatis/mapper/test1和resources/mybatis/mapper/test2两个目录下,与Java包一一对应。这个约定非常重要,因为每个SqlSessionFactory的mapperLocations只按通配符加载对应目录,写错目录会导致这个数据源的Mapper方法找不到SQL,运行时报Invalid bound statement (not found)。

目录不合理的第二个问题是XML的namespace指错包。比如test1库的xml里namespace写了com.neo.mapper.test2.User2Mapper,MyBatis不会报错,但会导致你的test1数据源去加载test2的Mapper,运行时出现无法预料的查询。所以XML里的namespace、Mapper接口全限定名、接口所在包三者必须严格一致。

3.4 mybatis-config.xml里做什么

资源里mybatis.config-locations指向的mybatis-config.xml通常用来放一些全局配置。实际项目中我一般会在里面开启驼峰映射或者设置日志实现:

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <typeAliases> <package name="com.neo.entity"/> </typeAliases> </configuration>

一个容易忽略的细节是:如果mybatis-config.xml里设置了环境(environments)信息,这个环境会被SqlSessionFactoryBean重新设置的数据源覆盖,所以不用刻意在xml里配DataSource。另外,多数据源场景下typeAliases按包扫描没有问题,但是两个实体的包如果不同且类型名相同,别名会冲突,建议实体类全限定名保持一致或直接使用全限定名。

4. 核心实现:DataSourceConfig类四段式注入与Mapper绑定

4.1 主库配置类:四段式Bean的完整写法

先看主库test1的配置类,这批代码是整套方案的核心。注意@Primary注解,它声明这个数据源是主数据源,避免Spring在注入DataSource时因为有两个实现而抛异常。

@Configuration @MapperScan(basePackages = "com.neo.mapper.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate") public class DataSource1Config { @Bean(name = "test1DataSource") @ConfigurationProperties(prefix = "spring.datasource.test1") @Primary public DataSource testDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "test1SqlSessionFactory") @Primary public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mybatis/mapper/test1/*.xml")); return bean.getObject(); } @Bean(name = "test1TransactionManager") @Primary public DataSourceTransactionManager testTransactionManager(@Qualifier("test1DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "test1SqlSessionTemplate") @Primary public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

逐层说下注入链路:test1DataSource通过DataSourceBuilder.create().build()创建,@ConfigurationProperties(prefix = "spring.datasource.test1")把配置文件里spring.datasource.test1前缀下的属性绑定到DataSource。然后创建test1SqlSessionFactory,注入test1DataSource,并通过bean.setMapperLocations(...)加载test1目录下的xml。再创建test1TransactionManager,这是test1库的事务管理器参数为test1DataSource。最后包一层test1SqlSessionTemplate,它持有了SqlSessionFactory,后续Mapper执行SQL时通过这个模板获得Session。

@MapperScan是这套方案里最关键的注解,两个参数缺一不可:basePackages指定扫描哪个包下的Mapper接口;sqlSessionTemplateRef指名将这些Mapper绑定到哪个SqlSessionTemplate。注意,这个Ref参数必须和@Bean的name保持一致,写错会导致Mapper创建时找不到模板。

4.2 从库配置类:没有@Primary的镜像

第二次配置test2的代码基本是复制粘贴,但有一个原则性区别:只能有一个数据源标@Primary。test2配置类里所有的@Bean都不加@Primary,否则主库身份会冲突。

@Configuration @MapperScan(basePackages = "com.neo.mapper.test2", sqlSessionTemplateRef = "test2SqlSessionTemplate") public class DataSource2Config { @Bean(name = "test2DataSource") @ConfigurationProperties(prefix = "spring.datasource.test2") public DataSource testDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "test2SqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mybatis/mapper/test2/*.xml")); return bean.getObject(); } @Bean(name = "test2TransactionManager") public DataSourceTransactionManager testTransactionManager(@Qualifier("test2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "test2SqlSessionTemplate") public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }

这里有个Spring Boot的配置细节值得重申:如果两个数据源的Bean都不加@Primary,应用启动事务管理器时Spring无法确定哪个是默认DataSource,会抛出NoUniqueBeanDefinitionException。所以主库必须标@Primary,且只有在主库上才标。而@Qualifier("test1DataSource")的作用是精确注入指定名字的数据源Bean,保证test1的SqlSessionFactory拿到的必须是test1的DataSource而不是主库的test1。

4.3 事务管理器为什么要建两套

每个SqlSessionFactory本身不管理事务,事务交给DataSourceTransactionManager。这个方案里每个数据源单独建一个事务管理器,意味着test1、test2的事务边界是隔离的。

这个设计有两层意义。首先,单库内部的事务依然完整——比如在User1Mapper.insert加一@Transactional "test1TransactionManager",写test1库的业务抛异常会正常回滚。其次,跨库事务是无法靠这个方案解决的,因为两个事务管理器各自为战,不会互相感知提交或回滚。因此涉及test1、test2两个库的操作,不要放在一个方法里开着单个事务去写,正确的做法是业务上拆分,或者引入分布式事务。

4.4 SqlSessionTemplate这层不能省

有些人会想绕过SqlSessionTemplate,直接把SqlSessionFactory注入Mapper,写法更短。但MyBatis的SqlSessionFactory是线程安全的,而SqlSession不是。SqlSessionTemplate是MyBatis-Spring提供的线程安全会话模板,它在内部为每个Mapper方法调用打开一个SqlSession,用完之后自动关闭,适合注入到Spring管理的Bean中。如果直接持有SqlSessionFactory并手动openSession,就要自己管理会话生命周期,一不小心就产生连接泄漏。

@MapperScan里的sqlSessionTemplateRef指向的正是这个模板,它把包内的Mapper接口和模板绑死。换个说法:每个库的Mapper拿到的SqlSessionTemplate是独立的,执行SQL时连接来自各自的DataSource,天然不会串库。

4.5 如果数据源变多,怎么扩展

再加一个test3库,步骤固定为:第一,application.properties加一组spring.datasource.test3.*配置。第二,新建DataSource3Config,复制DataSource2Config,把所有test2改成test3。第三,在com.neo.mapper.test3包下放第三方库的Mapper接口,在resources/mybatis/mapper/test3/放对应XML。三个库一定别把第几个是主库搞混,主库永远只有一份@Primary。

注意,这套方案不依赖自动配置的DataSourceAutoConfiguration。Spring Boot看到没有@EnableAutoConfiguration排除时,会因为classpath里存在DataSource而触发自动配置,可能和你的自定义DataSource产生冲突。比较稳妥的做法是在启动类上加exclude = DataSourceAutoConfiguration.class:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

加了这个排除后,数据源完全由自定义Config管理,不信你自己试一下把某个@Bean删掉再启动,报错会直指某个DataSource找不到,非常清晰。

5. 多数据源常见问题排查:五个必踩的坑与修复

5.1 启动即报NoUniqueBeanDefinitionException

  • 现象:Spring Boot启动到一半抛NoUniqueBeanDefinitionException,提示有两个DataSource类型的Bean,无法确定注入哪个。
  • 原因:配置类建了两个DataSource Bean,但主库没标@Primary,Spring在自动装配DataSourcer时找不到默认实现。
  • 解决:在主库配置类的DataSource、SqlSessionFactory、TransactionManager、SqlSessionTemplate四个Bean上全部标@Primary,从库一个都不标。如果标了还报错,检查启动类上是否加了exclude = DataSourceAutoConfiguration.class,因为DataSourceAutoConfiguration会在classpath里检测到DataSource后尝试二次注册,两个来源的实现Bean会打架。

5.2 Mapper方法全部报Invalid bound statement

  • 现象:应用能启动,但调用某个Mapper方法时抛Invalid bound statement (not found): com.neo.mapper.test1.User1Mapper.getAll。
  • 原因:常见的有三种:@MapperScan的包路径没扫到该Mapper;mapperLocations的通配路径写错目录,xml没有被加载;xml的namespace与Mapper的全限定名不一致。
  • 解决:先看target/classes目录下xml有没有被拷进来。Maven项目里xml如果放在src/main/java下,pom没配置resources过滤,打包时会漏掉xml,需要改pom:
<build> <resources> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>

然后对照mapperLocations路径,确认classpath:mybatis/mapper/test1/*.xml与实际文件路径一致。最后检查XML里namespace写的是不是com.neo.mapper.test1.User1Mapper,注意test1和test2别串了。

5.3 数据源连接偶发串库,SQL跑到另一个库

  • 现象:test1的Mapper查询偶尔返回test2库的数据,或者在日志里看到执行SQL的JDBC连接URL是另一个库。
  • 原因:多数是HikariCP连接池在多数据源下没有正确隔离,或者两个SqlSessionFactoryBean创建时引用了同一个DataSource Bean。
  • 解决:检查每个SqlSessionFactoryBean的setDataSource是否通过@Qualifier精确注入了对应数据源。如果直接在方法参数里写DataSource dataSource,Spring会注入@Primary那个test1DataSource,两个SqlSessionFactory就指向同一个连接池,必串库。排查手段是在每个SqlSessionFactoryBean创建时打印数据源信息:
System.out.println("test1 factory bind to: " + dataSource);

启动日志里能看到两者指向不同的DataSource实例哈希,哈希一致说明引用错了。

5.4 @Transactional下的数据源切换不生效问题

  • 现象:方法上加了@Transactional后,实际执行的SQL没有走预期的数据源,或者从库写数据时事务没有回滚。
  • 原因:多数据源配置下,@Transactional默认的事务管理器是DataSourceTransactionManager类型中标注@Primary的那个。如果test2的Mapper操作被方法级@Transactional包裹,事务管理器默认绑定了test1的模板,test2的SQL就会不在任何事务管理下执行。
  • 解决:跨库操作必须显式指定事务管理器。单库方法上写明@Transactional(transactionManager = "test1TransactionManager")与@Transactional(transactionManager = "test2TransactionManager")分别对应两个库:
@Transactional(transactionManager = "test2TransactionManager") public void saveUser(UserEntity user) { user2Mapper.insert(user); }

特别注意,如果同一个方法里既写test1又写test2,没有一个事务管理器能同时管理两个库,此时要么拆分方法、要么用全局分布式事务,不能幻想@Transactional帮你搞定一切。这不是Spring的缺陷,而是单机事务管理器本就无法跨物理库。

5.5 PageHelper分页插件失效或分页串库

  • 现象:引入PageHelper做分页,第一个库的查询分页正常,第二个库的分页不生效,或者limit字段错乱。
  • 原因:PageHelper的拦截器是绑定到SqlSessionFactory上的,它对拦截器创建时挂载的会话工厂生效。如果只给test1的SqlSessionFactoryBean配置了拦截器,test2的SqlSessionFactory没配,test2的Mapper查询就不经过拦截器,分页也就是一句空话。
  • 解决:在每个SqlSessionFactoryBean上单独配置拦截器,而不是只配置全局的mybatis-config:
@Bean(name = "test2SqlSessionFactory") public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); Interceptor[] interceptors = new Interceptor[]{new PageInterceptor()}; bean.setPlugins(interceptors); bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:mybatis/mapper/test2/*.xml")); return bean.getObject(); }

这段代码里的PageInterceptor是com.github.pagehelper.PageInterceptor,要配合在properties里配好pagehelper.helperDialect=mysql,如果还想优化,把pagehelper的offsetAsPageNum等参数在mybatis-config.xml里设置。以后每新增一个数据源,都要记得把拦截器同步加进去,漏一个就废一个。

6. 验证与扩展:Controller直连测试和事务边界处理技巧

验证这套方案是否真的把两个库分开,最快的方式是写一个Controller,分别注User1Mapper和User2Mapper,执行这批接口后看日志里的连接归属。

@RestController public class UserController { @Autowired private User1Mapper user1Mapper; @Autowired private User2Mapper user2Mapper; @GetMapping("/getUsers") public List<UserEntity> getUsers() { return user1Mapper.getAll(); } @GetMapping("/getUser") public UserEntity getUser(Long id) { return user2Mapper.getOne(id); } @GetMapping("/add") public void save(UserEntity user) { user2Mapper.insert(user); } @GetMapping("/update") public void update(UserEntity user) { user2Mapper.update(user); } @DeleteMapping("/delete/{id}") public void delete(@PathVariable("id") Long id) { user1Mapper.delete(id); } }

注意这里user1Mapper和user2Mapper是两个包的接口,注入Spring后都由IoC容器创建Mapper代理,不存在冲突。测试时可以加一行日志配置来确认SQL落库去向:

logging.level.com.neo.mapper.test1=debug logging.level.com.neo.mapper.test2=debug

打开debug日志后,每次SQL执行控制台会打印这条SQL的JDBC连接信息,你可以直接用这个技巧快速验证两个Mapper是否各自走各自的库,不用翻数据库binlog。

这个链路里还有一个很小的扩展习惯:如果你的分库场景里需要读一个库、主库写一个库,而两个库表结构相同,那不需要把Mapper包拆开,只要保证主库SqlSessionFactory扫描公共Mapper包,从库的Mapper包为空即可。但如果是业务分库,两个库的表结构不同,则必须维持本方案“包按库拆、XML按库拆”的约定。

跨库查询是这个方案的软肋,也是最容易把项目搞复杂的地方。如果业务上你确实要在一次请求里同时读test1和test2的数据,一个Controller里依次调用两个Mapper是没有问题的,但不要把它们包在同一个@Transactional(transactionManager = "test1TransactionManager")里,那会把test2的写入也归到test1的事务管理,回滚和提交完全错乱。最安全的方式是拆成两个方法分别标注各自的事务管理器,或者干脆让两个查询都在无事务状态下执行。

到这我把这套方案和相关的坑全部理清了一遍。从那以后,我每次新建一个数据源配置类,都强制走一遍核对流程:先确认启动类有没有排除DataSourceAutoConfiguration,再看@MapperScan的包路径是不是对应Mapper包,然后查mapperLocations的xml目录拼写,最后盯着@Primary有没有加错到从库上。这套笨办法反而帮我避开了绝大多数多数据源翻车点,希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询