SpringBoot集成Mybatis多数据源实现分表分库

springboot+mybatis实现mysql和oracle多数据源 在实际项目中很多时候会涉及到多个数据库的访问,或者数据库读写分离的形式。下面通过使用 Aspect+注解来实现mysql+oracle的多数据源配置(注意:事务一致性未提供)首先要去oracle官网下载ojdbc的jar包,根据oracle的版本去下载,或者在下载的oracle的jdbc包下的lib里面有,然后导入项目中!!!动态数据源流程说明Spring Boot 的动态数据源,本质上是把多个数据源存储在一个 Map 中,当需要使用某个数据源时,从 Map 中获取此数据源进行处理。而在 Spring 中, 阅读详情
AI摘要

博客围绕项目开发中数据量增大时的分库分表问题展开。介绍了使用自定义注解实现多数据源切换、包路径隔离两种分库方法,以及用MyBatis拦截器Interceptor实现分表查询。详细说明了MyBatis拦截器的作用、核心对象,还给出简单使用示例,最后总结测试中的疑问待后续研究。

1.在项目开发过程中,随着项目不断的更新迭代,数据量越来越大,项目越来重,为了提高接口响应速度以及缓解数据库压力,可能会涉及到分库分表,分库的概念比较明确,集群、主从复制等,将修改和更新数据的操作全部由主库处理,查询数据由从库处理。分表有垂直分表和水平分表,垂直分表是将列按照业务相关以及使用频繁程度来进行划分。水平分表就是多个相同的表,根据用户相关信息使用算法或者取模等规则,决定当前数据处于那个表中。
2.多法人的实现场景也可以见解分表分库思想实现,缺点就是成本较大,每新增一个法人就得新加一个数据库。

一、使用自定义注解实现多数据源切换

  1. 话不多说,直接上项目。新建SpringBoot项目,引入mybatis-plus和mysql依赖。
  2. pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.explame</groupId>
    <artifactId>datasourcedemo</artifactId>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.8</version>

        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--mybatis plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.4</version>

        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>com.github.caspar-chen</groupId>
            <artifactId>swagger-ui-layer</artifactId>
            <version>1.1.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!-- 打包跳过测试 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
                <configuration>
                    <skipTests>true</skipTests>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

3.yam文件配置

spring:
  datasource:
    db1:
      driverClassName: com.mysql.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/bus?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
      username: 用户名
      password: 数据库密码
    db2:
      driverClassName: com.mysql.jdbc.Driver
      jdbcUrl: jdbc:mysql://localhost:3306/nacos?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
      username: 用户名
      password: 数据库密码
server:
  port: 8081
  1. 新建数据源配置类
package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 数据源配置类
 */
@Configuration
public class MyDataSourceConfig {
    @Bean(name = "db1")
    @ConfigurationProperties(prefix = "spring.datasource.db1")
    public DataSource dataSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource dataSource2() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 配置自定义的类DynamicDataSource,这里是把两个(多个)数据源放到DynamicDataSource中,
     * 同时设置一个默认的数据源
     */
    @Bean("dynamicDataSource")
    public MyDataSource dynamicDataSource(@Qualifier("db1") DataSource db1,
                                          @Qualifier("db2") DataSource db2) {
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put("db1", db1);
        targetDataSource.put("db2", db2);
        MyDataSource myDataSource = new MyDataSource();
        myDataSource.setTargetDataSources(targetDataSource);
        myDataSource.setDefaultTargetDataSource(db1);
        return myDataSource;
    }

    /**
     * 把动态数据源放到SqlSessionFactory
     * 
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("db1") DataSource db1,
                                               @Qualifier("db2") DataSource db2) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource(db1, db2));
        //没有xml文件时会报错
      //  sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }
}

5.继承Spring的抽象类AbstractRoutingDataSource ,并重新determineCurrentLookupKey方法,决定Spring容器连接那个数据源

package com.example.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 扩展 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法
 * 动态数据源
 * determineCurrentLookupKey() 方法决定使用哪个数据源
 */
@Slf4j
public class MyDataSource extends AbstractRoutingDataSource {
	//使用线程局部缓存存储数据源信息,保证每个线程互不影响
    public static final ThreadLocal<String> holder = new ThreadLocal<>();

    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    public static void setDataSource(String type) {
        holder.set(type);
    }

    public static String getDataSource(){
        return holder.get();
    }

    public static void removeDataSource(){
        holder.remove();
    }
}

6.新建自定义注解,并使用Aspect对注解进行拦截

package com.example.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSourceNote {
    String value() default "";
}
package com.example.aspect;

import com.example.config.MyDataSource;
import com.example.util.DataSourceNote;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class MyDataSourceAspect {

    @Pointcut("execution(* com.example.mapper.*.*(..))")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        DataSourceNote dataSourceNote = method.getAnnotation(DataSourceNote.class);
        if (dataSourceNote != null) {
            String value = dataSourceNote.value();
            MyDataSource.setDataSource(value);
        }
        try {
            return joinPoint.proceed();
        } finally {
            //操作完成,移除指定数据源,还原默认数据源
            MyDataSource.removeDataSource();
        }
    }
}

7.新建mapper文件,创建接口,进行测试

package com.example.mapper;

import com.example.util.DataSourceNote;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface UserMapper {
    @Select("select name from student where id=1")
    @DataSourceNote("db1")
    String getFristDbUserName();
    @DataSourceNote("db2")
    @Select("select name from student where id=2")
    String getSecondDbUserName();
}

8.在两个数据库中分别创建student表。调用接口进行测试。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
测试成功啦!!!!!!!
10.上述写两个方法只是为了给大家更好的展示效果,实际开发过程中肯定不能这么写,肯定是使用用户信息或者当前操作的类型来决定使用那个数据源,这里就不给大家演示了,自行研究吧。

二、使用包路径隔离

  1. 这种方法跟方法一原理一致,区别在于方法一是在使用过程中进行切换,而方法二是项目启动就决定了,该方法开发成本更大,不予推荐,不做过多介绍,贴上代码。
    在这里插入图片描述
    2.分别新建数据源配置类,指定其扫描包路径即可
package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.io.IOException;

@Configuration
@MapperScan(basePackages = "com.example.othermapper", sqlSessionTemplateRef  = "secondSqlSessionTemplate")
public class OtherDataSourceConfig {

    @Bean("seconddatesource")
    @ConfigurationProperties(prefix = "spring.datasource.second")
    public DataSource createDataSource(){
        return DataSourceBuilder.create().build();
    }
    @Bean("secondSqlSessionFactory")
    public SqlSessionFactory secondSqlSessionFactory(@Qualifier("seconddatesource") DataSource oterdatesource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(oterdatesource);
       // bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:othermapper/*.xml"));
        return bean.getObject();
    }

    @Bean(name = "secondTransactionManager")
    public DataSourceTransactionManager secondTransactionManager( @Qualifier("seconddatesource") DataSource otherDataSource) {
        return new DataSourceTransactionManager(otherDataSource);
    }

    @Bean(name = "secondSqlSessionTemplate")
    public SqlSessionTemplate secondSqlSessionTemplate(@Qualifier("secondSqlSessionFactory") SqlSessionFactory otherSqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(otherSqlSessionFactory);
    }

}

package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
 
import javax.sql.DataSource;
 
@Configuration
@MapperScan(basePackages = "com.example.mapper", sqlSessionTemplateRef  = "primarySqlSessionTemplate")
public class FirstDataSourceConfig {
 
    @Bean(name = "primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.primary")
    @Primary
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory primarySqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //mapper下面没有文件时会报错
      //  bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return bean.getObject();
    }
 
    @Bean(name = "primaryTransactionManager")
    @Primary
    public DataSourceTransactionManager testTransactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
 
    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
 
}

三、使用Mybatis拦截器Interceptor实现分表查询

1.Mybatis拦截器Interceptor介绍

  1. MyBatis允许使用者在映射语句执行过程中的某一些指定的节点进行拦截调用,通过织入拦截器,在不同节点修改一些执行过程中的关键属性,从而影响SQL的生成、执行和返回结果,如:来影响Mapper.xml到SQL语句的生成、执行SQL前对预编译的SQL执行参数的修改、SQL执行后返回结果到Mapper接口方法返参POJO对象的类型转换和封装等。
    根据上面的对Mybatis拦截器作用的描述,可以分析其可能的用途;最常见的就是Mybatis自带的分页插件PageHelper或Rowbound参数,通过打印实际执行的SQL语句,发现我们的分页查询之前,先执行了COUNT(*)语句查询数量,然后再执行查询时修改了SQL语句即在我们写的SQL语句后拼接上了分页语句LIMIT(offset, pageSize);
    2.Mybatis核心对象介绍
    Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
    SqlSessionFactory SqlSession工厂
    SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
    Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
    StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
    ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
    ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
    TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
    MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
    SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回BoundSql 表示动态生成的SQL语句以及相应的参数信息

2.简单使用

  1. 知道它有什么用了以后,就知道能帮我们做什么。最经典的应用场景就是分页插件,说白了就是重写sql。同样的道理,除了分页以外会不会有其他需求场景?比如对数据增删改查的时候默认要拼接一个逻辑删除的字段,亦或是给表起别名,或者更改表名等。本章节主要测试更改表名和拼接其他字段查询的效果。话不多说直接上代码。
  2. 首先实现Mybatis拦截器,
package com.example.config;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitor;
import com.example.entiy.UserDto;
import com.example.util.UserLocalUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})),
        }
)
public class MyInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //args对应Signature注解里的args 
        Object[] args = invocation.getArgs();
        Executor executor = (Executor)invocation.getTarget();
        //这里对应Signature注解里的method 
        Method method = invocation.getMethod();
        MappedStatement mappedStatement = (MappedStatement)args[0];
        //args[2]这里面是我们sql的参数
        BoundSql boundSql = mappedStatement.getBoundSql(args[2]);
        String sql = boundSql.getSql();
        System.out.println("拦截前sql :" + sql);
        MySqlStatementParser mySqlStatementParser = new MySqlStatementParser(sql);
        SQLStatement statement =mySqlStatementParser.parseStatement();
        TableMySqlASTVisitorAdapter visitor = new TableMySqlASTVisitorAdapter();
        statement.accept(visitor);
        BoundSql bs = new BoundSql(mappedStatement.getConfiguration(),statement.toString(),boundSql.getParameterMappings(),args[1]);

        System.out.println("拦截后sql :" + statement);

        MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(bs));
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                bs.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        args[0] = newMs;

        return invocation.proceed();
    }
    /***
     * 这里不知道为啥要复制,还没有研究,
     * @param ms
     * @param newSqlSource
     * @return
     */
    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
   //重新构建sql要用的对象
    public static class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }
        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }
    }

    private void changeTableName(String sql){


    }
}

2.注册拦截器
可能有人看到这里会问为啥不用注解对Bean进行装载(呜呜呜,难受,当时用了另一种方法,死活注册不了,不知道为啥,也没找到原因,再说了活人还能让尿给憋死么,所以就换了一种方式,不过方法有很多,不限这一种)

package com.example.config;

import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Iterator;
import java.util.List;

@Component
public class InterceptorConfig implements ApplicationRunner {
    @Autowired
    private List<SqlSessionFactory> sqlSessionFactory;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        MyInterceptor myInterceptor = new MyInterceptor();
        Iterator<SqlSessionFactory> iterator = this.sqlSessionFactory.iterator();
        while (iterator.hasNext()){
            SqlSessionFactory sqlSessionFactory1 = (SqlSessionFactory)iterator.next();
            sqlSessionFactory1.getConfiguration().addInterceptor(myInterceptor);
        }
    }
}

3.实现durid的MySqlASTVisitorAdapter的相关方法,可以解析sql,添加字段等功能

package com.example.config;

import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLExistsExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSelectQueryBlock;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.example.entiy.UserDto;
import com.example.util.UserLocalUtil;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TableMySqlASTVisitorAdapter extends MySqlASTVisitorAdapter {
    public static final Map<String,String> tableMap=new HashMap<>();
    static {
        tableMap.put("1","_one");
    }
    @Override
    public boolean visit(SQLExprTableSource sqlExistsExpr){
    	//这里使用静态数据代替算法、取模等逻辑
        UserDto user = UserLocalUtil.getUser();
        String suffix = tableMap.get(user.getUserId());
        String tableName = sqlExistsExpr.getTableName();
        changeName((MySqlSelectQueryBlock) sqlExistsExpr.getParent());
        sqlExistsExpr.setExpr(sqlExistsExpr.getExpr()+suffix);
        return true;
    }
    private void changeName(MySqlSelectQueryBlock mySqlSelectQueryBlock){
        List<SQLSelectItem> selectList = mySqlSelectQueryBlock.getSelectList();
        mySqlSelectQueryBlock.addCondition("s.PHONE=13724889158");
    }

}

4.编写sql进行测试

在这里插入图片描述
在这里插入图片描述
sql修改成功

四.总结

  1. 需求是千变万化的,只有你想不到,没有做不到。在上述demo测试过程中有几个疑问点还没搞明白是什么原因,给大家罗列一下后续会更新,也欢迎大家留言。
  2. Signature注解里type和args里的属性值应该有什么关系,当时测试的时候当type=StatementHandler时会报错,调了好几次,由于时间原因没有研究
  3. 上述案例中有两个Signature,当我注释调一个的时候拦截器就拦截不到sql,这个问题回头再研究研究,可能是我哪里的属性写的不对。
  4. 案例写的比较简单,对很多东西也只是一知半解,有的地方可能写的不是很正确,欢迎大家指点
MyBatis-Plus 多数据源配置实战:从读写分离到分库分表​ 作为在分布式系统摸爬滚打七年的老兵,我深知多数据源场景是后端开发的必经之路。无论是读写分离优化性能,还是分库分表应对海量数据,亦或是整合第三方异构数据源,MyBatis-Plus 的多数据源配置都是核心解决方案。本文将通过银行核心系统的真实业务场景,带你从 0 到 1 掌握多数据源配置全流程,包含实战代码、坑点解析和最佳实践。 阅读详情

相关推荐

SpringBoot+Mybatis-plus多数据源实战:跨库操作避坑指南

本文详细介绍了SpringBoot与Mybatis-plus结合实现多数据源管理的实战指南,涵盖基础配置、动态数据源切换、事务管理及性能优化等关键点。特别针对跨库操作中的常见陷阱提供了解决方案,帮助开发者高效处理多数据库场景下的技术挑战。

weixin_29210727的博客 123

SpringMVC + MyBatis分库分表方案

  mybatis作为流行的ORM框架,项目实际使用过程中可能会遇到分库分表的场景。mybatis在分表,甚至是同主机下的分库都可以说是完美支持的,只需要将表名或者库名作为动态参数组装sql就能够完成。但是多余分在不同主机上的库,就不太一样了,组装sql无法区分数据库主机。网上搜索了一下,对于此类情况,大都采用的动态数据源的概念,也即定义不同的数据源连接不同的主机数据库,在查询前通过动...

weixin_30940783的博客 728

SpringBoot集成ShardingSphere实现多数据源动态路由

分库分表是应对单库性能瓶颈的核心架构策略,其本质是通过逻辑表与物理节点的映射关系,在SQL执行前完成数据路由。ShardingSphere作为主流分片中间件,提供JDBC嵌入式与Proxy代理两种形态,其中SpringBoot场景下首选ShardingSphere-JDBC——依托自动装配与零侵入特性,实现分片逻辑与业务代码解耦。该方案支持分库、分表、读写分离三重能力,技术价值在于提升吞吐、降低延迟、保障弹性扩展,广泛应用于高并发电商、教务、SaaS等系统。本文聚焦SpringBoot+ShardingSp

weixin_29574585的博客 172

Mybatis动态数据源-分库分表简单版实现

1、动态数据源【分库】 分库有两种选择方案: 用dao接口注解+切面实现动态数据源 利用表字段分库策略实现 此处采用第一种方案,第二种方案我将在分表是实现; 1.1 环境搭建 引入以下依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</arti

脚踏实地 2282

springboot+mybatis快速集成ShardingSphere实现分库分表

市面上实现分库分表的中间件有挺多的,这次我们说一说ShardingSphere集成到springboot的几个步骤, 其实也是很万金油的步骤哈哈。 1. 引入依赖 我们引入sharding-jdbc的springboot启动器 <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter&l

weixin_39702187的博客 3324

【SpringBoot笔记15】SpringBoot结合MyBatis实现多数据源配置

SqlSessionFactory就相当于是一个数据库,如果我们要配置多数据源,那就相当于是要在工程里面创建多个SqlSessionFactory对象,然后再使用的时候指定具体的SqlSessionFactory对象即可。注解,该注解作用是指定mapper接口所在的路径,并且这个注解中,也可以指定使用哪个【SqlSessionFactory】对象,只需要通过该注解的。在【com.spring.boot.demo.master.mapper】包下面,创建【EmpMapper】类。启动工程,浏览器访问【

✅ Java 开发工程师 1508

springboot+mybatis实现多数据源

目录前言准备环境建表语句代码项目结构依赖配置dao测试 前言 在一个项目体量不断增大的情况下,数据量随之增大,那么为了分散数据库的压力,可能会通过分库分表来进行减缓操作。这就与我们单一的数据源就不一样了,而是要配置多数据源了,本文就通过简单的示例,来示范多数据源实现的过程 准备环境 jdk8,idea2020.1.1,mysql8.0,maven3,springboot2.5.0 建表语句 这两张表要建在不同的数据库下 CREATE TABLE `job_test` ( `id` int(11) NOT

Curtisjia的博客 4278

SpringBoot整合ShardingSphere-JDBC实现分库分表实战

分库分表是应对MySQL单库性能瓶颈的核心架构策略,其本质是通过水平拆分将海量数据与高并发请求分散到多个物理数据库实例中。原理上依赖分片键路由、分布式主键生成和SQL改写引擎,在保障ACID局部性的同时牺牲全局事务能力。技术价值在于零代理延迟、ORM透明兼容及快速落地可行性,特别适合订单、日志等增长型业务场景。本文基于SpringBoot + ShardingSphere-JDBC + MyBatis技术栈,聚焦真实生产验证的分片配置、跨库JOIN支持与MyBatis无侵入集成,深入解析sharding-c

weixin_32821533的博客 160

shardingsphere5.x整合springboot+dynamic-datasource多数据源实战

shardingsphere,分库分表,多数据源

任人人人呢的博客 1万+

SpringBoot集成ShardingSphere分库分表实战指南

分库分表是应对单表千万级数据性能瓶颈的核心数据库扩展技术,其本质是通过逻辑表抽象与物理节点路由,将读写请求按分片键(如id、age)分散至多个数据库或数据表,从而突破单机容量与并发限制。该方案依托JDBC增强层实现无侵入式SQL解析与路由,具备事务兼容性好、开发成本低、与MyBatis-Plus等主流ORM无缝集成的技术优势,广泛应用于电商订单、用户中心、日志归档等高吞吐场景。本文聚焦Spring Boot 2.x与ShardingSphere 3.1.0的生产级落地,覆盖建库建表规范、applicatio

weixin_31528001的博客 194

13 万字 C 语言从入门到精通保姆级教程2021 年版

13 万字C语言保姆级教程,从入门到精通。

李南江 61万+

MyBatis 动态数据源切换在 Spring Boot 环境下的实现方案

摘要:本文系统介绍了在SpringBoot+MyBatis环境下实现动态数据源切换的技术方案。首先分析了多数据源场景需求,包括读写分离、多租户、分库分表等应用场景。然后深入解析了Spring的AbstractRoutingDataSource核心原理,通过ThreadLocal上下文管理和AOP切面实现数据源动态切换。文章详细演示了从配置多数据源、自定义注解到实现AOP拦截的完整方案,并探讨了生产环境中的线程安全、事务管理等关键问题。最后对比了手写实现与MyBatis-Plus、ShardingSphere

持续输出Java相关知识 1528

红外图像处理实战:用MATLAB实现时域高通滤波(THPF)去噪(附完整代码)

本文详细介绍了如何利用MATLAB实现时域高通滤波(THPF)算法进行红外图像去噪,包括核心原理、工程实现要点和完整代码示例。通过分析时间窗口选择、空域预处理优化和自适应参数调整等关键点,帮助开发者有效抑制固定图案噪声,适用于无人机载红外监测和安防监控等场景。

weixin_42783709的博客 183

SpringBoot多数据源配置,其实没你想的那么难

Spring提供了一个,它本身不管理连接,而是根据一个方法的返回值,从内部维护的Map里选一个真正的DataSource。所以我们要做三件事:定义多个真实的DataSource(比如master、slave)。写一个类继承,重写,从ThreadLocal里取当前数据源标识。写一个AOP切面或注解,在方法执行前把数据源标识塞进ThreadLocal,执行后清理。SpringBoot多数据源配置,核心就是不要被“动态”“路由”这些词吓到,代码量其实很少。先跑通主从切换,再考虑分库分表、事务一致性。

2501_90410679的博客 10

5分钟搞定Unity+Visual Studio开发环境配置(含C#游戏开发工作负载)

本文提供了一份详细的Unity与Visual Studio开发环境配置指南,帮助开发者在5分钟内完成C#游戏开发工作负载的设置。从Unity Hub安装到Visual Studio关键组件选择,再到常见问题排查,全面覆盖环境搭建的各个环节,显著提升游戏开发效率。

weixin_29014237的博客 248

WIFI基础入门--802.11g--增强速率物理层ERP--15

WIFI基础入门--802.11--增强速率物理层--191.802.11g的组件保护机制 1.802.11g的组件 802.11g其实是将好几种物理层规范合而为一。虽然统称为增强速率物理层(Extended Rate PHY简称ERP),但是实际上ERP有好几种不同的方式: ERP-DSSS与ERP-CCK 这两种模式向下兼容于原始直接序列规范(1Mbps与2Mbps),以及所提到的802.11...

香蕉一号 4617

SpringBoot+Mybatis-plus多数据源配置全流程(从yml到事务管理)

本文详细介绍了SpringBoot+Mybatis-plus多数据源配置的全流程,包括yml配置、动态切换和事务管理。通过实战案例演示了如何使用@DS注解实现数据源切换,并提供了常见问题的解决方案和性能优化建议,帮助开发者高效处理多数据库场景下的数据访问需求。

weixin_30776273的博客 540

为Codex添加任务完成提示音:提升开发效率的实用技巧

在自动化脚本开发和模型训练过程中,实时获取任务状态反馈是提升工作效率的关键。通过Hook机制,开发者可以在代码执行的关键节点注入自定义逻辑,实现各种自动化操作。Codex的Hook系统特别适合用于任务完成提醒,其post_execution节点能准确捕获执行结束事件。技术实现上,只需简单配置TOML文件并编写音频播放脚本,就能让系统在任务完成时自动播放提示音。这种声音反馈机制尤其适用于长时间运行的批量任务和模型训练场景,实测可提升30%以上的工作效率。结合动态音效选择和跨平台兼容方案,该技巧还能扩展为桌面通

weixin_26767391的博客 533

SpringBoot_v2多数据源配置详解:动态切换与AOP实现原理

通过本文的详细解析,相信您已经掌握了SpringBoot_v2多数据源配置的核心原理和实现细节。现在就可以在您的项目中应用这些技术,构建更加健壮和高效的数据库访问层!✨

gitblog_00201的博客 865

华为OD机试双机位C卷-水库溃坝填补(Py/Java/C/C++/Js/Go)

华为OD机试(机考)双机位C卷 【水库溃坝填补】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。水库溃坝填补。

qq_45776114的博客 2109

TIA西门子博途V19安装教程及注意事项

TIA西门子博途V19安装教程及注意事项

Robot_PLC_HMI_Automation 1万+

基于springboot+mybatis+Sharding jdbc实现的分库分表、读写分离

这两天抽空搞了一下Sharding jdbc,鉴于现在做的项目中也用到了这块(不是我搞的),作为一名对技术痴迷的渣男,怎么可能无视它的存在,必须自己来搞一搞哈。 搞起~ 项目搭建 1、新建一个spring boot工程,添加依赖 <dependencies> ...

chuopai1101的博客 1102

大模型——Coze工作流实战:一键生成像素风格视频

本文介绍了如何利用Coze平台工作流生成像素风格心理学短视频的方法。通过创建工作流、设置变量节点、调用大模型生成文案、数据格式化处理、合成音频、生成字幕等步骤,最终实现从输入主题到生成完整视频的自动化流程。文章详细展示了每个节点的具体配置方法,包括提示词编写和数据处理脚本,为读者提供了完整的像素风格视频制作解决方案。

817

SpringBoot+Mybatis-plus+shardingsphere实现分库分表

数据库选择:使用MySQL数据库,因为MySQL在分库分表方面有较成熟的解决方案。分库分表策略:可以采用水平分库分表的策略,根据一定的规则将数据分散存储在不同的数据库和表中,例如可以根据用户ID、订单ID等进行分片。数据分片策略:可以采用基于雪花算法的分布式ID生成器来生成全局唯一的ID,确保数据在不同数据库和表中的唯一性。数据同步:考虑到数据分散存储在不同的数据库和表中,需要实现数据同步机制来保证数据的一致性,可以使用Canal等开源工具来实现MySQL数据的实时同步。连接池优化。

LuckFairyLuckBaby的博客 2328

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Spring Boot集成Sharding-jdbc + Mybatis-Plus实现分库分表

Spring Boot集成Sharding-jdbc实现分库分表和读写分离一、 Sharding-jdbc简介二、项目创建 一、 Sharding-jdbc简介     Sharding-jdbc是当当网开源的中间件;定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为...

Macky_He的博客 1万+

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Springboot 2.6 + Mybatis Plus 3.5 集成 Sharding-jdbc 5.1 分库分表

随着业务数据的增加,单个的数据库已经无法支撑系统运行了,为了提升系统的流畅性,分库分表就是为了解决系统在大量数据情况下系统相应时间的问题。Sharding-jdbc 作为成熟的分库分表技术框架,在国内应用广泛。本文将介绍基于 Springboot 2.6 + Mybatis Plus 3.5 集成 Sharding-jdbc 5.1 实现分库分表功能。

Mrqiang9001 1万+

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

上一篇: SpringBoot集成Xxl-job
下一篇: SpringBoot集成Minio(简单使用)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值