SpringBoot + Mybatis + DynamicDataSource配置动态双数据源,可以动态切换数据源。
先贴一个单数据源的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配置:
com.alibaba
druid-spring-boot-starter
1.1.23
tk.mybatis
mapper-generator
1.1.5
tk.mybatis
mapper-spring-boot-starter
2.1.5
com.github.pagehelper
pagehelper
5.2.0
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 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数据库驱动为例。
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.3
mysql
mysql-connector-java
runtime
5.1.49
com.alibaba
druid-spring-boot-starter
1.1.23
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 {
}
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
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";
}
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 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();
}
}
采用Bean方式注入,不需要增加yml配置
通用mapper与分页插件依赖:
mysql
mysql-connector-java
runtime
5.1.49
com.alibaba
druid-spring-boot-starter
1.1.23
tk.mybatis
mapper-spring-boot-starter
2.1.5
com.github.pagehelper
pagehelper
5.2.0
BaseMapper:
package com.foo.bar.base;
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;
public interface BaseMapper extends Mapper, MySqlMapper {
}
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
查看SprinBoot自动配置匹配情况日志:
logging:
level:
root: INFO
org:
springframework:
boot:
autoconfigure:
logging: DEBUG
参考列表:
Druid 使用手册-使用ConfigFilter
DruidDataSource配置属性列表
Druid Spring Boot Starter
https://github.com/abel533/MyBatis-Spring-Boot
https://github.com/abel533/Mapper/wiki/1.2-spring