在使用jdbcTemplate时,查询结果转为POJO对象可以使用BeanPropertyRowMapper
,观察这个类可以发现,主要是利用DefaultConversionService
和PropertyDescriptor
这两个Spring的工具,实现属性转换和属性注入;
但是BeanPropertyRowMapper的性能不太好,做了很多安全校验工作,这些工作对于我们转换没有太大的用处,因此笔者基于BeanPropertyRowMapper开发了一个快速将Map转为Pojo的工具类,代码如下:
@Slf4j
public class EntityMapper {
private static final ConcurrentHashMap<Class<?>, Map<String, PropertyDescriptor>> CACHE = new ConcurrentHashMap<>(64);
/** ConversionService for binding JDBC values to bean properties. */
private static final DefaultConversionService conversionService = new DefaultConversionService();
static {
//使用默认转换器,并增加时间转换器
conversionService.addConverter(String.class, LocalDateTime.class,
value -> LocalDateTime.parse(value, ISO_LOCAL_DATE_TIME));
conversionService.addConverter(String.class, OffsetDateTime.class,
value -> OffsetDateTime.parse(value, ISO_OFFSET_DATE_TIME));
conversionService.addConverter(String.class, LocalDate.class,
value -> LocalDate.parse(value, ISO_LOCAL_DATE));
conversionService.addConverter(String.class, LocalTime.class,
value -> LocalTime.parse(value, ISO_LOCAL_TIME));
conversionService.addConverter(Timestamp.class, LocalDateTime.class,
value -> value.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime());
conversionService.addConverter(String.class, Date.class,
value -> {
ZoneId zoneId = ZoneId.systemDefault();
Instant instant = LocalDateTime.parse(value, ISO_LOCAL_DATE_TIME).atZone(zoneId).toInstant();
return Date.from(instant);
});
}
/**
* 增加自定义转换器
*/
public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) {
conversionService.addConverter(sourceType, targetType, converter);
}
/**
* map转为对象
*
* @param map map
* @param mappedClass 对象类型
* @return 对象
*/
public static <T> T mapToEntity(Map<String, Object> map, Class<T> mappedClass) {
T mappedObject = BeanUtils.instantiateClass(mappedClass);
Map<String, PropertyDescriptor> mappedFields = getMappedFields(mappedClass);
return populate(map, mappedObject, mappedFields);
}
/**
* 转为list
*
* @param mapList map list
* @param mappedClass 对象类型
* @return 对象列表
*/
public static <T> List<T> mapToEntityList(List<Map<String, Object>> mapList, Class<T> mappedClass) {
if (mapList == null || mapList.isEmpty()) {
return Collections.emptyList();
}
Map<String, PropertyDescriptor> mappedFields = getMappedFields(mappedClass);
List<T> list = new ArrayList<>(mapList.size());
for (Map<String, Object> map : mapList) {
T mappedObject = BeanUtils.instantiateClass(mappedClass);
populate(map, mappedObject, mappedFields);
list.add(mappedObject);
}
return list;
}
/**
* 将map的属性复制到mappedObject对象上
*
* @param map map
* @param mappedObject 目标对象
* @param mappedFields 对象类属性
* @return 目标对象
*/
private static <T> T populate(Map<String, Object> map, T mappedObject, Map<String, PropertyDescriptor> mappedFields) {
Set<Map.Entry<String, Object>> entrySet = map.entrySet();
try {
for (Map.Entry<String, Object> entry : entrySet) {
String column = entry.getKey();
Object value = entry.getValue();
String field = lowerCaseName(column);
PropertyDescriptor pd = mappedFields.get(field);
if (pd == null) {
continue;
}
Method writeMethod = pd.getWriteMethod();
Class<?> propertyType = pd.getPropertyType();
Class<?> valueClass = value.getClass();
Object convertValue;
//如果类型相同或者value是子类
if (propertyType.isAssignableFrom(valueClass)) {
convertValue = value;
}
//如果value是map
else if (value instanceof Map) {
convertValue = mapToEntity((Map<String, Object>) value, propertyType);
}
//如果可以转换
else if (conversionService.canConvert(valueClass, propertyType)) {
convertValue = conversionService.convert(value, propertyType);
}
//其他情况,抛出异常
else {
log.info("- cannot convert value {}, source type = {}, target type = {}", value, valueClass, propertyType);
throw new RuntimeException("无法转换");
}
writeMethod.invoke(mappedObject, convertValue);
}
} catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException("convert map to pojo error", ex);
}
return mappedObject;
}
/**
* 从缓存中获取
* @param mappedClass
* @param <T>
* @return
*/
private static <T> Map<String, PropertyDescriptor> getMappedFields(Class<T> mappedClass) {
Map<String, PropertyDescriptor> descriptorMap = CACHE.get(mappedClass);
if (descriptorMap != null) {
return descriptorMap;
}
return addEntityClass(mappedClass);
}
/**
* 增加实体类
* @param mappedClass
*/
public static Map<String, PropertyDescriptor> addEntityClass(Class<?> mappedClass) {
return CACHE.computeIfAbsent(mappedClass, key -> {
PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
Map<String, PropertyDescriptor> mappedFields = new HashMap<>();
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
mappedFields.put(lowerCaseName(pd.getName()), pd);
String underscoredName = underscoreName(pd.getName());
if (!lowerCaseName(pd.getName()).equals(underscoredName)) {
mappedFields.put(underscoredName, pd);
}
}
}
return mappedFields;
});
}
/**
* Convert a name in camelCase to an underscored name in lower case.
* Any upper case letters are converted to lower case with a preceding underscore.
* @param name the original name
* @return the converted name
* @since 4.2
* @see #lowerCaseName
*/
private static String underscoreName(String name) {
if (!StringUtils.hasLength(name)) {
return "";
}
StringBuilder result = new StringBuilder();
result.append(lowerCaseName(name.substring(0, 1)));
for (int i = 1; i < name.length(); i++) {
String s = name.substring(i, i + 1);
String slc = lowerCaseName(s);
if (!s.equals(slc)) {
result.append("_").append(slc);
}
else {
result.append(s);
}
}
return result.toString();
}
/**
* Convert the given name to lower case.
* By default, conversions will happen within the US locale.
* @param name the original name
* @return the converted name
* @since 4.2
*/
private static String lowerCaseName(String name) {
return name.toLowerCase(Locale.US);
}
}
使用方法:
String time = ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
ImmutableMap<String, Object> map =
ImmutableMap.of("name", "AAA",
"age", 12,
"birthday", time);
User user = EntityMapper.mapToEntity(map, User.class);
System.out.println(user);