当前位置: 首页 > news >正文

记录一次跨库连表的坑

一、背景

1. 业务背景

一个微服务项目,本次业务主要涉及两个板块,分别是 文章管理 和 系统管理。具有开发环境、测试环境、生产环境三个环境。其中,开发环境和测试环境用的是同一个服务器(nacos和MySQL都是用的同一个服务器中的)。

文章管理模块是系统的业务模块之一,放在article_server微服务中。在开发环境中的相关数据表放在article_server_dev数据库中,在测试环境中的相关数据表放在article_server_test数据库中。
系统管理是另外一个微服务system_manage中,包括用户管理、角色管理、组织管理等功能。在开发环境中系统管理的相关数据表放在system_manage_dev数据库中,在测试环境中系统管理的相关数据表放在system_manage_test数据库中。

具体数据库信息如下
在这里插入图片描述

此时有一个界面的需求是展示文章信息的列表,同时展示订单信息的创建人相关信息(创建人姓名、工号、电话号码)。由于是业务模块,所以在article_server微服务中编写业务逻辑代码,文章相关信息在文章数据库article_server_dev和article_server_test中的article表中;而要同时带出的文章编辑用户信息,则存储在system_manage_dev和system_manage_test数据库中的sys_user表中。在article表中有一个create_user_id字段,存放着与用户的关联字段。

2. 踩坑时期的做法

在一开始做这个业务的时候,偷了个懒,直接在mapper层使用了跨库联表,本地自测一切正常,发版到开发环境后,前端界面一切正常。具体如下:
Controller层

    @Resource
    private ApArticleService apArticleService;
    /**
     * 获取文章信息
     * 
     * @param req 文章请求参数
     * @return 文章信息
     * @author admin
     * @since v1.0
     */
    @PostMapping(name = "获取文章信息",value = "/selectArticleDetail")
    @ApiOperation("获取文章信息")
    public ResponseResult selectArticleDetail(@RequestBody ArticleInfoDto req){
        if (Objects.isNull(req) || StringUtils.isBlank(req.getArticleId())) {
            return ResponseResult.errorResult(400, "参数有误");
        }
        ArticleDto articleDto = apArticleService.selectArticleDetail(req);
        return ResponseResult.okResult(articleDto);
    }

Service层接口

    /**
     * 获取文章信息
     * 
     * @param req 文章请求参数
     * @return 文章信息
     * @author admin
     * @since v1.0
     */
    ArticleDto selectArticleDetail(ArticleInfoDto dto);

Service层实现类

    @Resource
    private ApArticleMapper apArticleMapper;
    /**
     * 获取文章信息
     * 
     * @param req 文章请求参数
     * @return 文章信息
     * @author admin
     * @since v1.0
     */
    @Override
    public ArticleDto selectArticleDetail(ArticleInfoDto dto) {
        return apArticleMapper.selectArticleDetail(dto.getArticleId());
    }

Mapper层接口

    /**
     * 获取文章信息
     * 
     * @param req 文章请求参数
     * @return 文章信息
     * @author admin
     * @since v1.0
     */
    ArticleDto selectArticleDetail(String articleId);

Mapper层xml文件

    <select id="selectArticleDetail" resultMap="resultMap">
        SELECT
        aa.*, su.real_name AS realName, su.phone, su.employ_no AS employNo
        FROM `ap_article` aa
        LEFT JOIN sys_user su ON aa.create_user_id = su.id
        <where>
            and aa.is_delete != 1
            and su.is_delete != 1
            <if test="articleId != null and articleId != ''">
                and aa.id = #{articleId}
            </if>
        </where>
    </select>

二、踩坑问题与原因排查

1. 踩坑问题

此时开发环境一切正常,发版到测试环境之后,发现测试环境有几篇文章的所有用户信息全部消失不见。

2. 原因排查

排查后发现,测试环境环境所有消失不见的用户信息,都仅限于某两个用户 张三 和 李四 。其他用户发表的文章都可以正常发现。

到数据库中排查这两个用户的文章信息,发现根据article_server_test数据库中article表,查找一篇张三写的文章的记录,该记录 create_user_id 为 “c01d7916f74811efb5c78c8caa27867d”。以其为主键id,可以在system_manage_test数据库中的sys_user 表中的 id 匹配到 张三 的用户信息。

既然可以找到信息,那为什么没有查询到结果呢?

只有张三和李四这两个用户匹配不上用户信息,而其他用户可以匹配上,于是开始寻找这两个用户与其他用户的区别。比较后发现,其他用户的数据都是直接从开发数据库迁移过来的,所以开发数据库和测试数据库的主键Id相同。而这两个用户开发数据库和测试数据库的主键Id不同,开发环境system_manage_dev数据库的sys_user表,张三 的id为“f380dcc6f74811efb5c78c8caa27867d”,测试环境system_manage_test数据库的sys_user表,张三 的id为之间提到的“c01d7916f74811efb5c78c8caa27867d”。

开发环境两张表示意图:
在这里插入图片描述
在这里插入图片描述
测试环境两张表示意图:
在这里插入图片描述
在这里插入图片描述

排查到这里就想到了之前的跨库联表操作,意识到有可能是跨库联表后,导致测试环境的情况下,连的是system_manage_dev的sys_user,而不是system_manage_test的sys_user表。

为验证此猜想,手动暂时先将article_server_test数据库中article表中对应记录的create_user_id改为system_manage_dev的sys_user中 张三 的主键id,发现可以查到相应数据。结论得到验证。

三、解决方案

1. 在sql语句中将数据库名写死(不可行)

首先想到在sql语句中将数据库名写死,但是不可行,因为开发环境、测试环境数据库名都有细微的差别,采用此方案会导致每个环境的这段sql语句不一致,所以此方案不可行。

2. 使用Spring Boot的配置属性动态注入数据库名,实现环境隔离(可行)

在开发环境和测试环境的配置文件application-dev.yml和application-test.yml分别进行如下配置:

# application-dev.properties
system.manage.db=system_manage_dev
# application-test.properties
system.manage.db=system_manage_test

此时mapper层的连表语句如下:

    <select id="selectArticleDetail" resultMap="resultMap">
        SELECT
        aa.*, su.real_name AS realName, su.phone, su.employ_no AS employNo
        FROM `ap_article` aa
        LEFT JOIN ${system.manage.db}.sys_user su ON aa.create_user_id = su.id
        <where>
            and aa.is_delete != 1
            and su.is_delete != 1
            <if test="articleId != null and articleId != ''">
                and aa.id = #{articleId}
            </if>
        </where>
    </select>

3. 远程Feign调用(可行)

在system_manage微服务写一个根据用户Id获取用户信息的方法,并用Feign远程调用。代码如下:

远程Feign调用:

    @GetMapping("/api/sys/user/getUserById")
    ResponseResult getUserById(@PathVariable("userId") String userId);

Service层实现类改为:

    @Resource
    private ApArticleMapper apArticleMapper;
    @Resource
    private IArticleClient articleClient;
    /**
     * 获取文章信息
     *
     * @param req 文章请求参数
     * @return 文章信息
     * @author admin
     * @since v2.0
     */
    @Override
    public ArticleDto selectArticleDetail(ArticleInfoDto dto) {
        ArticleDto articleDto = apArticleMapper.selectArticleDetail(dto.getArticleId());
        ResponseResult responseResult = articleClient.getUserById(articleDto.getCreateUserId());
        if (Objects.nonNull(responseResult) && Objects.nonNull(responseResult.getData()) ) {
            SysUser user = responseResult.getData();
            articleDto.setCreateUserName(user.getRealName);
            articleDto.setCreateUserNo(user.getEmployNo);
            articleDto.setPhone(user.getPhone);
        }
        return articleDto;
    }

Mapper层xml文件改为:

    <select id="selectArticleDetail" resultMap="resultMap">
        SELECT * FROM `ap_article`
        <where>
            and is_delete != 1
            <if test="articleId != null and articleId != ''">
                and id = #{articleId}
            </if>
        </where>
    </select>

同时在system_manager微服务中创建根据用户id获取用户信息的接口。
Controller层

    @Resource
    private SysUserService sysUserService ;
    /**
     * 获取用户信息
     * 
     * @param req 用户Id
     * @return 用户信息
     * @author admin
     * @since v2.0
     */
    @PostMapping(name = "获取用户信息",value = "/getUserById")
    @ApiOperation("获取用户信息")
    public ResponseResult getUserById(@RequestParam("userId") String userId){
        if (StringUtils.isBlank(userId)) {
            return ResponseResult.errorResult(400, "参数有误");
        }        
        SysUser user = apArticleService.getById(userId);
        return ResponseResult.okResult(user);
    }

四、原因分析

1.为什么这两个用户的主键id变了?

这两个用户是产品经理的个人账号,虽然也是从开发数据库迁移过来的,但是迁移后,产品经理在分配权限的时候把这两个账号先删除,再新建的,导致重新生成了uuid,所以主键id变了。

2.为什么跨库连表出现了问题?

首先,问题出现在连表时连错数据库了。

理想状态为:在开发环境是article_server_dev数据库的aritcle表和system_manage_dev数据库的sys_user表。在测试环境是article_server_test数据库的aritcle表和system_manage_test数据库的sys_user表。
能查到用户数据:
在这里插入图片描述

事实为:在开发环境是article_server_dev数据库的aritcle表和system_manage_dev数据库的sys_user表。在测试环境是article_server_test数据库的aritcle表和system_manage_dev数据库的sys_user表。

不能查到用户数据:
在这里插入图片描述

其次,思考为什么会出现这种状态。在article_server微服务中,开发环境配置的数据源为article_server_dev数据库,测试环境配置的数据源为article_server_test数据库。在system_manage微服务中,开发环境配置的数据源为system_manage_dev数据库,测试环境配置的数据源为system_manage_test数据库。
我在mapper层的xml文件中连表查询的时候,没有指定sys_user所在的数据库。这时候SpringBoot会做什么?

Spring Boot通过Profile(如dev、test)管理不同环境的配置。每个微服务的数据源(如article_server的datasource.url)会正确指向对应环境的数据库(如article_server_test)。
尽管article_server和system_manage各自的数据源配置正确,但联表查询涉及跨库时,需显式指定目标数据库名。若未通过配置动态注入数据库名,数据库会在当前连接的库中查找表,而sys_user表并不在article_server_test数据库中,所以SQL会固定指向某个环境(如system_manage_dev),导致测试环境错误。

五、最终办法

1. 配置文件动态配置数据库名。

2. 放弃跨库联表,分别在两微服务查询并使用Feign调用。

相关文章:

  • Java中用Map<String,Object>存储层次结构
  • 数据结构(初阶)(五)----栈
  • 解决下载支持gpu的pytorch问题
  • 云原生网络篇——万级节点服务网格与智能流量治理
  • [密码学实战]Java生成SM2根证书及用户证书
  • 【零基础C语言】第四节 数组
  • 系统架构设计师—计算机基础篇—计算机网络
  • leetcode 59. 螺旋矩阵 II 中等
  • DeepSeek效应初现:Grok-3补刀ChatGPT,OpenAI已在ICU?
  • 如何理解语言模型
  • 【算法】875. 快速幂
  • 代码随想录刷题day35|(二叉树篇)二叉树的非递归遍历(前序+后序)
  • 解决各大浏览器中http地址无权限调用麦克风摄像头问题(包括谷歌,Edge,360,火狐)后续会陆续补充
  • Mac mini M4安装nvm 和node
  • 化学工业领域 - 基础化工、精细化工、煤化工极简理解
  • (十一)基于vue3+mapbox-GL实现模拟高德实时导航轨迹播放
  • REACT学习第三幕--沉睡花园
  • Pseudo-Q: Generating Pseudo Language Queries for Visual Grounding
  • Java类加载机制 双亲委派机制 八股速记版
  • 网络配置的基本信息
  • 上海徐汇 “家 + 书屋”,创新服务广大家庭
  • 取得金奖西瓜品种独家使用权的上海金山,为何要到异地“试种”?
  • 第十届青春文学奖揭晓,梁晓声获特别奖
  • 纽约市长称墨海军帆船撞桥已致2人死亡,撞桥前船只疑似失去动力
  • 联合国:欢迎俄乌伊斯坦布尔会谈,希望实现全面停火
  • 征稿启事|澎湃·镜相第三届非虚构写作大赛暨2026第六届七猫现实题材征文大赛