Springboot Mybatis系统数据同步基本思路和实现

基本要求

  1. 数据同步要及时可靠,方案要有通用性;
  2. 进行双向同步;
  3. 不同的数据源,无法直接访问。

基本思路

  1. 使用Mybatis拦截器,去获取Insert/Update/Delete语句,并组装sql参数形成完整sql;
  2. 字符串含有单引’,使用\'替换,sql才能执行成功;
  3. 由于要进行双向同步,可以考虑使用雪花算法来保证记录ID的唯一性,由于数据源不能直接访问,考虑使用中转服务来进行数据传递;
  4. Insert语句,重置ID字段;
  5. 为了保证数据同步接口的并发数据和数据的可靠性,使用消息队列来进行消峰。
    在这里插入图片描述

关键代码

  1. SQL拦截器
@Intercepts(@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}))
@Component
@Slf4j
public class SqlInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        if (invocation.getArgs().length > 1) {
            Object parameter = invocation.getArgs()[1];
            BoundSql boundSql = mappedStatement.getBoundSql(parameter);
            String tableName = SqlUtil.getTableName(boundSql.getSql());
            log.info("tableName = {}", tableName);
            String sql = SqlUtil.getSql(mappedStatement, parameter);
            log.info("sql = {}", sql);
        }

        return invocation.proceed();
    }
}
  1. SQL提取方法
public class SqlUtil {

    /**
     * 获取sql语句
     */
    public static String getSql(MappedStatement mappedStatement, Object parameterObject) {

        Configuration configuration = mappedStatement.getConfiguration();
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);

        // 获取参数
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

        // sql语句中多个空格都用一个空格代替
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
            // 获取类型处理器注册器,类型处理器的功能是进行java类型和数据库类型的转换
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            // 如果根据parameterObject.getClass()可以找到对应的类型,则替换
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?",
                        Matcher.quoteReplacement(formatValue(parameterObject)));
            } else {
                // MetaObject主要是封装了originalObject对象,提供了get和set的方法用于获取和设置originalObject的属性值,主要支持对JavaBean、Collection、Map三种类型对象的操作
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?",
                                Matcher.quoteReplacement(formatValue(obj)));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        // 该分支是动态sql
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?",
                                Matcher.quoteReplacement(formatValue(obj)));
                    } else {
                        sql = sql.replaceFirst("\\?", "缺失");
                    }
                }
            }
        }
        return sql;
    }

    /**
     * 获取表名
     */
    public static String getTableName(String sql) {

        // 正则表达式匹配 INSERT 语句中的表名
        Pattern pattern = Pattern.compile("\\bINTO\\s+(\\w+)");

        // 忽略大小写
        Matcher matcher = pattern.matcher(sql.toUpperCase());

        // 提取匹配的表名
        if (matcher.find()) {
            return matcher.group(1);
        }

        // 正则表达式匹配 UPDATE 语句中的表名
        pattern = Pattern.compile("\\bUPDATE\\s+(\\w+)");
        matcher = pattern.matcher(sql.toUpperCase());

        // 提取匹配的表名
        if (matcher.find()) {
            return matcher.group(1);
        }

        // 正则表达式匹配 DELETE 语句中的表名
        pattern = Pattern.compile("\\bFROM\\s+(\\w+)");
        matcher = pattern.matcher(sql.toUpperCase());

        // 提取匹配的表名
        if (matcher.find()) {
            return matcher.group(1);
        }

        return null;
    }

    /**
     * 将参数值格式化为 SQL 字符串格式
     */
    private static String formatValue(Object value) {

        // 字符串类型,在值两端加单引号
        if(value instanceof String) {
            String data = ((String) value).trim();
            // 如果字符串包含"'", 替换为 "\\\\'"
            if(data.contains("'")) {
                data = data.replace("'", "\\'");
            }
            return "'" + data + "'";
        }
        // 日期时间类型,使用 SimpleDateFormat 格式化
        else if (value instanceof Date) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return "'" + dateFormat.format((Date) value) + "'";
        }
        // 布尔类型,直接返回字符串格式
        else if (value instanceof Boolean) {
            return ((boolean) value) ? "true" : "false";
        }
        // 其他类型,直接使用 toString() 方法
        else {
            return value.toString();
        }
    }
}
  1. SQL数据消费
public interface SqlMapper {

    /**
     * 插入
     * @param sql
     */
    void executeNativeInsertSql(@Param("sql") String sql);

    /**
     * 更新
     * @param sql
     */
    void executeNativeUpdateSql(@Param("sql") String sql);

    /**
     * 删除
     * @param sql
     */
    void executeNativeDeleteSql(@Param("sql") String sql);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ocean.angel.tool.mapper.SqlMapper">

    <insert id="executeNativeInsertSql">
        ${sql}
    </insert>

    <update id="executeNativeUpdateSql">
        ${sql}
    </update>

    <delete id="executeNativeDeleteSql">
        ${sql}
    </delete>

</mapper>

运行结果

  1. Insert
    在这里插入图片描述
  2. Update
    在这里插入图片描述3. Delete
    在这里插入图片描述4. batchInsert
tableName = T_USER_INFO
sql = insert into t_user_info ( username, pwd, mobile, id_card, nick, create_time ) values ( 'Jaime.yu', '123456', '15755141000', '341202*********3538', 'Jaim\'e\'', '2024-02-04 17:32:28' ) ; insert into t_user_info ( username, pwd, mobile, id_card, nick, create_time ) values ( 'Jaime.yu', '00000', '00000000000', '341202*********3533', 'Jaime', '2024-02-04 17:32:28' )
  1. batchUpdate
tableName = T_USER_INFO
sql = update t_user_info SET username = 'Jaime.yu', pwd = '123456', mobile = '15755141000', id_card = '341202*********3538', nick = 'Jaim\'e\'00', update_time = '2024-02-04 17:34:00' where id = 25 ; update t_user_info SET username = 'Jaime.yu', pwd = '00000', mobile = '00000000000', id_card = '341202*********3533', nick = 'Jaime00', update_time = '2024-02-04 17:34:00' where id = 26
  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值