依赖 | 版本 |
---|---|
windows | 10 |
mysql | Mysql 8.0.25 |
SpringBoot | 2.4.10 |
通过自定义注解+自定义mybatis拦截器的方式实现对sql的拦截,然后重写逻辑sql,将逻辑表替换成真实的表。
源码地址
package com.lh.boot.mybatis.fkfb.config;
import cn.hutool.extra.spring.SpringUtil;
import com.lh.boot.mybatis.fkfb.config.strategy.AbstractSplitTableStrategy;
import com.lh.boot.mybatis.fkfb.config.strategy.StrategyManager;
import lombok.extern.slf4j.Slf4j;
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.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;
/**
* @author: StarrySky
* @createDate: 2021/8/23 10:05
* @version: 1.0
* @description:
*/
@Slf4j
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// id为执行的mapper方法的全路径名
String id = mappedStatement.getId();
BoundSql boundSql = statementHandler.getBoundSql();
// 注解逻辑判断 添加注解了才拦截
Class<?> classType = Class.forName(id.substring(0, mappedStatement.getId().lastIndexOf(".")));
if (classType.isAnnotationPresent(TableSeg.class)) {
TableSeg tableSeg = classType.getAnnotation(TableSeg.class);
String sql = rewriteSql(tableSeg, boundSql);
//通过反射修改sql语句
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
}
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
log.warn("MyInterceptor=======" + properties.toString());
}
/**
* 重新sql
*
* @param tableSeg 注解
* @param boundSql sql信息
* @return 重写后的sql
*/
private String rewriteSql(TableSeg tableSeg, BoundSql boundSql) {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
StrategyManager strategyManager = SpringUtil.getBean(StrategyManager.class);
AbstractSplitTableStrategy strategy = strategyManager.getStrategy(tableSeg.strategy());
String newTableName = strategy.doSharding(tableSeg.tableName(), parameterMappings, parameterObject);
String newSql = sql.replaceAll(tableSeg.tableName(), newTableName);
log.info("rewriteSql=======> logicTable={}", tableSeg.tableName());
log.info("rewriteSql=======> logicSql={}", sql);
log.info("rewriteSql=======> newTableName={}", newTableName);
log.info("rewriteSql=======> newSql={}", newSql);
return newSql;
}
}
package com.lh.boot.mybatis.fkfb.config;
import java.lang.annotation.*;
/**
* @author: StarrySky
* @createDate: 2021/8/23 11:14
* @version: 1.0
* @description: 自定义注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface TableSeg {
/**
* 逻辑表名
*
* @return String
*/
String tableName();
/**
* 分表策略
*
* @return 策略名
*/
String strategy();
}
package com.lh.boot.mybatis.fkfb.config.strategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author: StarrySky
* @createDate: 2021/8/23 15:33
* @version: 1.0
* @description: 策略管理者
*/
@Slf4j
@Component
public class StrategyManager {
private final Map<String, AbstractSplitTableStrategy> strategies = new ConcurrentHashMap<>(10);
public AbstractSplitTableStrategy getStrategy(String key) {
return strategies.get(key);
}
public Map<String, AbstractSplitTableStrategy> getStrategies() {
return strategies;
}
public void registerStrategy(String key, AbstractSplitTableStrategy strategy) {
if (strategies.containsKey(key)) {
log.error("Key is already in use! key={}", key);
throw new RuntimeException("Key is already in use! key=" + key);
}
strategies.put(key, strategy);
}
}
package com.lh.boot.mybatis.fkfb.config.strategy;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.ParameterMapping;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @author: StarrySky
* @createDate: 2021/8/23 15:09
* @version: 1.0
* @description: 分表策略抽象类
*/
@Slf4j
public abstract class AbstractSplitTableStrategy {
/**
* 策略管理者
*/
@Autowired
private StrategyManager strategyManager;
abstract String key();
@PostConstruct
public void init() {
this.register();
}
/**
* @param logicTableName 逻辑表名
* @param list 映射
* @param val 值
* @return 实际表名
*/
public abstract String doSharding(String logicTableName, List<ParameterMapping> list, Object val);
protected final void register() {
String name = key();
strategyManager.registerStrategy(name, this);
}
/**
* 从mybatis映射中取指定的值
*
* @param list 映射集
* @param val 参数值
* @param shardingKey 分片键
* @return 分片键对应的值
*/
protected String getShardingValue(List<ParameterMapping> list, Object val, String shardingKey) {
JSONObject obj;
if (val.toString().contains("=")) { //用变量传值
String replaceAll = val.toString().replaceAll("=", ":");
obj = (JSONObject) JSONObject.parse(replaceAll);
} else { //用对象传值
obj = (JSONObject) JSONObject.parse(JSON.toJSONString(val));
}
for (ParameterMapping para : list) {
String property = para.getProperty();
log.info("abstract getShardingValue! shardingKey={} property={} value={}", shardingKey, property, obj.get(property));
if (para.getProperty().equals(shardingKey)) {
return obj.getString(shardingKey); //获取制定sql参数
}
}
throw new RuntimeException("Sharding value is null! shardingKey=" + shardingKey);
}
}
package com.lh.boot.mybatis.fkfb.config.strategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.ParameterMapping;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author: StarrySky
* @createDate: 2021/8/23 13:46
* @version: 1.0
* @description:
*/
@Slf4j
@Component
public class ProductSplitTableStrategy extends AbstractSplitTableStrategy {
public static final String PRODUCT_STRATEGY = "PRODUCT_STRATEGY";
@Override
public String key() {
return PRODUCT_STRATEGY;
}
@Override
public String doSharding(String logicTableName, List<ParameterMapping> list, Object val) {
/**
* 根据订单id取模分表
*/
String orderId = getShardingValue(list, val, "productId");
return logicTableName + "_" + (Long.parseLong(orderId) % 2 + 1);
}
}
package com.lh.boot.mybatis.fkfb.config.strategy;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.mapping.ParameterMapping;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class ProductDescSplitTableStrategy extends AbstractSplitTableStrategy {
public static final String PRODUCT_DESC_STRATEGY = "PRODUCT_DESC_STRATEGY";
@Override
public String key() {
return PRODUCT_DESC_STRATEGY;
}
@Override
public String doSharding(String logicTableName, List<ParameterMapping> list, Object val) {
/**
* 根据产品id取模分表
*/
String orderId = getShardingValue(list, val, "productId");
return logicTableName + "_" + (Long.parseLong(orderId) % 2 + 1);
}
}
package com.lh.boot.mybatis.fkfb.mapper;
import com.lh.boot.mybatis.fkfb.config.TableSeg;
import com.lh.boot.mybatis.fkfb.config.strategy.ProductSplitTableStrategy;
import com.lh.boot.mybatis.fkfb.entity.ProductInfo;
import com.lh.boot.mybatis.fkfb.entity.ProductInfoVO;
import java.util.List;
@TableSeg(tableName = "product_info", strategy = ProductSplitTableStrategy.PRODUCT_STRATEGY)
public interface ProductInfoMapper {
int deleteByPrimaryKey(Long productId);
int insert(ProductInfo record);
int insertSelective(ProductInfo record);
ProductInfo selectByPrimaryKey(Long productId);
int updateByPrimaryKeySelective(ProductInfo record);
int updateByPrimaryKey(ProductInfo record);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lh.boot.mybatis.fkfb.mapper.ProductInfoMapper">
<resultMap id="BaseResultMap" type="com.lh.boot.mybatis.fkfb.entity.ProductInfo">
<constructor>
<idArg column="product_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
<arg column="store_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
<arg column="price" javaType="java.math.BigDecimal" jdbcType="DECIMAL"/>
<arg column="product_name" javaType="java.lang.String" jdbcType="VARCHAR"/>
<arg column="city" javaType="java.lang.String" jdbcType="VARCHAR"/>
<arg column="status" javaType="java.lang.String" jdbcType="VARCHAR"/>
constructor>
resultMap>
<sql id="Base_Column_List">
product_id, store_id, price, product_name, city, status
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from product_info
where product_id = #{productId,jdbcType=BIGINT}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from product_info
where product_id = #{productId,jdbcType=BIGINT}
delete>
<insert id="insert" parameterType="productInfo">
insert into product_info (product_id, store_id, price,
product_name, city, status
)
values (#{productId,jdbcType=BIGINT}, #{storeId,jdbcType=BIGINT}, #{price,jdbcType=DECIMAL},
#{productName,jdbcType=VARCHAR}, #{city,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}
)
insert>
<insert id="insertSelective" parameterType="productInfo">
insert into product_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="productId != null">
product_id,
if>
<if test="storeId != null">
store_id,
if>
<if test="price != null">
price,
if>
<if test="productName != null">
product_name,
if>
<if test="city != null">
city,
if>
<if test="status != null">
status,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="productId != null">
#{productId,jdbcType=BIGINT},
if>
<if test="storeId != null">
#{storeId,jdbcType=BIGINT},
if>
<if test="price != null">
#{price,jdbcType=DECIMAL},
if>
<if test="productName != null">
#{productName,jdbcType=VARCHAR},
if>
<if test="city != null">
#{city,jdbcType=VARCHAR},
if>
<if test="status != null">
#{status,jdbcType=VARCHAR},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="productInfo">
update product_info
<set>
<if test="storeId != null">
store_id = #{storeId,jdbcType=BIGINT},
if>
<if test="price != null">
price = #{price,jdbcType=DECIMAL},
if>
<if test="productName != null">
product_name = #{productName,jdbcType=VARCHAR},
if>
<if test="city != null">
city = #{city,jdbcType=VARCHAR},
if>
<if test="status != null">
status = #{status,jdbcType=VARCHAR},
if>
set>
where product_id = #{productId,jdbcType=BIGINT}
update>
<update id="updateByPrimaryKey" parameterType="productInfo">
update product_info
set store_id = #{storeId,jdbcType=BIGINT},
price = #{price,jdbcType=DECIMAL},
product_name = #{productName,jdbcType=VARCHAR},
city = #{city,jdbcType=VARCHAR},
status = #{status,jdbcType=VARCHAR}
where product_id = #{productId,jdbcType=BIGINT}
update>
mapper>
package com.lh.boot.mybatis.fkfb.mapper;
import com.lh.boot.mybatis.fkfb.config.TableSeg;
import com.lh.boot.mybatis.fkfb.config.strategy.ProductDescSplitTableStrategy;
import com.lh.boot.mybatis.fkfb.entity.ProductDesc;
@TableSeg(tableName = "product_desc", strategy = ProductDescSplitTableStrategy.PRODUCT_DESC_STRATEGY)
public interface ProductDescMapper {
int deleteByPrimaryKey(Long productId);
int insert(ProductDesc record);
int insertSelective(ProductDesc record);
ProductDesc selectByPrimaryKey(Long productId);
int updateByPrimaryKeySelective(ProductDesc record);
int updateByPrimaryKey(ProductDesc record);
}
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lh.boot.mybatis.fkfb.mapper.ProductDescMapper">
<resultMap id="BaseResultMap" type="com.lh.boot.mybatis.fkfb.entity.ProductDesc">
<constructor>
<idArg column="product_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
<arg column="store_id" javaType="java.lang.Long" jdbcType="BIGINT"/>
<arg column="product_size" javaType="java.lang.String" jdbcType="VARCHAR"/>
<arg column="stock" javaType="java.lang.Long" jdbcType="BIGINT"/>
<arg column="desc_info" javaType="java.lang.String" jdbcType="VARCHAR"/>
constructor>
resultMap>
<sql id="Base_Column_List">
product_id, store_id, product_size, stock, desc_info
sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from product_desc
where product_id = #{productId,jdbcType=BIGINT}
select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
delete from product_desc
where product_id = #{productId,jdbcType=BIGINT}
delete>
<insert id="insert" parameterType="com.lh.boot.mybatis.fkfb.entity.ProductDesc">
insert into product_desc (product_id, store_id, product_size,
stock, desc_info)
values (#{productId,jdbcType=BIGINT}, #{storeId,jdbcType=BIGINT}, #{productSize,jdbcType=VARCHAR},
#{stock,jdbcType=BIGINT}, #{descInfo,jdbcType=VARCHAR})
insert>
<insert id="insertSelective" parameterType="com.lh.boot.mybatis.fkfb.entity.ProductDesc">
insert into product_desc
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="productId != null">
product_id,
if>
<if test="storeId != null">
store_id,
if>
<if test="productSize != null">
product_size,
if>
<if test="stock != null">
stock,
if>
<if test="descInfo != null">
desc_info,
if>
trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="productId != null">
#{productId,jdbcType=BIGINT},
if>
<if test="storeId != null">
#{storeId,jdbcType=BIGINT},
if>
<if test="productSize != null">
#{productSize,jdbcType=VARCHAR},
if>
<if test="stock != null">
#{stock,jdbcType=BIGINT},
if>
<if test="descInfo != null">
#{descInfo,jdbcType=VARCHAR},
if>
trim>
insert>
<update id="updateByPrimaryKeySelective" parameterType="com.lh.boot.mybatis.fkfb.entity.ProductDesc">
update product_desc
<set>
<if test="storeId != null">
store_id = #{storeId,jdbcType=BIGINT},
if>
<if test="productSize != null">
product_size = #{productSize,jdbcType=VARCHAR},
if>
<if test="stock != null">
stock = #{stock,jdbcType=BIGINT},
if>
<if test="descInfo != null">
desc_info = #{descInfo,jdbcType=VARCHAR},
if>
set>
where product_id = #{productId,jdbcType=BIGINT}
update>
<update id="updateByPrimaryKey" parameterType="com.lh.boot.mybatis.fkfb.entity.ProductDesc">
update product_desc
set store_id = #{storeId,jdbcType=BIGINT},
product_size = #{productSize,jdbcType=VARCHAR},
stock = #{stock,jdbcType=BIGINT},
desc_info = #{descInfo,jdbcType=VARCHAR}
where product_id = #{productId,jdbcType=BIGINT}
update>
mapper>