【简介】
如果不熟悉MyBatisPlus可以先看下这篇文章:SpringBoot + MyBatis Plus 生成代码器 (时间格式问题)
本文主要讲解使用Druid + 注解的方式配置多数据源
【引入依赖】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
【application.yml】
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/kyx?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭
enabled: false
url: jdbc:mysql://localhost:3306/qiankyx?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: admin
login-password: kyx123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
auto-mapping-behavior: full
mapper-locations: classpath*:mapper/**/*Mapper.xml
type-aliases-package: com.kyx.orderSys.biz.**.model
global-config:
db-config:
logic-not-delete-value: 1
#逻辑删除配置
logic-delete-value: 0
#数据库大写下划线转换
capital-mode: true
【DataSourceType.java】
添加数据源枚举类, 与yml数据库对应. 可根据业务需求, 自定义增加相应的值
/**
* 数据源
*
* @author xyang
*/
public enum DataSourceType
{
/**
* 主数据源
*/
MASTER,
/**
* 从数据源
*/
SLAVE
}
【DynamicDataSource.java】
数据源切换处理类. 用于记录与切换数据源
public class DynamicDataSource extends AbstractRoutingDataSource
{
public static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey()
{
return getDataSourceType();
}
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType)
{
log.info("切换到{}数据源", dsType);
contextHolder.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType()
{
return contextHolder.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType()
{
contextHolder.remove();
}
}
【DruidConfig.java】
druid 配置多数据源, 设置动态数据源
@Configuration
public class DruidConfig
{
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.druid.slave")
@ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dataSource(DataSource masterDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
return new DynamicDataSource(masterDataSource, targetDataSources);
}
/**
* 设置数据源
*
* @param targetDataSources 备选数据源集合
* @param sourceName 数据源名称
* @param beanName bean名称
*/
public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
{
try
{
DataSource dataSource = SpringUtils.getBean(beanName);
targetDataSources.put(sourceName, dataSource);
}
catch (Exception e)
{
}
}
}
注意事项: ConfigurationProperties中的值需与yml文件中的路径相对应
【DataSource.java】
自定义多数据源切换注解
优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.MASTER;
}
【DataSourceAspect.java】
多数据源处理. 设置切点, 根据上面定义的DataSource注解, 动态切换数据源
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.kyx.orderSys.base.annotation.DataSource)"
+ "|| @within(com.kyx.orderSys.base.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (StringUtils.isNotNull(dataSource)) {
DynamicDataSource.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSource.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
【注解的使用】
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
@DataSource(DataSourceType.SLAVE)
public IPage<User> getFastUsers(Page<User> page, Wrapper<User> queryWrapper) {
return baseMapper.selectPage(page, queryWrapper);
}
@Override
@DataSource(DataSourceType.SLAVE)
public boolean removeById(Serializable id) {
return super.removeById(id);
}
// 此处@DataSource注解可省略
@Override
@DataSource(DataSourceType.MASTER)
public boolean updateById(User entity) {
return super.updateById(entity);
}
}
【注意事项】
使用druid多数据源时,项目启动出现数据源循环依赖
解决办法: sprintboot 低版本可以使用 spring.datasource.initialize=false (默认为true) 来解决,升级到sprintboot2.1.2后就发现被弃用了, 可在启动类上增加这个exclude = { DataSourceAutoConfiguration.class} 属性即可
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})
@MapperScan("com.kyx.orderSys.biz.**.mapper")
public class CyyApplication {
public static void main(String[] args) {
SpringApplication.run(CyyApplication.class, args);
}
}