前言
本文提供三种 Java 数据处理核心方案,涵盖对象与 Map 的双向映射及复杂 Map 结构扁平化,适用于接口参数转换、数据同步、跨系统数据传输等场景:
- 注解式映射(推荐):通过自定义注解标记字段映射关系,侵入性低、配置直观,适配复杂嵌套对象
KV 配置式映射:基于 Hutool 工具类,通过 Map 配置字段对应关系,适合简单对象快速转换
Map 扁平化工具:将嵌套 Map/List 结构转为单层键值对,适配 URL 参数拼接、简单配置输出等场景
其中注解式映射因无需额外配置类、可读性强、支持多层嵌套,更推荐作为优先方案。
方案一:注解式对象 - Map 映射(推荐)
通过自定义 @Table 和 @Table.Col 注解,直接在 JavaBean 字段上标记目标映射键名,配合工具类实现对象与 Map 的双向递归转换,适配含嵌套对象、集合的复杂场景。
核心注解定义(@Table + @Table.Col)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| package com.landray.kmss.shunan.travel.annotations;
import java.lang.annotation.*;
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Table {
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface Col { String value(); } }
|
注解使用示例(出差申请单数据模型)
以出差申请单同步接口的请求参数模型为例,通过注解标记每个字段对应的目标键名,支持嵌套对象(如SuperiorDepartment)、集合(如List<PassengerDetail>)的多层映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| package com.landray.kmss.shunan.travel.sync.data;
import com.landray.kmss.shunan.travel.annotations.Table;
import java.util.List;
@Table public class TripApplyData {
@Table.Col("fd_approval_number") private String approvalNumber;
@Table.Col("fd_op_code") private int opCode = 0;
@Table.Col("fd_superior_depart_list") private List<SuperiorDepartment> superiorDepartList;
@Table public static class SuperiorDepartment {
@Table.Col("fd_dept_id") private String deptId;
}
@Table public static class PassengerDetail {
@Table.Col("fd_name") private String name;
}
}
|
注解映射核心工具类(BeanMapMapper)
基于 Hutool 反射工具实现,支持:
- 对象 →
Map:递归转换嵌套对象、集合(集合元素需标注@Table)
Map → 对象:根据映射关系反向构建对象,自动处理嵌套结构和集合类型
- 空值过滤:忽略值为
null 的字段,避免无效数据传输
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| package com.landray.kmss.shunan.travel.util;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ReflectUtil; import com.landray.kmss.shunan.travel.annotations.Table;
import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*;
public final class BeanMapMapper {
private BeanMapMapper() {}
public static Map<String, Object> toMap(Object bean) { if (ObjUtil.isNull(bean)) return null; Class<?> clazz = bean.getClass(); if (!clazz.isAnnotationPresent(Table.class)) return null;
Map<String, Object> map = new LinkedHashMap<>(); Field[] fields = ReflectUtil.getFields(clazz); for (Field f : fields) { Table.Col col = f.getAnnotation(Table.Col.class); if (ObjUtil.isNull(col)) continue; Object val = ReflectUtil.getFieldValue(bean, f); if (ObjUtil.isNull(val)) continue;
if (Collection.class.isAssignableFrom(f.getType())) { Type generic = f.getGenericType(); if (generic instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) generic; Class<?> actual = (Class<?>) pt.getActualTypeArguments()[0]; if (actual.isAnnotationPresent(Table.class)) { List<Map<String, Object>> list = new ArrayList<>(); for (Object item : (Collection<?>) val) list.add(toMap(item)); map.put(col.value(), list); continue; } } } if (f.getType().isAnnotationPresent(Table.class)) { map.put(col.value(), toMap(val)); continue; } map.put(col.value(), val); } return map; }
@SuppressWarnings("unchecked") public static <T> T toBean(Map<String, Object> map, Class<T> clazz) { if (MapUtil.isEmpty(map)) return null; if (!clazz.isAnnotationPresent(Table.class)) return null;
T instance = ReflectUtil.newInstance(clazz); Field[] fields = ReflectUtil.getFields(clazz); for (Field f : fields) { Table.Col col = f.getAnnotation(Table.Col.class); if (ObjUtil.isNull(col)) continue; Object val = map.get(col.value()); if (ObjUtil.isNull(val)) continue;
if (f.getType().isAnnotationPresent(Table.class)) { ReflectUtil.setFieldValue(instance, f, toBean((Map<String, Object>) val, f.getType())); continue; } if (Collection.class.isAssignableFrom(f.getType())) { Type generic = f.getGenericType(); if (generic instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) generic; Class<?> actual = (Class<?>) pt.getActualTypeArguments()[0]; if (actual.isAnnotationPresent(Table.class) && val instanceof List) { List<Map<String, Object>> list = (List<Map<String, Object>>) val; Collection<Object> target = createCollection(f.getType()); for (Map<String, Object> item : list) target.add(toBean(item, actual)); ReflectUtil.setFieldValue(instance, f, target); continue; } } } ReflectUtil.setFieldValue(instance, f, val); } return instance; }
private static Collection<Object> createCollection(Class<?> fieldType) { if (List.class.isAssignableFrom(fieldType)) return new ArrayList<>(); if (Set.class.isAssignableFrom(fieldType)) return new LinkedHashSet<>(); return new ArrayList<>(); } }
|
工具类调用示例
从模型数据中获取 Map,通过 BeanMapMapper 快速转换为目标实体类,适配数据同步场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
private Map<String, Object> getModelData() { try { return kmReviewMain.getExtendDataModelInfo().getModelData(); } catch (Exception e) { throw new SyncBusinessException("MODEL_DATA_ERROR", "获取模型数据异常", e); } }
private TripApplyData getApply() { try { return BeanMapMapper.toBean(getModelData(), TripApplyData.class); } catch (Exception e) { throw new SyncBusinessException("MODEL_DATA_ERROR", "Map转出差申请单实体异常", e); }
}
|
通过 Map 配置字段映射关系,结合 Hutool 的 BeanUtil 实现对象之间的快速转换,适用于字段较少、映射关系简单的场景(如 VO 与数据库实体的转换)。
转换工具类(SyncBeanConvertUtil)
封装对象转换逻辑,通过预定义的映射配置(CITY_VO_COPY_OPTIONS)实现 VO 到实体的批量转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.landray.kmss.shunan.travel.sync.utils;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import com.landray.kmss.shunan.travel.model.ShunanTravelCity; import com.landray.kmss.shunan.travel.sync.vo.CityVO;
import java.util.ArrayList; import java.util.List;
import static com.landray.kmss.shunan.travel.mappings.ShunanTravelCityVoMapping.CITY_VO_COPY_OPTIONS;
public class SyncBeanConvertUtil {
public static List<ShunanTravelCity> cityVoToEntityList(CityVO cityVO) { if (ObjUtil.isNull(cityVO) || CollUtil.isEmpty(cityVO.getDataList())) { return new ArrayList<>(); }
List<ShunanTravelCity> entityList = new ArrayList<>(); for (CityVO.QueryCityCommonBean voBean : cityVO.getDataList()) { ShunanTravelCity entity = new ShunanTravelCity(); BeanUtil.copyProperties(voBean, entity, CITY_VO_COPY_OPTIONS); entityList.add(entity); }
return entityList; } }
|
KV 映射配置类(ShunanTravelCityVoMapping)
通过 Map 定义 VO 字段与实体字段的映射关系,配合 Hutool 的CopyOptions实现字段别名映射,配置直观、便于维护:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.landray.kmss.shunan.travel.mappings;
import cn.hutool.core.bean.copier.CopyOptions; import java.util.HashMap; import java.util.Map;
public class ShunanTravelCityVoMapping {
public static final Map<String, String> CITY_VO_TO_ENTITY_MAPPING;
public static final CopyOptions CITY_VO_COPY_OPTIONS;
static { CITY_VO_TO_ENTITY_MAPPING = new HashMap<>();
CITY_VO_TO_ENTITY_MAPPING.put("code", "fdCode"); CITY_VO_TO_ENTITY_MAPPING.put("name", "fdName"); CITY_VO_TO_ENTITY_MAPPING.put("englishName", "fdEnglishName"); CITY_VO_TO_ENTITY_MAPPING.put("countrySimpleCode", "fdCountrySimpleCode"); CITY_VO_TO_ENTITY_MAPPING.put("type", "fdType"); CITY_VO_TO_ENTITY_MAPPING.put("countryBelongCode", "fdCountryBelongCode"); CITY_VO_TO_ENTITY_MAPPING.put("countryBelongNameZh", "fdCountryBelongNameZh"); CITY_VO_TO_ENTITY_MAPPING.put("provinceBelongCode", "fdProvinceBelongCode"); CITY_VO_TO_ENTITY_MAPPING.put("provinceBelongNameZh", "fdProvinceBelongNameZh"); CITY_VO_TO_ENTITY_MAPPING.put("cityInitial", "fdCityinitial"); CITY_VO_TO_ENTITY_MAPPING.put("parentCityCode", "fdParentCityCode"); CITY_VO_TO_ENTITY_MAPPING.put("cityBelongCode", "fdCityBelongCode"); CITY_VO_TO_ENTITY_MAPPING.put("cityBelongNameZh", "fdCityBelongNameZh"); CITY_VO_TO_ENTITY_MAPPING.put("isDomestic", "fdIsDomestic"); CITY_VO_TO_ENTITY_MAPPING.put("haveAirport", "fdHaveAirport"); CITY_VO_TO_ENTITY_MAPPING.put("haveTrainStation", "fdHaveTrainStation"); CITY_VO_TO_ENTITY_MAPPING.put("nationalAdministrativeCode", "fdNationalAdministrativeCode");
CITY_VO_COPY_OPTIONS = new CopyOptions().setFieldMapping(CITY_VO_TO_ENTITY_MAPPING); }
private ShunanTravelCityVoMapping() {} }
|
转换工具调用示例
在接口数据同步场景中,将接口返回的 VO 快速转换为数据库实体,用于后续持久化操作:
1 2 3 4 5 6 7 8 9 10
|
CityVO cityVO = apiResponse.getResult();
List<ShunanTravelCity> currentPageCities = SyncBeanConvertUtil.cityVoToEntityList(cityVO);
|
实用工具:Map 结构扁平化工具类(MapUtil)
扩展 Hutool 的MapUtil,提供嵌套 Map/List 结构的扁平化功能,将多层级键值对转为单层结构,适用于 URL 参数拼接、简单配置输出、日志打印等场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package com.landray.kmss.shunan.travel.util;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil;
import java.util.List; import java.util.Map;
public class MapUtil extends cn.hutool.core.map.MapUtil {
public static void flattenMap(Map<String, Object> sourceMap, String parentKey, Map<String, String> targetMap) { if (isEmpty(sourceMap) || ObjUtil.isNull(targetMap)) { return; }
for (Map.Entry<String, Object> entry : sourceMap.entrySet()) { String currentKey = entry.getKey(); Object currentValue = entry.getValue();
String fullKey = StrUtil.isEmpty(parentKey) ? currentKey : parentKey + "." + currentKey;
if (ObjUtil.isNull(currentValue)) { continue; }
if (currentValue instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> nestedMap = (Map<String, Object>) currentValue; flattenMap(nestedMap, fullKey, targetMap); } else if (currentValue instanceof List) { List<?> list = (List<?>) currentValue; for (int i = 0; i < list.size(); i++) { Object listItem = list.get(i); String listKey = fullKey + "[" + i + "]"; if (listItem instanceof Map) { @SuppressWarnings("unchecked") Map<String, Object> itemMap = (Map<String, Object>) listItem; flattenMap(itemMap, listKey, targetMap); } else { targetMap.put(listKey, StrUtil.toString(listItem)); } } } else { targetMap.put(fullKey, StrUtil.toString(currentValue)); } } } }
|
扁平化效果示例
源嵌套 Map:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "user": { "name": "张三", "age": 30 }, "hobbies": ["编程", "阅读"], "address": { "city": "北京", "detail": "XX街道" }, "passengers": [ {"name": "李四", "phone": "123456"}, {"name": "王五", "phone": "654321"} ] }
|
扁平化后 Map:
1 2 3 4 5 6 7 8 9 10 11 12
| { "user.name": "张三", "user.age": "30", "hobbies[0]": "编程", "hobbies[1]": "阅读", "address.city": "北京", "address.detail": "XX街道", "passengers[0].name": "李四", "passengers[0].phone": "123456", "passengers[1].name": "王五", "passengers[1].phone": "654321" }
|
调用示例
1 2 3 4 5 6 7 8 9 10 11 12
| Map<String, Object> sourceMap = new HashMap<>();
Map<String, String> flatMap = new HashMap<>();
MapUtil.flattenMap(sourceMap, "", flatMap);
|
方案对比与选择建议
| 方案类型 |
核心优势 |
适用场景 |
依赖工具 |
| 注解式映射 |
侵入性低、支持复杂嵌套、配置直观 |
字段多、嵌套深的对象(如接口参数) |
Hutool 反射工具 |
KV 配置式映射 |
配置集中、无需修改实体类 |
简单对象(如 VO 与数据库实体转换) |
Hutool BeanUtil |
Map 扁平化工具 |
解决多层结构转单层需求 |
URL 参数、日志打印、简单配置输出 |
Hutool 基础工具 |
优先选择注解式映射: 对于大多数业务场景(尤其是接口数据同步、复杂参数转换),注解式映射的灵活性和可读性更优;仅在无需修改实体类(如第三方提供的 VO)或字段极少时,考虑 KV 配置式映射。