一、場景復現
(1)項目
目錄
配置文件
spring:
application:
name: multi-datasource
profiles:
active: dev1
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/base?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8
username: root
password: admin
mybatis:
mapper-locations: classpath*:mapper/base/*.xml
type-aliases-package: com.mk.mybatis.multidatasource
configuration:
map-underscore-to-camel-case: true
sys:
one-mybatis:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/man?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8
username: root
password: admin
mybatis:
mapper-locations: classpath*:mapper/one/*.xml
type-aliases-package: com.mk.mybatis.multidatasource.one.entity
configuration:
map-underscore-to-camel-case: true
two-mybatis:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/person?autoReconnect=true&zeroDateTimeBehavior=convertToNull&characterEncoding=UTF-8
username: root
password: admin
mybatis:
mapper-locations: classpath*:mapper/two/*.xml
type-aliases-package: com.mk.mybatis.multidatasource.two.entity
configuration:
map-underscore-to-camel-case: true
server:
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
@Bean
public 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
@Bean
public DataSourceTransactionManager transactionManager(@Qualifier("datasource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean
public 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();
}
@Bean
public 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();
}
@Bean
public 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();
}
@Bean
public DataSourceTransactionManager oneTransactionManager(@Qualifier("oneDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public 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();
}
@Bean
public 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();
}
@Bean
public 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();
}
@Bean
public DataSourceTransactionManager twoTransactionManager(@Qualifier("twoDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public SqlSessionTemplate twoSqlSessionTemplate(@Qualifier("twoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
(2)測試類
BaseDao
@Resource
private DaseDao baseDao;
@Test
public 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
@Resource
private PersonDao personDao;
@Test
public 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.selectByPrimaryKey
at 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
@Resource
private ManDao manDao;
@Test
public 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.selectByPrimaryKey
at 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 {
}