mybatis拦截器分表

mybatis提供了拦截器插件用来处理被拦截的方法的某些逻辑.下面会通过创建8张表,当用户注册时,根据对手机号取余数数据入不同的库.

建表

https://note.youdao.com/yws/api/personal/file/WEBfa5f294403e547b8de8f75ed5754a294?method=download&shareKey=fdf0fdffda7b82a83c763977e2420bad

引入插件
<property name="plugins">
    <array>
        <bean id="sharingInterceptor" class="com.lios.base.sharetable.SharingInterceptor"/>
    </array>
</property>
拦截器

具体代码:

/**
 * @author LiosWong
 * @description
 * @date 2018/7/31 下午7:05
 */
@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}))
public class SharingInterceptor implements Interceptor{
    Logger logger = LoggerFactory.getLogger(SharingInterceptor.class);
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
        MetaObject metaObject = MetaObject.forObject(statementHandler,SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,reflectorFactory);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String id = mappedStatement.getId();
        id = id.substring(0,id.lastIndexOf("."));
        Class cls = Class.forName(id);
        SegmentTable segmentTable = (SegmentTable) cls.getAnnotation(SegmentTable.class);
        if(segmentTable!=null){
            String sql = (String) metaObject.getValue("delegate.boundSql.sql");
            BoundSql boundSql = statementHandler.getBoundSql();
            String tableKey = StrategyFactory.createShareStrategy(segmentTable.shareType()).setSegmentTable(segmentTable).setBoundSql(boundSql).getRouteValue();
            metaObject.setValue("delegate.boundSql.sql",sql.replaceFirst(segmentTable.tableName(),segmentTable.tableName()+tableKey));
        }
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        if(target instanceof StatementHandler){
            return Plugin.wrap(target,this);
        }
        return target;
    }
    @Override
    public void setProperties(Properties properties) {

    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SegmentTable {
    /**
     * 表名称
     * @return
     */
    String tableName()  default "";


    /**
     * 分表策略
     * @return
     */
    ShareType shareType() default ShareType.MOD;


    /**
     * 分表数量
     * @return
     */
    int tableNum() default 0;

    /**
     * 分表数量
     * @return
     */
    String shareBy() default "";
}
@Repository
@DataSource(value = DynamicDataSourceGlobal.LOCAL)
@SegmentTable(tableName = "user",shareType = ShareType.MOD,tableNum = 8,shareBy = "mobile")
public class UserInfoDaoImpl extends AbstractBaseMapper<UserInfoEntity> implements UserInfoDao{
    @Override
    public UserInfoEntity selectById(Long id) {
        return this.getSqlSession().selectOne(this.getStatement(".selectById"),id);
    }
    @Override
    public UserInfoEntity selectByMobile(String mobile) {
        return this.getSqlSession().selectOne(this.getStatement(".selectByMobile"),mobile);
    }
    @Override
    public UserInfoEntity insert(UserInfoEntity userInfoEntity){
        this.getSqlSession().insert(this.getStatement(".insert"),userInfoEntity);
        return null;
    }
}

StrategyFactory根据不同分表策略处理相应的逻辑,拦截器里主要思路就是拦截需要处理的方法,然后对参数、结果集拦截处理,然后重新构建sql语句并执行.

数据录入

https://note.youdao.com/yws/api/personal/file/WEB4410d0e711f9f67487e43c97e7ac3d15?method=download&shareKey=e8ad78d0c2b9049391037b13738a955e

更多文章请关注微信公众号:
https://note.youdao.com/yws/api/personal/file/WEB6c0f1a891fdea03ecc708833e0df9b3b?method=download&shareKey=45f2c5279d958cfcdc96b6c92be0ad96

### MyBatis 分表拦截器的实现与使用 MyBatis分表拦截器可以通过自定义插件来实现。以下是一个基于 MyBatis 拦截器机制的分表方案,该方案可以动态修改 SQL 中的表名以适应多租户或数据分片的需求。 #### 1. 自定义拦截器 MyBatis 提供了插件机制,允许开发者通过拦截器对 SQL 执行过程进行干预。拦截器需要实现 `Interceptor` 接口,并通过注解 `@Intercepts` 和 `@Signature` 来指定拦截的目标方法。 ```java import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) }) public class TenantTableInterceptor implements Interceptor { private static final String TENANT_ID_KEY = "tenantId"; @Override public Object intercept(Invocation invocation) throws Throwable { // 获取当前 SQL StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); String originalSql = (String) ReflectionUtils.getFieldValue(statementHandler.getBoundSql(), "sql"); // 动态替换表名逻辑 String modifiedSql = replaceTableName(originalSql); // 将修改后的 SQL 设置回 BoundSql ReflectionUtils.setFieldValue(statementHandler.getBoundSql(), "sql", modifiedSql); return invocation.proceed(); } private String replaceTableName(String sql) { // 获取当前租户 ID(假设从线程上下文中获取) String tenantId = TenantContext.getTenantId(); if (tenantId == null || tenantId.isEmpty()) { return sql; // 如果没有租户信息,则不修改表名 } // 替换表名逻辑,例如将表名后缀替换为租户 ID return sql.replaceAll("tbl_", "tbl_" + tenantId + "_"); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以在这里设置一些配置参数 } } ``` 上述代码中,`replaceTableName` 方法根据租户 ID 修改 SQL 中的表名[^1]。具体实现可以根据业务需求调整,例如支持时间分片或其他规则。 #### 2. 配置拦截器 在 Spring Boot 项目中,可以通过 `@Bean` 注册自定义拦截器。 ```java import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.annotation.MapperScan; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.sql.DataSource; @Configuration @MapperScan("com.example.mapper") public class MyBatisConfig { @Bean public TenantTableInterceptor tenantTableInterceptor() { return new TenantTableInterceptor(); } @Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // 添加拦截器 sessionFactory.setPlugins(new Interceptor[]{tenantTableInterceptor()}); return sessionFactory.getObject(); } } ``` #### 3. 租户上下文管理 为了确保每个请求都能正确绑定到对应的租户,可以使用一个线程本地变量(ThreadLocal)来存储租户 ID。 ```java public class TenantContext { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setTenantId(String tenantId) { contextHolder.set(tenantId); } public static String getTenantId() { return contextHolder.get(); } public static void clear() { contextHolder.remove(); } } ``` 在 Web 请求过滤器中设置租户 ID: ```java import org.springframework.stereotype.Component; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class TenantFilter implements Filter { @Override public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { try { // 从请求头中获取租户 ID String tenantId = request.getHeader("X-Tenant-Id"); TenantContext.setTenantId(tenantId); chain.doFilter(request, response); } finally { TenantContext.clear(); // 清理线程本地变量 } } } ``` #### 4. 示例 SQL 假设原始 SQL 如下: ```sql SELECT * FROM tbl_user WHERE id = #{id}; ``` 经过拦截器处理后,SQL 将变为: ```sql SELECT * FROM tbl_123_user WHERE id = #{id}; ``` 其中 `123` 是租户 ID[^2]。 --- ### 注意事项 - 确保表结构一致:分表后,所有子表的结构必须与主表保持一致。 - 数据迁移:如果已有数据,需要设计合理的数据迁移策略。 - 性能优化:分表可能增加查询复杂度,需结合索引和缓存优化性能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值