SpringBoot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源。
单数据源的mybatis + 通用mapper + pageHelper
先贴一个单数据源的mybatis + 通用mapper + pageHelper配置
application.properties
spring.datasource.druid.url=jdbc:mysql://localhost:3306/foo?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.username=bar
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=5
spring.datasource.druid.initial-size=5
spring.datasource.druid.test-while-idle=true
spring.datasource.druid.max-wait=60000
spring.datasource.druid.filters=config,slf4j
spring.datasource.druid.filter.slf4j.statement-executable-sql-log-enable=true
spring.datasource.druid.publickey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOvuGrxZ0Cj/ju27uWJ8VqDmY7956OGhVeefB0zw6F7OhBg9Bz+0c84RjzipqS0NQWwOb4kobfmg3WsV6ekr7TECAwEAAQ==
spring.datasource.druid.password=PNak4Yui0+2Ft6JSoKBsgNPl+A033rdLhFw+L0np1o+HDRrCo9VkCuiiXviEMYwUgpHZUFxb2FpE0YmSguuRww==
spring.datasource.druid.connection-properties=config.decrypt=true;config.decrypt.key=${spring.datasource.druid.publickey}
maven配置:
<!--mybatis start-->
<!-- druid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!--mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-generator</artifactId>
<version>1.1.5</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!--mybatis end-->
MybatisConfig.java
package com.foo.bar.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.github.pagehelper.PageInterceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import tk.mybatis.spring.annotation.MapperScan;
import javax.sql.DataSource;
import java.util.List;
import java.util.Properties;
/**
* @MapperScan 方式可以避免重复扫描
*/
@Configuration
@MapperScan(value = "com.foo.bar.mapper",
properties = {
"mappers=com.foo.bar.base.BaseMapper",
"notEmpty=true",
"IDENTITY=MYSQL"
}
)
public class MybatisConfig {
/**
* DataSource 配置
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DataSource dataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
//yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
sessionFactory.setMapperLocations(
((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
.getResources("classpath:mappers/**/*.xml"));
sessionFactory.setTypeAliasesPackage("com.foo.bar.entity");
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//开启下划线转驼峰支持
configuration.setMapUnderscoreToCamelCase(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
/**
* 分页拦截器配置
*/
@Bean
public PageInterceptor pageInterceptors(List<SqlSessionFactory> sqlSessionFactoryList) {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "false");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "count=check");
properties.setProperty("params", "count=countSql");
pageInterceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor);
}
return pageInterceptor;
}
}
这样单数据源的mybatis + 通用mapper + pageHelper的环境就可以了。
多数据源添加依赖
加入Mybatis启动器,这里添加了Druid连接池、Mysql数据库驱动为例。
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!-- mysql 连接类 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.49</version>
</dependency>
<!-- druid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
添加启动类
package com.foo.bar;
import com.foo.bar.annotation.EnableMybatis;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author Chuck
*/
@EnableMybatis
@EnableTransactionManagement
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class BarApplication {
public static void main(String[] args) {
SpringApplication.run(BarApplication.class, args);
}
}
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }):
这里用到了双数据源,需要排除数据源的自动配置,如果只有一个数据源用Spring Boot的自动配置就行。
@EnableTransactionManagement:开启事务支持。
@EnableMybatis:开启Mybatis功能
package com.foo.bar.annotation;
import com.foo.bar.config.MybatisConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(MybatisConfig.class)
public @interface EnableMybatis {
}
Mybatis配置类
package com.foo.bar.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;
@Configuration
@MapperScan(basePackages = DSConfig.BASE_PACKAGES)
public class MybatisConfig implements DSConfig {
@Primary
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master,@Qualifier(DB_SLAVE) DataSource slave) {
Map<Object, Object> dsMap = new HashMap<>(2);
dsMap.put(DB_MASTER, master);
dsMap.put(DB_SLAVE, slave);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(master);
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicDataSource);
//yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
sessionFactory.setMapperLocations(
((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
.getResources(DSConfig.MAPPER_LOCATIONS));
sessionFactory.setTypeAliasesPackage(TYPE_ALIASES_PACKAGE);
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//开启下划线转驼峰支持
configuration.setMapUnderscoreToCamelCase(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
}
DSConfig常量类:
package com.foo.bar.config;
public interface DSConfig {
String DS_PREFIX = "spring.datasource";
String DS_ACTIVE = "active";
String DB_MASTER = "masterDruidDataSource";
String DB_SLAVE = "slaveDruidDataSource";
String DRUID = "druid";
String BASE_PACKAGES = "com.foo.bar.**.mapper";
String MAPPER_LOCATIONS = "mappers/**/*.xml";
String TYPE_ALIASES_PACKAGE = "com.foo.bar.mapper.domain";
}
Druid连接池配置类
yml配置:
# DataSource
spring:
datasource:
active: druid
druid:
publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJyEm+x8bHLOGhj55ZZy8Ug1WqBjTGXuu/Rz5JZ5lTtjQ9mqwv69G8FtaBHOtKHL+ll5SXDIGFpOcU3A0+eSMpECAwEAAQ==
stat-view-servlet:
enabled: true
url-pattern: /druid/*
allow: 127.0.0.1
login-username: root
login-password: root
reset-enable: false
web-stat-filter:
enabled: true
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
url-pattern: /*
master:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/foo?useSSL=false&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password: eNDCOPEEjiLph59Hkp4oI8c/TAljTNVIFHU/YvT3ZjjC0fsXhqP5uDRygzR8APFuqNg+MCEQ3ZjMgbQ/oRC5Aw==
connect-properties:
config.decrypt: true
config.decrypt.key: ${spring.datasource.druid.publickey}
filters: stat,wall,slf4j,config
max-active: 100
initial-size: 10
max-wait: 60000
min-idle: 10
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20
slave:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/bar?useSSL=false&useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8
username: root
password: eNDCOPEEjiLph59Hkp4oI8c/TAljTNVIFHU/YvT3ZjjC0fsXhqP5uDRygzR8APFuqNg+MCEQ3ZjMgbQ/oRC5Aw==
connect-properties:
config.decrypt: true
config.decrypt.key: ${spring.datasource.druid.publickey}
filters: stat,wall,slf4j,config
max-active: 100
initial-size: 10
max-wait: 60000
min-idle: 10
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-while-idle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: true
max-open-prepared-statements: 50
max-pool-prepared-statement-per-connection-size: 20
logging:
level:
root: info
Druid连接池的自动配置类:
package com.foo.bar.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import java.util.Arrays;
import java.util.Collections;
import javax.sql.DataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
@ConditionalOnClass(DruidDataSource.class)
@ConditionalOnProperty(prefix = DSConfig.DS_PREFIX, value = DSConfig.DS_ACTIVE, havingValue = DSConfig.DRUID)
@Slf4j
public class DruidAutoConfig implements DSConfig {
/**
* DataSource 配置
* @return
*/
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
@Bean(name = DB_MASTER)
@Primary
public DataSource masterDruidDataSource() {
return DruidDataSourceBuilder.create().build();
}
/**
* DataSource 配置
* @return
*/
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
@Bean(name = DB_SLAVE)
public DataSource slaveDruidDataSource() {
return DruidDataSourceBuilder.create().build();
}
}
切换数据源
切换数据源注解:
package com.foo.bar.annotation;
import com.foo.bar.config.DSConfig;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
String value() default DSConfig.DB_MASTER;
}
动态数据源类:
package com.foo.bar.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
log.info("当前数据源为{}", DataSourceContextHolder.getDS());
return DataSourceContextHolder.getDS();
}
}
动态数据源AOP实现类:
package com.foo.bar.aspect;
import com.foo.bar.annotation.DS;
import com.foo.bar.config.DataSourceContextHolder;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 保证在Spring事务切面之前
* @author Chuck
*/
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
@Before("@annotation(com.foo.bar.annotation.DS)")
public void beforeSwitchDS(JoinPoint point) {
Class<?> className = point.getTarget().getClass();
String methodName = point.getSignature().getName();
Class<?>[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
Method method = className.getMethod(methodName, argClass);
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
DataSourceContextHolder.setDS(dataSource);
}
@After("@annotation(com.foo.bar.annotation.DS)")
public void afterSwitchDS(JoinPoint point) {
DataSourceContextHolder.clearDS();
}
}
绑定当前线程数据源类:
package com.foo.bar.config;
public class DataSourceContextHolder {
public static final String DEFAULT_DS = DSConfig.DB_MASTER;
private static final ThreadLocal<String> DS_HOLDER = new ThreadLocal<>();
public static void setDS(String dbType) {
DS_HOLDER.set(dbType);
}
public static String getDS() {
return (DS_HOLDER.get());
}
public static void clearDS() {
DS_HOLDER.remove();
}
}
集成通用mapper及分页插件
采用Bean方式注入,不需要增加yml配置
通用mapper与分页插件依赖:
<!-- 数据库 start -->
<!-- 引用通用mapper后已包含mybatis依赖 -->
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>2.1.3</version>-->
<!-- </dependency>-->
<!-- mysql 连接类 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>5.1.49</version>
</dependency>
<!-- druid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!--mapper-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 数据库 end -->
BaseMapper:
package com.foo.bar.base;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
public interface BaseMapper<T> extends Mapper<T>, MySqlMapper<T> {
}
MybatisConfig:
使用通用mapper需使用tk.mybatis.spring.annotation.MapperScan扫描,这种方式可以避免重复扫包
package com.foo.bar.config;
import com.github.pagehelper.PageInterceptor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;
/**
* @MapperScan 方式可以避免重复扫描
*/
@Configuration
@MapperScan(value = DSConfig.BASE_PACKAGES,
properties = {
"mappers=com.foo.bar.base.BaseMapper",
"notEmpty=true",
"IDENTITY=MYSQL"
}
)
public class MybatisConfig implements DSConfig {
@Primary
@Bean
public DynamicDataSource dynamicDataSource(@Qualifier(DB_MASTER) DataSource master,@Qualifier(DB_SLAVE) DataSource slave) {
Map<Object, Object> dsMap = new HashMap<>(2);
dsMap.put(DB_MASTER, master);
dsMap.put(DB_SLAVE, slave);
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setDefaultTargetDataSource(master);
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DynamicDataSource dynamicDataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicDataSource);
//yaml中的mybatis配置会失效,这里必须设置mapper的位置,否则用不了xml的mapper
sessionFactory.setMapperLocations(
((ResourcePatternResolver) new PathMatchingResourcePatternResolver())
.getResources(DSConfig.MAPPER_LOCATIONS));
sessionFactory.setTypeAliasesPackage(TYPE_ALIASES_PACKAGE);
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
//开启下划线转驼峰支持
configuration.setMapUnderscoreToCamelCase(true);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
/**
* 分页拦截器配置
*/
@Bean
public PageInterceptor pageInterceptors(List<SqlSessionFactory> sqlSessionFactoryList) {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("helperDialect", "mysql");
properties.setProperty("reasonable", "false");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "count=check");
properties.setProperty("params", "count=countSql");
pageInterceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
sqlSessionFactory.getConfiguration().addInterceptor(pageInterceptor);
}
return pageInterceptor;
}
}
查看SprinBoot自动配置匹配情况日志:
logging:
level:
root: INFO
org:
springframework:
boot:
autoconfigure:
logging: DEBUG
参考列表:
https://github.com/abel533/MyBatis-Spring-Boot
https://github.com/abel533/Mapper/wiki/1.2-spring