阿里巴巴处理 MP 实体对象嵌套的方案
1. 阿里巴巴推荐的第一方案:DO + DTO 分层
// 1. 数据库实体(DO - Data Object)保持简单@Data@TableName("alarm_config")publicclassAlarmConfigDO{privateLongid;privateStringconfigName;privateLongschemeId;privateIntegerstatus;// ... 其他基础字段}@Data@TableName("alarm_rule")publicclassAlarmRuleDO{privateLongid;privateStringruleName;privateStringruleType;privateDoublethreshold;privateLongconfigId;// 外键,不是对象// ... 其他基础字段}// 2. 业务传输对象(DTO)包含嵌套关系@DatapublicclassAlarmConfigDTO{privateLongid;privateStringconfigName;privateLongschemeId;privateIntegerstatus;// 嵌套对象(不是数据库字段)privateList<AlarmRuleDTO>rules;@DatapublicstaticclassAlarmRuleDTO{privateLongid;privateStringruleName;privateStringruleType;privateDoublethreshold;// 可以包含 config 信息,但不是对象嵌套privateLongconfigId;privateStringconfigName;}}// 3. Service 层组装@ServicepublicclassAlarmConfigService{publicAlarmConfigDTOgetConfigWithRules(LongconfigId){// 分别查询AlarmConfigDOconfigDO=alarmConfigMapper.selectById(configId);List<AlarmRuleDO>ruleDOs=alarmRuleMapper.selectByConfigId(configId);// 手动组装 DTOAlarmConfigDTOdto=convertToDTO(configDO);dto.setRules(ruleDOs.stream().map(this::convertRuleToDTO).collect(Collectors.toList()));returndto;}}2. 阿里巴巴的 MyBatis 映射方案(XML/注解)
// 方案一:MyBatis XML 映射// AlarmConfigMapper.xml<mapper namespace="com.aiwei.mapper.AlarmConfigMapper"><resultMap id="ConfigWithRulesMap"type="AlarmConfigDTO"><id property="id"column="id"/><result property="configName"column="config_name"/><!--集合映射:一对多--><collection property="rules"ofType="AlarmRuleDTO"select="selectRulesByConfigId"column="id"/></resultMap><select id="selectConfigWithRules"resultMap="ConfigWithRulesMap">SELECT*FROM alarm_configWHEREid=#{configId}</select><select id="selectRulesByConfigId"resultType="AlarmRuleDTO">SELECT*FROM alarm_ruleWHEREconfig_id=#{configId}ANDis_delete=0</select></mapper>// 方案二:注解映射(阿里巴巴较少用,但可用)@MapperpublicinterfaceAlarmConfigMapperextendsBaseMapper<AlarmConfigDO>{@Select("SELECT * FROM alarm_config WHERE id = #{configId}")@Results({@Result(property="id",column="id"),@Result(property="rules",column="id",many=@Many(select="selectRulesByConfigId"))})AlarmConfigDTOselectConfigWithRules(@Param("configId")LongconfigId);@Select("SELECT * FROM alarm_rule WHERE config_id = #{configId}")List<AlarmRuleDTO>selectRulesByConfigId(@Param("configId")LongconfigId);}3. 阿里巴巴的高性能方案:多表联查 + Map 处理
// 避免 N+1 查询问题,使用 JOIN 一次查完@ServicepublicclassAlarmQueryService{/** * 阿里巴巴推荐:联表查询返回 Map,手动组装 */publicMap<String,Object>getConfigWithRules(LongconfigId){// 1. 联表查询(避免多次查询)List<Map<String,Object>>resultList=alarmConfigMapper.selectConfigWithRulesJoin(configId);// 2. 手动组装为嵌套结构Map<String,Object>configMap=newHashMap<>();List<Map<String,Object>>ruleList=newArrayList<>();if(!resultList.isEmpty()){// 第一条记录包含 config 信息Map<String,Object>firstRow=resultList.get(0);configMap.put("id",firstRow.get("config_id"));configMap.put("name",firstRow.get("config_name"));// 提取所有规则for(Map<String,Object>row:resultList){if(row.get("rule_id")!=null){Map<String,Object>ruleMap=newHashMap<>();ruleMap.put("id",row.get("rule_id"));ruleMap.put("name",row.get("rule_name"));ruleMap.put("type",row.get("rule_type"));ruleMap.put("threshold",row.get("threshold"));ruleList.add(ruleMap);}}}configMap.put("rules",ruleList);returnconfigMap;}}// Mapper SQL@Select("SELECT "+" c.id as config_id, c.config_name, "+" r.id as rule_id, r.rule_name, r.rule_type, r.threshold "+"FROM alarm_config c "+"LEFT JOIN alarm_rule r ON c.id = r.config_id "+"WHERE c.id = #{configId} AND r.is_delete = 0")List<Map<String,Object>>selectConfigWithRulesJoin(@Param("configId")LongconfigId);4. 阿里巴巴的 COLA 架构模式
// 按照 COLA 架构(Clean Object-oriented & Layered Architecture)// 1. 领域对象(Domain Object)@DatapublicclassAlarmConfig{privateLongid;privateStringname;privateList<AlarmRule>rules;// 领域方法publicvoidaddRule(AlarmRulerule){if(rules==null){rules=newArrayList<>();}rules.add(rule);rule.setConfigId(this.id);}publicbooleanvalidate(){returnrules!=null&&rules.stream().allMatch(AlarmRule::isValid);}}// 2. 数据转换器(Assembler)@ComponentpublicclassAlarmConfigAssembler{publicAlarmConfigtoDomain(AlarmConfigDOconfigDO,List<AlarmRuleDO>ruleDOs){AlarmConfigconfig=newAlarmConfig();config.setId(configDO.getId());config.setName(configDO.getConfigName());// 转换规则List<AlarmRule>rules=ruleDOs.stream().map(this::toRuleDomain).collect(Collectors.toList());config.setRules(rules);returnconfig;}publicAlarmConfigDOtoDO(AlarmConfigconfig){AlarmConfigDOconfigDO=newAlarmConfigDO();configDO.setId(config.getId());configDO.setConfigName(config.getName());returnconfigDO;}}// 3. 仓储层(Repository)@RepositorypublicclassAlarmConfigRepository{@AutowiredprivateAlarmConfigMapperconfigMapper;@AutowiredprivateAlarmRuleMapperruleMapper;@AutowiredprivateAlarmConfigAssemblerassembler;publicAlarmConfigfindById(LongconfigId){// 分别查询AlarmConfigDOconfigDO=configMapper.selectById(configId);List<AlarmRuleDO>ruleDOs=ruleMapper.selectByConfigId(configId);// 组装为领域对象returnassembler.toDomain(configDO,ruleDOs);}}5. 阿里巴巴的 MapStruct + Builder 模式
// 使用 MapStruct 自动转换@Mapper(componentModel="spring",uses={AlarmRuleMapperConverter.class})publicinterfaceAlarmConfigConverter{AlarmConfigDTOtoDTO(AlarmConfigDOconfigDO);@Mapping(target="rules",ignore=true)AlarmConfigDTOtoSimpleDTO(AlarmConfigDOconfigDO);// 带嵌套的转换defaultAlarmConfigDTOtoDTOWithRules(AlarmConfigDOconfigDO,List<AlarmRuleDO>ruleDOs){AlarmConfigDTOdto=toSimpleDTO(configDO);dto.setRules(ruleDOs.stream().map(AlarmRuleMapperConverter.INSTANCE::toDTO).collect(Collectors.toList()));returndto;}}// Service 使用@Service@RequiredArgsConstructorpublicclassAlarmConfigService{privatefinalAlarmConfigMapperconfigMapper;privatefinalAlarmRuleMapperruleMapper;privatefinalAlarmConfigConverterconverter;publicAlarmConfigDTOgetConfigDetail(LongconfigId){AlarmConfigDOconfigDO=configMapper.selectById(configId);List<AlarmRuleDO>ruleDOs=ruleMapper.selectByConfigId(configId);returnconverter.toDTOWithRules(configDO,ruleDOs);}}6. 阿里巴巴处理嵌套查询的实际案例
// 案例:告警方案 -> 配置 -> 规则 三级嵌套@ServicepublicclassAlarmSchemeService{/** * 三级嵌套查询的最佳实践 */publicMap<String,Object>getSchemeDetail(LongschemeId){// 1. 查询方案基础信息AlarmSchemeDOscheme=alarmSchemeMapper.selectById(schemeId);// 2. 查询所有配置(避免N+1,批量查询)List<AlarmConfigDO>configs=alarmConfigMapper.selectBySchemeId(schemeId);List<Long>configIds=configs.stream().map(AlarmConfigDO::getId).collect(Collectors.toList());// 3. 批量查询所有规则(一次查询)List<AlarmRuleDO>allRules=alarmRuleMapper.selectByConfigIds(configIds);// 4. 按configId分组规则Map<Long,List<AlarmRuleDO>>rulesByConfigId=allRules.stream().collect(Collectors.groupingBy(AlarmRuleDO::getConfigId));// 5. 组装结果Map<String,Object>result=newLinkedHashMap<>();result.put("scheme",convertScheme(scheme));List<Map<String,Object>>configList=configs.stream().map(config->{Map<String,Object>configMap=convertConfig(config);List<AlarmRuleDO>rules=rulesByConfigId.get(config.getId());if(rules!=null){configMap.put("rules",rules.stream().map(this::convertRule).collect(Collectors.toList()));}returnconfigMap;}).collect(Collectors.toList());result.put("configs",configList);returnresult;}/** * 使用 MP 的 QueryWrapper 进行批量查询 */privateList<AlarmRuleDO>batchQueryRules(List<Long>configIds){if(configIds.isEmpty()){returnCollections.emptyList();}QueryWrapper<AlarmRuleDO>wrapper=newQueryWrapper<>();wrapper.in("config_id",configIds).eq("is_delete",0).eq("status",1).orderByAsc("sort_order");returnalarmRuleMapper.selectList(wrapper);}}7. 阿里巴巴的缓存方案处理嵌套
// 使用缓存避免重复查询嵌套数据@Service@Slf4jpublicclassCachedAlarmService{@AutowiredprivateRedisTemplate<String,Object>redisTemplate;/** * 阿里巴巴常用:三级缓存策略 */@Cacheable(value="alarm:config:detail",key="#configId",unless="#result == null")publicAlarmConfigDTOgetCachedConfigWithRules(LongconfigId){// 1. 查询配置AlarmConfigDOconfig=alarmConfigMapper.selectById(configId);if(config==null){returnnull;}// 2. 查询规则(使用本地缓存)List<AlarmRuleDO>rules=getCachedRulesByConfigId(configId);// 3. 组装AlarmConfigDTOdto=newAlarmConfigDTO();BeanUtils.copyProperties(config,dto);dto.setRules(rules.stream().map(this::convertRuleToDTO).collect(Collectors.toList()));returndto;}/** * 批量查询规则并缓存 */@Cacheable(value="alarm:rules",key="#configId")publicList<AlarmRuleDO>getCachedRulesByConfigId(LongconfigId){QueryWrapper<AlarmRuleDO>wrapper=newQueryWrapper<>();wrapper.eq("config_id",configId).eq("is_delete",0).orderByAsc("sort_order");returnalarmRuleMapper.selectList(wrapper);}}8. 阿里巴巴推荐的最佳实践总结
核心原则:
- DO 保持简单:数据库实体只包含数据库字段
- DTO 承载业务:传输对象包含嵌套关系
- 避免 N+1 查询:优先使用 JOIN 或批量查询
- 手动组装优于自动映射:更可控,性能更好
具体做法:
// ✅ 阿里巴巴推荐做法:publicAlarmConfigDTOgetConfigDetail(LongconfigId){// 1. 分别查询基础数据AlarmConfigDOconfig=configMapper.selectById(configId);List<AlarmRuleDO>rules=ruleMapper.selectByConfigId(configId);// 2. 手动组装 DTOAlarmConfigDTOdto=newAlarmConfigDTO();BeanUtils.copyProperties(config,dto);// 3. 转换嵌套对象dto.setRules(rules.stream().map(rule->{AlarmRuleDTOruleDto=newAlarmRuleDTO();BeanUtils.copyProperties(rule,ruleDto);returnruleDto;}).collect(Collectors.toList()));returndto;}// ❌ 不推荐做法:// 在 DO 中直接定义对象属性// 依赖 MyBatis 的自动嵌套映射(性能差,难维护)对于你的告警分析业务:
// 推荐方案:publicMap<String,Object>analyzeAlarm(LongconfigId,LongflightRecordId){// 1. 分别查询AlarmConfigDOconfig=alarmConfigMapper.selectById(configId);List<AlarmRuleDO>rules=alarmRuleMapper.selectEnabledByConfigId(configId);List<FlightDataDO>flightData=flightDataMapper.selectByRecordId(flightRecordId);// 2. 转换为 Map 格式Map<String,List<Map<String,Object>>>ruleMap=rules.stream().collect(Collectors.groupingBy(AlarmRuleDO::getRuleType,Collectors.mapping(this::convertRuleToMap,Collectors.toList())));// 3. 执行业务逻辑Map<String,Object>result=executeAnalysis(ruleMap,flightData);// 4. 组装返回result.put("config",convertConfigToMap(config));result.put("queryTime",LocalDateTime.now());returnresult;}阿里巴巴内部通常选择:手动组装 + Map 结构,而不是依赖 ORM 的自动嵌套映射。