一、添加MyBatis-Plus依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.14</version>
</dependency>
二、设置数据源配置
server:
port: 8081
spring:
datasource:
master:
url: jdbc:mysql://139.196.240.76:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
slave1:
url: jdbc:mysql://139.196.240.76:3307/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
master为主库,负责增删改;slave1为从库,负责查询。
三、编写mybatis-plus配置类
/**
* 数据源配置
*
* @author PlumRoc
* @date 2022-11-01
*/
@Configuration
public class DataSourceConfig {
/**
* 主数据源 (可读可写的主数据源)
*/
@Primary
@Bean(name = "masterDataSource")
@Qualifier("masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
//指定连接池类型-DruidDataSource
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
/**
* 从数据源1(只读从数据源1)
*/
@Bean(name = "slave1DataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource salve1DataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
/**
* 动态数据源
*
* @param masterDataSource 可读可写主数据源
* @param slave1DataSource 只读子数据源1
*/
@Bean(name = "dynamicDataSource")
public DataSource createDynamicDataSource(
@Qualifier(value = "masterDataSource") final DataSource masterDataSource,
@Qualifier(value = "slave1DataSource") final DataSource slave1DataSource) {
//将所有数据源放到Map中
Map<Object, Object> targetDataSources = new HashMap<>(4);
targetDataSources.put(DataSourceTypeEnum.MASTER, masterDataSource);
targetDataSources.put(DataSourceTypeEnum.SLAVE1, slave1DataSource);
//动态数据源
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
//设置可通过路由key,切换的数据源Map集
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
/**
* @author PlumRoc
*/
@Configuration
@MapperScan("com.plumroc.springbootdatasource.mapper")
public class MyBatisPlusConfig {
@Resource
private DataSource dynamicDataSource;
/**
* sqlSessionFactory 配置
*
* @return SqlSessionFactory
* @throws Exception
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
//设置动态数据源
sqlSessionFactory.setDataSource(dynamicDataSource);
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
//是否使用转驼峰
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
sqlSessionFactory.setConfiguration(configuration);
//添加分页功能
sqlSessionFactory.setPlugins(mybatisPlusInterceptor());
//扫描 mapper 路径
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml"));
return sqlSessionFactory.getObject();
}
/**
* 事务管理
*/
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource);
}
/**
* MyBatisPlus分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
四、AOP切面
/**
* 目标数据源注解
* 注:@Order(-1)是为了保证该AOP在@Transactional之前执行
*
* @author PlumRoc
* @date 2022-11-01
*/
@Aspect
@Order(-1)
@Slf4j
@Component
public class DynamicDataSourceAspect {
/**
* 拦截使用 @DataSourceSwitcher注解的方法,前置设置数据源
*
* @param point
* @param ds
*/
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, DataSourceSwitcher ds) {
log.info("[ set DataSource ] >> {} > {}", ds.value(), point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.value());
}
/**
* 拦截使用 @DataSourceSwitcher注解的方法,后置-移除数据源
*
* @param point
* @param ds
*/
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, DataSourceSwitcher ds) {
log.info("[ remove DataSource ] >> {} > {}", ds.value(), point.getSignature());
DynamicDataSourceContextHolder.removeDataSourceType();
}
}
五、自定义注解
/**
* 目标数据源注解-作用于方法上
*
* @author PlumRoc
* @date 2022-11-01
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceSwitcher {
/**
* 目标数据源枚举名称
*/
DataSourceTypeEnum value();
}
六、扩展动态
/**
* 扩展动态-数据源
*
* @author PlumRoc
* @date 2022-11-01
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 通过路由Key切换数据源
* <p>
* spring 在开始进行数据库操作时会通过这个方法来决定使用哪个数据库,
* 因此我们在这里调用 DynamicDataSourceContextHolder.getDataSourceType()方法获取当前操作类别,
* 同时可进行读库的负载均衡
*/
@Override
protected Object determineCurrentLookupKey() {
DataSourceTypeEnum typeEnum = DynamicDataSourceContextHolder.getDataSourceType();
log.info("[ Change data source ] >> " + typeEnum.name());
return typeEnum;
}
}
/**
* 通过ThreadLocal将数据源设置到每个线程上下文中
* 用于切换读/写模式数据源
* 原理:
* 1.利用ThreadLocal保存当前线程数据源模式
* 2.操作结束后清除该数据,避免内存泄漏,同时也为了后续在该线程进行写操作时任然为读模式
*
* @author PlumRoc
* @date 2022-11-01
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<DataSourceTypeEnum> CONTEXT_HOLDER = new ThreadLocal<>();
public static void setDataSourceType(DataSourceTypeEnum dataSourceType) {
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源路由key
* 默认主库
*/
public static DataSourceTypeEnum getDataSourceType() {
return CONTEXT_HOLDER.get() == null ? DataSourceTypeEnum.MASTER : CONTEXT_HOLDER.get();
}
public static void removeDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
七、测试
/**
* @author PlumRoc
* @date 2022-11-01
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserService {
@Resource
private UserInfoMapper userInfoMapper;
/**
* 分页查询
*
* @return 查询结果
*/
@Override
@DataSourceSwitcher(DataSourceTypeEnum.SLAVE1)
public Page<UserInfo> queryByPage(Page<UserInfo> page) {
log.info("[ 分页查询列表 ] start page:{}", page);
this.baseMapper.selectPage(page, null);
log.info("[ 分页查询列表 ] end page:{}", page);
return page;
}
/**
* 新增人员
* 指定使用主库
*
* @param addBO
*/
@Override
@DataSourceSwitcher(DataSourceTypeEnum.MASTER)
public void addUser(UserAddBO addBO) {
log.info("[ 新增人员 ] start param:{}", addBO);
String userId = UUID.randomUUID().toString().replaceAll("-", "");
UserInfo userInfo = new UserInfo();
userInfo.setUserId(userId);
userInfo.setUserName(addBO.getUserName());
userInfo.setRealName(addBO.getRealName());
userInfo.setMobile(addBO.getMobile());
userInfo.setCreateTime(new Date());
userInfo.setUpdateTime(new Date());
userInfo.setRemark(addBO.getRemark());
//demo项目部分参数值写死
userInfo.setUserPassword("123456");
userInfo.setDelFlag(0);
userInfoMapper.insert(userInfo);
log.info("[ 新增人员 ] end userId:{},userName:{}", userId, addBO.getUserName());
}
/**
* 修改人员信息
*
* @param updateBO
*/
@Override
@DataSourceSwitcher(DataSourceTypeEnum.MASTER)
public void updateUser(UserUpdateBO updateBO) {
log.info("[ 修改人员信息 ] start param:{}", updateBO);
UserInfo userInfo = new UserInfo();
userInfo.setUserId(updateBO.getUserId());
userInfo.setUserName(updateBO.getUserName());
userInfo.setRealName(updateBO.getRealName());
userInfo.setMobile(updateBO.getMobile());
userInfo.setCreateTime(new Date());
userInfo.setUpdateTime(new Date());
userInfo.setRemark(updateBO.getRemark());
userInfoMapper.updateByUserIdSelective(userInfo);
log.info("[ 修改人员信息 ] end userId:{},userName:{}", updateBO.getUserId(), updateBO.getUserName());
}
/**
* 查询所有 人员信息
*
* @return 人员信息 列表
*/
@Override
@DataSourceSwitcher(DataSourceTypeEnum.SLAVE1)
public List<UserInfo> getAll() {
log.info("[ 查询所有人员列表 ] 指定使用从库1 ");
return userInfoMapper.getAll();
}
/**
* 根据业务主键ID查询
*
* @param userId 业务主键
*/
@Override
@DataSourceSwitcher(DataSourceTypeEnum.SLAVE1)
public UserInfo getByUserId(String userId) {
log.info("[ 根据业务主键ID查询 ] 指定使用从库1 userId:{}", userId);
return userInfoMapper.getByUserId(userId);
}
}