方法一:【内存 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 | 获取用户所属受理组别 | userName | acceptGroupIds | AcceptGroupMember |
| 2 | 获取组别关联的受理类别 | acceptGroupIds | acceptTypeCodes | AcceptGroupAcceptType |
| 3 | 获取受理类别的样品类别编码 | acceptTypeCodes | sampleMiddleTypeCodes | AcceptType |
| 4 | 获取样品类别对应的大类 | sampleMiddleTypeCodes | sampleMajorTypes | SampleMiddleType |
| 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 拼起来。
你现在怎么学?
-
以后看不懂方法一的时候,先去看对应的方法二 SQL
方法一的每一步,都对应 SQL 中的一段 JOIN 或 WHERE 子句。 -
记住几个最常用的内存操作
-
stream()→ 遍历集合 -
map()→ 把 A 对象转成 B 对象 -
filter()→ 过滤掉不要的数据 -
collect(Collectors.toList())→ 转成列表 -
collect(Collectors.toMap(key, value))→ 转成 Map,方便按 key 查找 -
Collectors.toSet()→ 去重成 Set
-
-
练习:把方法二(SQL)背下来的表关系,用方法一重新走一遍
写的时候一边写一边说:“这一步对应 SQL 的哪一段”
🎯 一句话总结
你不需要把方法一当成“新知识”来学,而是把它当成“用 Java 写 SQL”来理解。
你只是习惯了用 SQL 做关联,还没习惯用 Java 的 Map 和 Stream 做关联。你的 SQL 思维已经很强了,现在只需要把这套思维平移过来就好了。🎯


被折叠的 条评论
为什么被折叠?



