在实际工作中,我们的系统可能是多个数据源,通过集成MyBatis-Plus和HikariCP实现多数据源动态切换是一个常见的需求。以下是一个详细的实现步骤,你可以根据自己的需要进行调整。
1. 添加依赖
首先,在pom.xml文件中添加所需的依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
HikariCP数据源不需要额外导包,springboot自动引入
2. 配置数据源属性
在application.yml文件中配置多个数据源属性:

3. 配置数据源
创建一个配置类来配置多个数据源:
package com.work.config;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.work.enums.DatabaseTypeEnum;
import com.zaxxer.hikari.HikariDataSource;
/**
* springboot整合mybatis-plus 实现多数据源动态切换
* 配置数据源和事务管理器
* @author summer
*/
@Configuration
@MapperScan(basePackages = "com.work.mapper", sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
/**
* 主库数据源
* @return
*/
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* 从库数据源
* @return
*/
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* 动态切换数据源
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Primary
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseTypeEnum.MASTER.getDatabaseType(), masterDataSource);
targetDataSources.put(DatabaseTypeEnum.SLAVE.getDatabaseType(), slaveDataSource);
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
//设置默认的数据源,当没有指定数据源时,将使用主库为默认数据源
dataSource.setDefaultTargetDataSource(masterDataSource);
//将配置好的多数据源设置到DynamicRoutingDataSource中
dataSource.setTargetDataSources(targetDataSources);
return dataSource;
}
/**
* 配置sqlSession
* @param dataSource
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dataSource) throws Exception {
// 导入mybatissqlsession配置
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
// 指明数据源
sessionFactory.setDataSource(dataSource);
//多数据源必须在定义数据源时添加以下2句,否则分页插件无效
Interceptor[] interceptors = new Interceptor[] { mybatisPlusInterceptor() };
sessionFactory.setPlugins(interceptors);
// 返回SqlSessionFactory实例,用于创建SqlSession,使用SqlSession调用sql时不需要指定数据源,因为就在此处指定了
return sessionFactory.getObject();
}
/**
* 配置事务管理器
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
/**
* mybatis-Plus 分页配置
* 注意:如果是单数据源,此时分页插件能正常使用,而如果是多数据源,那么还需在配置SqlSessionFactory的地方再添加配置,否则分页时查询总数total还是0
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor=new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor=new PaginationInnerInterceptor();
paginationInnerInterceptor.setDbType(DbType.MYSQL);
paginationInnerInterceptor.setOverflow(true);
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
return mybatisPlusInterceptor;
}
}
4.定义数据库类型枚举
package com.work.enums;
/**
* 数据库类型枚举
* @author summer
*
*/
public enum DatabaseTypeEnum {
MASTER("master", "主库"),
SLAVE("slave", "从库"),
;
private final String databaseType;
private final String msg;
DatabaseTypeEnum(String databaseType, String msg) {
this.databaseType = databaseType;
this.msg = msg;
}
public String getDatabaseType() {
return databaseType;
}
public String getMsg() {
return msg;
}
}
5.动态切换数据源
使用AOP或者注解的方式实现数据源的动态切换
package com.work.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import com.work.config.DynamicRoutingDataSource;
import com.work.enums.DatabaseTypeEnum;
/**
* AOP 实现动态数据源切换
* @author summer
*
*/
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(com.work.target.ReadOnly)")
public void useSlaveDataSource() {
DynamicRoutingDataSource.setDataSource(DatabaseTypeEnum.SLAVE.getDatabaseType());
}
@Before("@annotation(com.work.target.WriteOnly)")
public void useMasterDataSource() {
DynamicRoutingDataSource.setDataSource(DatabaseTypeEnum.MASTER.getDatabaseType());
}
//需要每次清空ThreadLocal的内容,防止内存泄漏
@After("@annotation(com.work.target.ReadOnly) || @annotation(com.work.target.WriteOnly)")
public void clearDataSource() {
DynamicRoutingDataSource.clearDataSource();
}
}
创建自定义注解:
package com.work.target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 读数据标识注解
* @author summer
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnly {
}
package com.work.target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 写数据标识注解
* @author summer
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteOnly {
}
实现DynamicRoutingDataSource类来管理数据源切换:
package com.work.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态主从数据源
* 使用 ThreadLocal来持有当前线程的数据源类型
* @author summer
*
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource{
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return CONTEXT_HOLDER.get();
}
}
6.编写测试类
实体类:
package com.work.model.db;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName(value = "check_code_info")
public class CheckCodeInfo {
/**
* 主键ID 自增长
*/
@TableId(type = IdType.AUTO)
private Integer id;
private String token;
private String checkCode;
/**
* 使用状态1:未使用2:已使用
*/
private Integer status;
/**
* 创建时间
*/
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新时间
*/
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 过期时间
*/
// @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date expiTime;
}
mapper类:
package com.work.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.work.model.db.CheckCodeInfo;
public interface CheckCodeInfoMapper extends BaseMapper<CheckCodeInfo>{
}
Service类:
package com.work.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.work.model.db.CheckCodeInfo;
public interface CheckCodeInfoService {
/**
* 读数据
* @param
* @return
*/
IPage<CheckCodeInfo> getCheckCodeInfoList(int currentPage, int pageSize);
/**
* 添加数据
*/
boolean addCheckCodeInfo();
}
Service实现类:
package com.work.service.impl;
import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.work.mapper.CheckCodeInfoMapper;
import com.work.model.db.CheckCodeInfo;
import com.work.service.CheckCodeInfoService;
import com.work.target.ReadOnly;
import com.work.target.WriteOnly;
import cn.hutool.core.util.RandomUtil;
@Service
public class CheckCodeInfoServiceImpl implements CheckCodeInfoService {
@Autowired
private CheckCodeInfoMapper checkCodeInfoMapper;
/**
* 测试分页查询
* 读数据注解
*/
@ReadOnly
@Override
public IPage<CheckCodeInfo> getCheckCodeInfoList(int currentPage, int pageSize) {
// LambdaQueryWrapper<CheckCodeInfo> lambdaQueryWrapper=new LambdaQueryWrapper<CheckCodeInfo>();
//分页参数 mybatis-plus自带的分页,传入当前页码和每页条数
Page<CheckCodeInfo> page=new Page<CheckCodeInfo>(currentPage,pageSize);
//调用Mapper的分页查询方法,传入Page对象和查询条件(这里为null表示无条件查询)
IPage<CheckCodeInfo> pageResult=checkCodeInfoMapper.selectPage(page, null);
// 从 从库查询数据
return pageResult;
}
/**
* 测试写数据
* 写数据注解
*/
@WriteOnly
@Override
public boolean addCheckCodeInfo() {
CheckCodeInfo checkCodeInfo = new CheckCodeInfo();
checkCodeInfo.setToken(RandomUtil.randomString(30));
checkCodeInfo.setCheckCode(RandomUtil.randomNumbers(3));
checkCodeInfo.setCreateTime(new Date());
checkCodeInfo.setUpdateTime(new Date());
int num = checkCodeInfoMapper.insert(checkCodeInfo);
// 写入主库
return num > 0 ? true : false;
}
}
测试Controller类:
package com.work.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.work.common.result.CommonResult;
import com.work.model.db.CheckCodeInfo;
import com.work.service.CheckCodeInfoService;
/**
* 动态切换多数据源,测试Controller类
* @author summer
*
*/
@RestController
@RequestMapping("/checkCode")
public class DataSourceTestController {
@Autowired
private CheckCodeInfoService checkCodeInfoService;
/**
* 测试数据源动态切换,从 从库读取数据
* @return
*/
@GetMapping("/getCheckCodeInfoList")
public CommonResult<IPage<CheckCodeInfo>> getCheckCodeInfoList(@RequestParam(defaultValue = "1") int currentPage,
@RequestParam(defaultValue = "10") int pageSize) {
return CommonResult.success(checkCodeInfoService.getCheckCodeInfoList(currentPage,pageSize));
}
/**
* 测试数据源动态切换,从 从库读取数据
* @return
*/
@PostMapping("/addCheckCodeInfo")
public CommonResult<Boolean> addCheckCodeInfo() {
return CommonResult.success(checkCodeInfoService.addCheckCodeInfo());
}
}
postMan测试:

主库:

测试从库查询:

从库:

7.控制台结果,也能看到确实使用了HikariCP的数据库连接池,这样就大公告成了
![]()



4702

被折叠的 条评论
为什么被折叠?



