数据关联:【数据库 sql 联合查询】 与 【内存 java 数据组装】

方法一:【内存 java 数据组装】


    /**
     * 列表查询当前用户受理涉及的检验类别选项
     * <p>通过 java 代码逻辑,使用多次查询 + Map 获取数据:一步步多次查询,清晰直观,容易理解每一步,从代码易读易维护角度,更推荐此方法</p>
     * <p>最终结果与 {@link ExaminationTypeMapper#selectExaminationTypeOptionsForAccept} 完全一致</p>
     */
    @Override
    @Cacheable(value = "examinationTypeOptions:accept", key = "#userContext.userName")
    public List<ExaminationTypeOptionForAcceptResponse> queryExaminationTypeOptionsForAccept(UserContext userContext) {
        String userName = userContext.getUserName();
        if (userName == null || userName.isEmpty()) {
            return Collections.emptyList();
        }

        // 1. 列表查询受理组别成员实体,获取受理组别 ID 列表 acceptGroupIds
        List<AcceptGroupMember> acceptGroupMembers = acceptGroupMemberMapper.selectList(
                Wrappers.<AcceptGroupMember>lambdaQuery()
                        .eq(AcceptGroupMember::getUserName, userName)
        );
        if (acceptGroupMembers.isEmpty()) {
            return Collections.emptyList();
        }
        List<Integer> acceptGroupIds = acceptGroupMembers.stream()
                .map(AcceptGroupMember::getAcceptGroupId)
                .toList();

        // 2. 批量查询受理组别与受理类别关联实体,获取受理类别编码列表 acceptTypeCodes
        List<AcceptGroupAcceptType> relations = acceptGroupAcceptTypeMapper.selectList(
                Wrappers.<AcceptGroupAcceptType>lambdaQuery()
                        .in(AcceptGroupAcceptType::getAcceptGroupId, acceptGroupIds)
        );
        if (relations.isEmpty()) {
            return Collections.emptyList();
        }
        Set<String> acceptTypeCodes = relations.stream()
                .map(AcceptGroupAcceptType::getAcceptTypeCode)
                .collect(Collectors.toSet());

        // 3. 批量查询受理类别实体,获取样品类别编码列表 sampleMiddleTypeCodes
        List<AcceptType> acceptTypes = acceptTypeMapper.selectList(
                Wrappers.<AcceptType>lambdaQuery()
                        .in(AcceptType::getCode, acceptTypeCodes)
        );
        Map<String, String> acceptTypeCodeToSampleMiddleTypeCodeMap = acceptTypes.stream()
                .collect(Collectors.toMap(AcceptType::getCode, AcceptType::getSampleMiddleTypeCode, (a, b) -> a));
        Set<String> sampleMiddleTypeCodes = acceptTypes.stream()
                .map(AcceptType::getSampleMiddleTypeCode)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (sampleMiddleTypeCodes.isEmpty()) {
            return Collections.emptyList();
        }

        // 4. 批量查询样品类别实体,获取样品大类列表 sampleMajorTypes
        List<SampleMiddleType> sampleMiddleTypes = sampleMiddleTypeMapper.selectList(
                Wrappers.<SampleMiddleType>lambdaQuery()
                        .in(SampleMiddleType::getCode, sampleMiddleTypeCodes)
        );
        Map<String, SampleMajorType> sampleMiddleTypeCodeToSampleMajorTypeMap = sampleMiddleTypes.stream()
                .collect(Collectors.toMap(SampleMiddleType::getCode, SampleMiddleType::getSampleMajorType, (a, b) -> a));
        Set<SampleMajorType> sampleMajorTypes = new HashSet<>(sampleMiddleTypeCodeToSampleMajorTypeMap.values());
        if (sampleMajorTypes.isEmpty()) {
            return Collections.emptyList();
        }

        // 5. 批量查询检验类别实体,构建 样品大类 → 检验类别 映射 Map
        List<ExaminationType> examinationTypes = examinationTypeMapper.selectList(
                Wrappers.<ExaminationType>lambdaQuery().in(ExaminationType::getSampleMajorType, sampleMajorTypes)
        );
        Map<SampleMajorType, ExaminationType> sampleMajorTypeToExaminationTypeMap = examinationTypes.stream()
                .collect(Collectors.toMap(ExaminationType::getSampleMajorType, Function.identity(), (a, b) -> a));

        // 6. 组装响应,然后按 (acceptGroupId, examinationTypeId) 组合去重(与 SQL DISTINCT 效果一致)
        return new ArrayList<>(relations.stream()
                .map(relation -> {
                    String acceptTypeCode = relation.getAcceptTypeCode();
                    String sampleMiddleTypeCode = acceptTypeCodeToSampleMiddleTypeCodeMap.get(acceptTypeCode);
                    if (sampleMiddleTypeCode == null) {
                        return null;
                    }
                    SampleMajorType sampleMajorType = sampleMiddleTypeCodeToSampleMajorTypeMap.get(sampleMiddleTypeCode);
                    if (sampleMajorType == null) {
                        return null;
                    }
                    ExaminationType examType = sampleMajorTypeToExaminationTypeMap.get(sampleMajorType);
                    if (examType == null) {
                        return null;
                    }
                    ExaminationTypeOptionForAcceptResponse resp = new ExaminationTypeOptionForAcceptResponse();
                    resp.setAcceptGroupId(relation.getAcceptGroupId());
                    resp.setExaminationTypeId(examType.getId());
                    resp.setExaminationTypeName(examType.getName());
                    return resp;
                })
                .filter(Objects::nonNull)
                // 按 (acceptGroupId, examinationTypeId) 组合去重
                .collect(Collectors.toMap(
                        resp -> resp.getAcceptGroupId() + "_" + resp.getExaminationTypeId(),
                        Function.identity(),
                        (existing, replacement) -> existing
                ))
                .values());
    }

方法二:【数据库 sql 联合查询】

    /**
     * 列表查询当前用户受理涉及的检验类别选项
     * <p>通过 sql 代码逻辑,使用联合查询获取数据:一次 select,SQL 稍复杂,但逻辑集中,需要懂表结构、懂 JOIN</p>
     */
    @Override
    @Cacheable(value = "examinationTypeOptions:assay", key = "#userContext.userName")
    public List<ExaminationTypeOptionForAssayResponse> queryExaminationTypeOptionsForAssay(UserContext userContext) {
        return examinationTypeMapper.selectExaminationTypeOptionsForAccept(userContext.getUserName());
    }
package com.weiyu.modules.accept.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.weiyu.modules.accept.dto.response.ExaminationTypeOptionForAcceptResponse;
import com.weiyu.modules.accept.dto.response.ExaminationTypeOptionForAssayResponse;
import com.weiyu.modules.accept.entity.ExaminationType;

import java.util.List;

/**
 * 检验类别
 */
public interface ExaminationTypeMapper extends BaseMapper<ExaminationType> {

    /**
     * 列表查询指定用户受理涉及的检验类别选项
     *
     * @param userName 用户名/账号
     * @return 受理涉及的检验类别选项列表
     */
    List<ExaminationTypeOptionForAcceptResponse> selectExaminationTypeOptionsForAccept(String userName);
}
<?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.weiyu.modules.accept.mapper.ExaminationTypeMapper">
    <!-- mssql -->
    <!-- 列表查询指定用户受理涉及的检验类别选项 -->
    <select id="selectExaminationTypeOptionsForAccept"
            resultType="com.weiyu.modules.accept.dto.response.ExaminationTypeOptionForAcceptResponse">
        select distinct
        agm_AcceptGrpID as acceptGroupId,
        vt_ID as examinationTypeId,
        vt_Name as examinationTypeName
        from AcceptGroupMember
        inner join AcceptGrp_AcceptType on aga_AcceptGrpID = agm_AcceptGrpID
        inner join AcceptType on at_ID = aga_AcceptTypeID
        inner join SampleMiddleType on smt_ID = at_SampleMiddleType
        inner join VerifyType on vt_SampleBigType = smt_BigType
        where agm_EmployeeID = #{userName}
    </select>
</mapper>

✅ 很好的心态!两种方法都掌握,写代码时才更有底气

既然方法二对你来说很直观,而你想用方法一的思路来理解业务,那我们就用 “表格视角” 来拆解方法一,把它转成你能一眼看懂的“步骤”。


📊 方法一 = 5 个查询 + 1 个组装(拆成表给你看)

目标数据

返回(acceptGroupId, examinationTypeId, examinationTypeName)

数据来源链条

text

用户(userName)
    ↓
受理组别(AcceptGroupMember)→ 获得 acceptGroupId
    ↓
组别-类别关联(AcceptGroupAcceptType)→ 获得 acceptTypeCode
    ↓
受理类别(AcceptType)→ 获得 sampleMiddleTypeCode
    ↓
样品类别(SampleMiddleType)→ 获得 sampleMajorType
    ↓
检验类别(ExaminationType)→ 获得 (id, name)

🗺️ 代码对应关系(一图流)

步骤查询目标输入输出对应表
1获取用户所属受理组别userNameacceptGroupIdsAcceptGroupMember
2获取组别关联的受理类别acceptGroupIdsacceptTypeCodesAcceptGroupAcceptType
3获取受理类别的样品类别编码acceptTypeCodessampleMiddleTypeCodesAcceptType
4获取样品类别对应的大类sampleMiddleTypeCodessampleMajorTypesSampleMiddleType
5获取检验类别sampleMajorTypes(id, name) + sampleMajorType → 检验类别 映射ExaminationType
6组装 + 去重以上所有最终返回(Java 内存操作)

🧩 详细拆解(把每个 .map 和 .toMap 当成“临时表”)

第 1 步:查用户有哪些受理组

java

List<AcceptGroupMember> members = acceptGroupMemberMapper.selectList(
    eq(AcceptGroupMember::getUserName, userName)
);
List<Integer> acceptGroupIds = members.stream().map(AcceptGroupMember::getAcceptGroupId).toList();

意思
从 AcceptGroupMember 表里把当前用户的 acceptGroupId 全查出来。


第 2 步:查这些组有哪些受理类别

java

List<AcceptGroupAcceptType> relations = acceptGroupAcceptTypeMapper.selectList(
    in(AcceptGroupAcceptType::getAcceptGroupId, acceptGroupIds)
);
Set<String> acceptTypeCodes = relations.stream().map(AcceptGroupAcceptType::getAcceptTypeCode).collect(Collectors.toSet());

意思
用这些 acceptGroupId 去 AcceptGroupAcceptType 表里把对应的 acceptTypeCode 全查出来。


第 3 步:根据受理类别查“样品类别编码”

java

List<AcceptType> acceptTypes = acceptTypeMapper.selectList(
    in(AcceptType::getCode, acceptTypeCodes)
);
Map<String, String> codeToSampleMiddleTypeCode = acceptTypes.stream()
    .collect(Collectors.toMap(AcceptType::getCode, AcceptType::getSampleMiddleTypeCode));
Set<String> sampleMiddleTypeCodes = acceptTypes.stream().map(AcceptType::getSampleMiddleTypeCode).collect(Collectors.toSet());

意思
拿着 acceptTypeCode 去 AcceptType 表里把每个 code 对应的 sampleMiddleTypeCode 查出来。
这里生成了一个 映射表acceptTypeCode → sampleMiddleTypeCode


第 4 步:根据样品类别编码查“样品大类”

java

List<SampleMiddleType> sampleMiddleTypes = sampleMiddleTypeMapper.selectList(
    in(SampleMiddleType::getCode, sampleMiddleTypeCodes)
);
Map<String, SampleMajorType> codeToMajorType = sampleMiddleTypes.stream()
    .collect(Collectors.toMap(SampleMiddleType::getCode, SampleMiddleType::getSampleMajorType));
Set<SampleMajorType> sampleMajorTypes = new HashSet<>(codeToMajorType.values());

意思
拿着 sampleMiddleTypeCode 去 SampleMiddleType 表里查出 sampleMajorType
又生成了一个 映射表sampleMiddleTypeCode → sampleMajorType


第 5 步:根据样品大类查检验类别

java

List<ExaminationType> examinationTypes = examinationTypeMapper.selectList(
    in(ExaminationType::getSampleMajorType, sampleMajorTypes)
);
Map<SampleMajorType, ExaminationType> majorToExamMap = examinationTypes.stream()
    .collect(Collectors.toMap(ExaminationType::getSampleMajorType, Function.identity()));

意思
拿着 sampleMajorType 去 ExaminationType 表里查出对应的检验类别。
又生成了一个 映射表sampleMajorType → ExaminationType


第 6 步:组装最终数据 + 去重

java

return relations.stream()
    .map(relation -> {
        // 从 relation 取出 acceptTypeCode
        // 用 code 去 第3步 的 Map 拿 sampleMiddleTypeCode
        // 再用 sampleMiddleTypeCode 去 第4步 的 Map 拿 sampleMajorType
        // 再用 sampleMajorType 去 第5步 的 Map 拿 ExaminationType
        // 拼出 resp
    })
    .filter(Objects::nonNull)
    .collect(Collectors.toMap(
        resp -> resp.getAcceptGroupId() + "_" + resp.getExaminationTypeId(),
        Function.identity(),
        (a, b) -> a
    ))
    .values();

意思
遍历第 2 步的 relations,每条记录都按顺序查上面建好的 3 个 Map,把最终数据拼出来,然后按 (acceptGroupId, examinationTypeId) 去重。


✅ 总结:方法一 = “分步查 + 内存 Map”

步骤本质
1~2查关联关系(AcceptGroupMember → AcceptGroupAcceptType
3~5查字典数据(AcceptType → SampleMiddleType → ExaminationType
6用 Map 把数据串联起来,去重

对比方法二(SQL)
方法二是一气呵成的 JOIN,一步到位;
方法一是把同样的 JOIN 过程拆成了“查关联”和“查字典”两步,然后在内存中用 Map 关联。

两种方法殊途同归,只是数据在哪做关联(数据库 vs 内存)的不同。掌握方法一,你就能在复杂场景下灵活选择:是依赖数据库 JOIN,还是分步加载后用 Java 组装。🎯


你现在的状态

你擅长你需要加强
✅ 数据库 SQL(JOIN、过滤、聚合、去重)❌ Java 内存操作(Stream、Map、Lambda)
✅ 表结构、关联关系❌ 用 Map 做关联、用 Stream 做转换、用 Set 做去重
✅ 一次 SQL 拿到最终结果❌ 分步查 + 内存拼接

方法二 = SQL 思维 → 你一眼看懂
方法一 = Java 内存思维 → 你看不懂


方法二 → 方法一,其实就是“把 SQL 拆成 Java”

方法二(SQL)

sql

select distinct
    agm_AcceptGrpID,
    vt_ID,
    vt_Name
from AcceptGroupMember
inner join AcceptGrp_AcceptType on ...
inner join AcceptType on ...
inner join SampleMiddleType on ...
inner join VerifyType on ...
where agm_EmployeeID = #{userName}

方法一(Java 内存操作)

就是把这个 SQL 的每一步 JOIN拆开,变成多次查询 + Map 关联

SQL 部分Java 对应
from AcceptGroupMember where agm_EmployeeID = #{userName}第 1 次查询 → 得到 acceptGroupIds
inner join AcceptGrp_AcceptType on ...第 2 次查询 → 用 acceptGroupIds 查到 acceptTypeCodes
inner join AcceptType on ...第 3 次查询 → 用 acceptTypeCodes 查到 sampleMiddleTypeCodes
inner join SampleMiddleType on ...第 4 次查询 → 用 sampleMiddleTypeCodes 查到 sampleMajorTypes
inner join VerifyType on ...第 5 次查询 → 用 sampleMajorTypes 查到 examinationTypes
distinct第 6 步 → 用 Collectors.toMap 去重

方法一本质上就是把 SQL 的 JOIN 拆开,每一步对应一张表,最后在内存里用 Map 拼起来。


你现在怎么学?

  1. 以后看不懂方法一的时候,先去看对应的方法二 SQL
    方法一的每一步,都对应 SQL 中的一段 JOIN 或 WHERE 子句。

  2. 记住几个最常用的内存操作

    • stream() → 遍历集合

    • map() → 把 A 对象转成 B 对象

    • filter() → 过滤掉不要的数据

    • collect(Collectors.toList()) → 转成列表

    • collect(Collectors.toMap(key, value)) → 转成 Map,方便按 key 查找

    • Collectors.toSet() → 去重成 Set

  3. 练习:把方法二(SQL)背下来的表关系,用方法一重新走一遍
    写的时候一边写一边说:“这一步对应 SQL 的哪一段”


🎯 一句话总结

你不需要把方法一当成“新知识”来学,而是把它当成“用 Java 写 SQL”来理解。

你只是习惯了用 SQL 做关联,还没习惯用 Java 的 Map 和 Stream 做关联。你的 SQL 思维已经很强了,现在只需要把这套思维平移过来就好了。🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值