SpringBoot结合mybatis使用拦截器进行多数据源动态切换

读写分离实现自动切换数据库(基于mybatis拦截器)_weixin_34378922的博客-CSDN博客

基于楼上此贴进行的开发

重要文件1 DynamicDataSourceHolder 

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DynamicDataSourceHolder {

    /**
     * 线程安全的本地线程类
     */
    private static ThreadLocal<String> contextHolder = new ThreadLocal<String>();

    /**
     * 主数据库
     */
    public static final String DB_MASTER = "master";

    /**
     * 从库
     */
    public static final String DB_SLAVE = "slave";

    /**
     * 获取数据源
     * @return
     */
    public static String getDbType(){
        String db = contextHolder.get();
//        log.debug("getDbType方法中从线程安全的里面获取到:" + db);
        if (db == null){
            db = DB_MASTER;
        }
        return db;
    }

    /**
     * 注入线程的数据源
     * @param str
     */
    public static void setDbType(String str){
//        log.debug("所注入使用的数据源:" + str);
        contextHolder.set(str);
    }

    /**
     * 清理连接
     */
    public static void clearDBType(){
        contextHolder.remove();
    }
}

重要文件2 DynamicDataSourceInterceptor

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.Locale;
import java.util.Properties;


//@Intercepts标记了这是一个Interceptor,然后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型
// 为MappedStatement、Object、RowBounds和ResultHandler的query方法;第二个@Signature我们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。
//   只要拦截了update和query即可,所有的增删改查都会封装在update和query中
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
@Slf4j
public class DynamicDataSourceInterceptor implements Interceptor {

    /**
     * 判断是插入还是增加还是删除之类的正则, u0020是空格
     */
    private static final String regex = ".*insert\\u0020.*|.*delete\\u0020.*|.update\\u0020.*";

    /**
     * 我们要操作的主要拦截方法,什么情况下去拦截,就看这个了
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //判断当前是否有实际事务处于活动状态 true 是
        boolean synchronizationActive = TransactionSynchronizationManager.isActualTransactionActive();
        //获取sql的资源变量参数(增删改查的一些参数)
        Object[] objects = invocation.getArgs();
        //MappedStatement 可以获取到到底是增加还是删除 还是修改的操作
        MappedStatement mappedStatement = (MappedStatement) objects[0];
        //用来决定datasource的
        String lookupKey = DynamicDataSourceHolder.DB_MASTER;

        if (synchronizationActive != true){
            //当前的是有事务的====================Object[0]=org.apache.ibatis.mapping.MappedStatement@c028cc
//            log.debug("当前的是有事务的====================Object[0]=" + objects[0]);
            //读方法,说明是 select 查询操作
            if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)){
                //如果selectKey 为自增id查询主键(select last_insert_id()方法),使用主库,这个查询是自增主键的一个查询
                if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)){
                    //使用主库
                    lookupKey = DynamicDataSourceHolder.DB_MASTER;
                } else {
                    //获取到绑定的sql
                    BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                    String sqlstr = boundSql.getSql();
                    //toLowerCase方法用于把字符串转换为小写,replaceAll正则将所有的制表符转换为空格
                    String sql = sqlstr.toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                    //sql是这个===================:select top 1 * from cipentinfo where regno=?
//                    log.debug("sql是这个===================:" + sql);

                    //使用sql去匹配正则,看他是否是增加、删除、修改的sql,如果是则使用主库
                    if (sql.matches(regex)){
                        lookupKey = DynamicDataSourceHolder.DB_MASTER;
                    } else {
                        //从读库(从库),注意,读写分离后一定不能将数据写到读库中,会造成非常麻烦的问题
                        lookupKey = DynamicDataSourceHolder.DB_SLAVE;
                    }
                }
            }
        } else {
            //非事务管理的用主库
            lookupKey = DynamicDataSourceHolder.DB_MASTER;
        }

        //设置方法[slave] ues [SELECT] Strategy,SqlCommanType[SELECT],sqlconmantype[{}]......................... cipentinfoNamespace.selectOneByRegNo
//        log.debug("设置方法[{}] ues [{}] Strategy,SqlCommanType[{}],sqlconmantype[{}]......................... " + mappedStatement.getId(), lookupKey, mappedStatement.getSqlCommandType().name(), mappedStatement.getSqlCommandType());
        //最终决定使用的数据源
        DynamicDataSourceHolder.setDbType(lookupKey);
        return invocation.proceed();
    }

    /**
     * 返回封装好的对象,决定返回的是本体还是编织好的代理
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        //Executor是mybatis的,所有的增删改查都会经过这个类
        if (target instanceof Executor){
            //如果是Executor 那就进行拦截
            return Plugin.wrap(target, this);
        } else {
            //否则返回本体
            return target;
        }
    }

    /**
     * 类初始化的时候做一些操作
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {

    }

第三步.设置拦截器

一
@Import({DynamicDataSourceHolder.class,DynamicDataSourceInterceptor.class})

@Autowired
private DynamicDataSourceInterceptor dynamicDataSourceInterceptor;

 

sqlSessionFactoryBean.setPlugins(new Interceptor[]{dynamicDataSourceInterceptor});

大功告成 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SpringBoot是一个高效的Java开发框架,它能够方便开发者集成MyBatis-Plus实现多数据源动态切换以及支持分页查询。MyBatis-Plus是一种优秀的ORM框架,它增强了MyBatis的基础功能,并支持通过注解方式进行映射。 首先,我们需要在pom.xml文件中添加MyBatis-Plus和数据库连接池的依赖。在application.yml文件中,我们需要配置多个数据源和对应的连接信息。我们可以定义一个DataSourceConfig用于获取多个数据源,然后在Mapper配置类中使用@MapperScan(basePackages = {"com.test.mapper"})来扫描Mapper接口。 要实现动态切换数据源,我们可以自定义一个注解@DataSource来标注Mapper接口或方法,然后使用AOP拦截数据源切换,实现动态切换。在实现分页查询时,我们可以使用MyBatis-Plus提供的分页插件来支持分页查询。 代码示例: 1. 在pom.xml文件中添加MyBatis-Plus和数据库连接池的依赖。 ``` <dependencies> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.4</version> </dependency> </dependencies> ``` 2. 在application.yml文件中配置多个数据源和对应的连接信息。以两个数据源为例: ``` spring: datasource: druid: db1: url: jdbc:mysql://localhost:3306/db1 username: root password: root driver-class-name: com.mysql.jdbc.Driver db2: url: jdbc:mysql://localhost:3306/db2 username: root password: root driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 指定默认数据源 primary: db1 ``` 3. 定义一个DataSourceConfig用于获取多个数据源。 ``` @Configuration public class DataSourceConfig { @Bean("db1") @ConfigurationProperties("spring.datasource.druid.db1") public DataSource dataSource1() { return DruidDataSourceBuilder.create().build(); } @Bean("db2") @ConfigurationProperties("spring.datasource.druid.db2") public DataSource dataSource2() { return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DataSource dataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); // 设置数据源映射关系 Map<Object, Object> dataSourceMap = new HashMap<>(); dataSourceMap.put("db1", dataSource1()); dataSourceMap.put("db2", dataSource2()); dynamicDataSource.setTargetDataSources(dataSourceMap); // 设置默认数据源 dynamicDataSource.setDefaultTargetDataSource(dataSource1()); return dynamicDataSource; } } ``` 4. 在Mapper配置类中使用@MapperScan(basePackages = {"com.test.mapper"})来扫描Mapper接口,并使用@DataSource注解来标注Mapper接口或方法。 ``` @Configuration @MapperScan(basePackages = {"com.test.mapper"}) public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } } @DataSource("db1") public interface UserMapper { @Select("select * from user where id = #{id}") User selectById(@Param("id") Long id); } ``` 5. 实现AOP拦截数据源切换。 ``` @Aspect @Component public class DataSourceAspect { @Before("@annotation(ds)") public void beforeSwitchDataSource(JoinPoint point, DataSource ds) { String dataSource = ds.value(); if (!DynamicDataSourceContextHolder.containDataSourceKey(dataSource)) { System.err.println("数据源 " + dataSource + " 不存在,使用默认数据源"); } else { System.out.println("使用数据源:" + dataSource); DynamicDataSourceContextHolder.setDataSourceKey(dataSource); } } } ``` 6. 分页查询的使用示例: ``` @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override @DataSource("db1") public IPage<User> getUserList(int pageNum, int pageSize) { Page<User> page = new Page<>(pageNum, pageSize); return userMapper.selectPage(page, null); } } ``` 以上就是SpringBoot整合MyBatis-Plus实现多数据源动态切换和分页查询的具体实现过程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值