MyBatis-Plus实现代码层面的读写分离

一、添加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);
    }
}

源码

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis-Plus 是一个在 MyBatis 基础上进行增强的持久层框架,提供了很多方便开发的功能和工具。关于 MyBatis-Plus读写分离,可以通过配置动态数据源和使用 MyBatis-Plus 提供的注解来实现读写分离是指将数据库的读操作和写操作分别在不同的数据库实例上进行,以提高系统的并发能力和性能。在 MyBatis-Plus 中,可以通过使用多个数据源来实现读写分离。 首先,需要配置多个数据源,一个用于读操作,一个用于写操作。可以使用 Spring Boot 提供的配置方式,或者使用 MyBatis-Plus 提供的 DynamicDataSource 动态数据源。 然后,在需要进行读操作的方法上,可以使用 MyBatis-Plus 提供的 @Slave 注解,指定使用读数据源。例如: ```java @Slave public List<User> getUserList() { // ... } ``` 在需要进行写操作的方法上,则不需要特别指定数据源,默认会使用主数据源。 最后,在 MyBatis-Plus 的配置文件中,需要配置动态数据源的切换策略。可以通过使用 AbstractRoutingDataSource 类来实现切换策略,根据方法上的注解来决定使用哪个数据源。 这样配置之后,当调用带有 @Slave 注解的方法时,MyBatis-Plus 会自动切换到读数据源;调用其他方法时,会使用写数据源。从而实现读写分离的功能。 需要注意的是,读写分离的配置还涉及到数据库的主从同步和数据一致性等问题,在配置过程中需要综合考虑这些因素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PerryLes

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值