一、场景复现
(1)项目
目录
配置文件
spring:application:name: multi-datasourceprofiles:active: dev1datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/base?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8username: rootpassword: admin
mybatis:mapper-locations: classpath*:mapper/base/*.xmltype-aliases-package: com.mk.mybatis.multidatasourceconfiguration:map-underscore-to-camel-case: truesys:one-mybatis:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/man?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8username: rootpassword: adminmybatis:mapper-locations: classpath*:mapper/one/*.xmltype-aliases-package: com.mk.mybatis.multidatasource.one.entityconfiguration:map-underscore-to-camel-case: truetwo-mybatis:datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/person?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8username: rootpassword: adminmybatis:mapper-locations: classpath*:mapper/two/*.xmltype-aliases-package: com.mk.mybatis.multidatasource.two.entityconfiguration:map-underscore-to-camel-case: trueserver:port: 8080
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.9.RELEASE</version><relativePath/></parent><groupId>com.mk.mybatis</groupId><artifactId>multi-datasource</artifactId><version>1.0-SNAPSHOT</version><name>multi-datasource</name><description>MybatisMultiDataSource</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.48</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.20</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.9.RELEASE</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>2.1.5</version><exclusions><exclusion><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></exclusion></exclusions></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.7</version><dependencies><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.7</version></dependency></dependencies><configuration><!--允许移动生成的文件 --><verbose>true</verbose><!-- 是否覆盖 --><overwrite>true</overwrite><!-- 自动生成的配置 --><configurationFile>generatorConfig1.xml</configurationFile></configuration></plugin></plugins></build>
</project>
继承mybatis生成器生成的dao类
@Mapper
public interface DaseDao extends IBaseDao {
}
@Mapper
public interface ManDao extends IManDao {
}
@Mapper
public interface PersonDao extends IPersonDao {
}
默认mybatis数据源配置
@Configuration
@MapperScan(value = "com.mk.mybatis.multidatasource.base.dao", sqlSessionTemplateRef = "baseSqlSessionTemplate")
public class BaseMybatisConfig {@Primary@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSourceProperties baseDataSourceProperties() {return new DataSourceProperties();}@Primary@Bean("datasource")public DataSource baseDataSource(@Qualifier("baseDataSourceProperties") DataSourceProperties dataSourceProperties) {DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().build();return dataSource;}@Primary@Bean@ConfigurationProperties(prefix = "mybatis")public MybatisProperties baseMybatisProperties() {return new MybatisProperties();}@Primary@Beanpublic SqlSessionFactory baseSqlSessionFactory(@Qualifier("datasource") DataSource dataSource,@Qualifier("baseMybatisProperties") MybatisProperties mybatisProperties) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(mybatisProperties.resolveMapperLocations());bean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());bean.setConfigurationProperties(mybatisProperties.getConfigurationProperties());bean.setConfiguration(mybatisProperties.getConfiguration());bean.setConfigLocation(Optional.ofNullable(mybatisProperties.getConfigLocation()).map(location -> {try {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();return resourceResolver.getResource(location);} catch (Exception var3) {return null;}}).orElse(null));return bean.getObject();}@Primary@Beanpublic DataSourceTransactionManager transactionManager(@Qualifier("datasource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Primary@Beanpublic SqlSessionTemplate baseSqlSessionTemplate(@Qualifier("baseSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
one mybatis数据源配置
@Configuration
@MapperScan(value = "com.mk.mybatis.multidatasource.one.dao", sqlSessionTemplateRef = "oneSqlSessionTemplate")
@Slf4j
public class OneMybatisConfig {@Bean@ConfigurationProperties(prefix = "sys.one-mybatis.datasource")public DataSourceProperties oneDataSourceProperties() {return new DataSourceProperties();}@Beanpublic DataSource oneDataSource(@Qualifier("oneDataSourceProperties") DataSourceProperties dataSourceProperties) {DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().build();return dataSource;}@Bean@ConfigurationProperties(prefix = "sys.one-mybatis.mybatis")public MybatisProperties oneMybatisProperties(){return new MybatisProperties();}@Beanpublic SqlSessionFactory oneSqlSessionFactory(@Qualifier("oneDataSource") DataSource dataSource,@Qualifier("oneMybatisProperties")MybatisProperties mybatisProperties)throws Exception{SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(mybatisProperties.resolveMapperLocations());bean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());bean.setConfigurationProperties(mybatisProperties.getConfigurationProperties());bean.setConfiguration(mybatisProperties.getConfiguration());bean.setConfigLocation(Optional.ofNullable(mybatisProperties.getConfigLocation()).map(location->{try {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();return resourceResolver.getResource(location);} catch (Exception var3) {return null;}}).orElse(null));return bean.getObject();}@Beanpublic DataSourceTransactionManager oneTransactionManager(@Qualifier("oneDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic SqlSessionTemplate oneSqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory){return new SqlSessionTemplate(sqlSessionFactory);}
}
two mybatis数据源配置
@Configuration
@MapperScan(value = "com.mk.mybatis.multidatasource.two.dao", sqlSessionTemplateRef = "twoSqlSessionTemplate")
@Slf4j
public class TwoMybatisConfig {@Bean@ConfigurationProperties(prefix = "sys.two-mybatis.datasource")public DataSourceProperties twoDataSourceProperties() {return new DataSourceProperties();}@Beanpublic DataSource twoDataSource(@Qualifier("twoDataSourceProperties") DataSourceProperties dataSourceProperties) {DataSource dataSource = dataSourceProperties.initializeDataSourceBuilder().build();return dataSource;}@Bean@ConfigurationProperties(prefix = "sys.two-mybatis.mybatis")public MybatisProperties twoMybatisProperties() {return new MybatisProperties();}@Beanpublic SqlSessionFactory twoSqlSessionFactory(@Qualifier("twoDataSource") DataSource dataSource,@Qualifier("twoMybatisProperties") MybatisProperties mybatisProperties) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(dataSource);bean.setMapperLocations(mybatisProperties.resolveMapperLocations());bean.setTypeAliasesPackage(mybatisProperties.getTypeAliasesPackage());bean.setConfigurationProperties(mybatisProperties.getConfigurationProperties());bean.setConfiguration(mybatisProperties.getConfiguration());bean.setConfigLocation(Optional.ofNullable(mybatisProperties.getConfigLocation()).map(location -> {try {ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();return resourceResolver.getResource(location);} catch (Exception var3) {return null;}}).orElse(null));return bean.getObject();}@Beanpublic DataSourceTransactionManager twoTransactionManager(@Qualifier("twoDataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}@Beanpublic SqlSessionTemplate twoSqlSessionTemplate(@Qualifier("twoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);}
}
(2)测试类
BaseDao
@Resourceprivate DaseDao baseDao;@Testpublic void testBaseDao() {System.out.println(baseDao.selectByPrimaryKey(1));}
输出
2020-05-04 21:03:21,174 DEBUG [main] 5363 com.mk.mybatis.multidatasource.base.base.dao.IBaseDao.selectByPrimaryKey[143]: ==> Preparing: select id, `name` from base where id = ?
2020-05-04 21:03:21,205 DEBUG [main] 5394 com.mk.mybatis.multidatasource.base.base.dao.IBaseDao.selectByPrimaryKey[143]: ==> Parameters: 1(Integer)
2020-05-04 21:03:21,221 DEBUG [main] 5410 com.mk.mybatis.multidatasource.base.base.dao.IBaseDao.selectByPrimaryKey[143]: <== Total: 0
null
debug看到的数据源
PersonDao
@Resourceprivate PersonDao personDao;@Testpublic void testPersonDao() {System.out.println(personDao.selectByPrimaryKey(1));}
输出
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.mk.mybatis.multidatasource.two.dao.PersonDao.selectByPrimaryKeyat org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)at org.apache.ibatis.binding.MapperProxy.lambda$cachedMapperMethod$0(MapperProxy.java:61)at org.apache.ibatis.binding.MapperProxy$$Lambda$443/448131210.apply(Unknown Source)at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:61)at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:56)at com.sun.proxy.$Proxy67.selectByPrimaryKey(Unknown Source)at com.mk.mybatis.multidatasource.MultiDatasourceApplicationTests.testPersonDao(MultiDatasourceApplicationTests.java:34)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
debug看到的数据源
ManDao
@Resourceprivate ManDao manDao;@Testpublic void testManDao() {System.out.println(manDao.selectByPrimaryKey(1));}
输出:
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.mk.mybatis.multidatasource.one.dao.ManDao.selectByPrimaryKeyat org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:235)at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)at org.apache.ibatis.binding.MapperProxy.lambda$cachedMapperMethod$0(MapperProxy.java:61)at org.apache.ibatis.binding.MapperProxy$$Lambda$443/736951628.apply(Unknown Source)at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:61)at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:56)at com.sun.proxy.$Proxy68.selectByPrimaryKey(Unknown Source)at com.mk.mybatis.multidatasource.MultiDatasourceApplicationTests.testManDao(MultiDatasourceApplicationTests.java:40)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:497)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)at org.junit.runners.ParentRunner.run(ParentRunner.java:363)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)at org.junit.runner.JUnitCore.run(JUnitCore.java:137)at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
debug看到的数据源
(3)分析
从上面的测试结果来看,多数据源配置不生效。
然而去掉PersonDao和ManDao的@Mapper注解,多数据源配置就生效了
PersonDao
//@Mapper
public interface PersonDao extends IPersonDao {
}
2020-05-04 21:26:18,477 DEBUG [main] 4644 com.mk.mybatis.multidatasource.two.base.dao.IPersonDao.selectByPrimaryKey[143]: ==> Preparing: select id, `name` from person where id = ?
2020-05-04 21:26:18,497 DEBUG [main] 4664 com.mk.mybatis.multidatasource.two.base.dao.IPersonDao.selectByPrimaryKey[143]: ==> Parameters: 1(Integer)
2020-05-04 21:26:18,506 DEBUG [main] 4673 com.mk.mybatis.multidatasource.two.base.dao.IPersonDao.selectByPrimaryKey[143]: <== Total: 0
null
ManDao
//@Mapper
public interface ManDao extends IManDao {
}
2020-05-04 21:31:04,470 DEBUG [main] 4413 com.mk.mybatis.multidatasource.one.base.dao.IManDao.selectByPrimaryKey[143]: ==> Preparing: select id, `name` from man where id = ?
2020-05-04 21:31:04,499 DEBUG [main] 4442 com.mk.mybatis.multidatasource.one.base.dao.IManDao.selectByPrimaryKey[143]: ==> Parameters: 1(Integer)
2020-05-04 21:31:04,513 DEBUG [main] 4456 com.mk.mybatis.multidatasource.one.base.dao.IManDao.selectByPrimaryKey[143]: <== Total: 0
null
二、原由
由于使用了tk.mybatis的包,默认自动全包扫描@Mapper注解。tk.mybatis扫描生成的Mapper比其他自定义配置的bean生成对应的Mapper对象快,而自定义的mybatis@MapperScan后扫描,Dao Bean已经生成了,无法再指定数据源进行注入了。
三、解决方案
(1)去掉tk.mybatis
<dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>2.1.5</version><exclusions><exclusion><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></exclusion></exclusions></dependency>
(2)不加@Mapper注解
//@Mapper
public interface PersonDao extends IPersonDao {
}