标签:跳过 time 转移 for eth name 设置 更新 exception
本文针对java开发且采用前后端分离的开发模式,非java开发可能作用不大。同时数据库以mysql为例,部分表述只做示例,并非严谨的mysql语句。
普通的业务系统开发过程中,下面描述的这种需求应该是比较常见的。一个申请单,需要显示申请人名字,审核人名字。
这里涉及到两张表:申请单(t_apply), 用户(t_user),后台数据表我们可能会这么设计:
// 方案一
t_apply(
apply_id,
apply_no,
***
apply_user_id,
apply_user_name,
apply_auditor_id,
apply_auditor_name
***
)
t_user(
user_id,
user_name
)
也可能这么设计
// 方案二
t_apply(
apply_id,
apply_no,
***
apply_user_id,
apply_auditor_id
***
)
t_user(
user_id,
user_name
)
方案一冗余了申请人名字和审核人名字字段,很好,在查询的时候不需要连表查询。但要考虑,如果这个用户改名了呢,申请单的名字要不要做修改?如果不需要,保存并在后期显示当时的快照即可,那么本文可以跳过了。如果需要,那么冗余就显得没有必要了。因为这里更新的成本有些大。
方案二存的是ID,没有冗余的字段,很好,只是查询的时候要连表查。稍显麻烦。
连表对于数据库,是一个比较大的性能开销。多数企业做应用架构并未考虑读写分离,连表过多更会产生影响,阿里的开发规范也有提到连表查询最好不要超过3张表,尤其是这种非核心但又非得要的信息,连表查询更加显得不重要且耗数据库性能。
方案W:把问题丢给前端。后台做基础数据查询,申请单、用户都返回,前端根据用户ID,去找用户名字,然后显示。这种方式,估计前端看了想打人。
方案S:把数据库的性能转移给java。不连表查询,任何查询都不连表。举个例子,对于申请单列表:
如此,将数据库的压力转移给了java。这点查询与循环,对于java来说,还是没啥问题的。然而,此刻java开发人员就没有那么高兴了。不让连表查询,每次这么查询遍历,够累的。
这个填塞的过程其实是很常见的,比如再来一个公司,申请单有一个归属公司 company_id , 公司名字存在基础表 t_company。申请单要显示公司名字,java开发人员在用方案S进行数据查询的时候,公司和用户的操作,步骤完全一致,只是对应的实体不同。
因为懒,不想写重复的代码,哪怕是重复逻辑的代码,所以就得想想办法
好,来说说本人想到的方案:那就是抽离这一部分填塞的业务,用一个横切来实现。把方案W和方案S结合一下。基础数据(如用户)提供基本查询方法,业务开发(如申请单)调用基本查询方法,实现数据的填塞。
2个注解,一个加在视图对象字段上,表达该字段需要从别的表中查。一个加在控制层方法上,表达该方法需要处理返回值字段连表查询。
字段层注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Converted {
@ApiParam("依赖字段,根据当前类实体存储的字段,得到目标字段")
String dependProperty();
@ApiParam("BeanService,如 UserService.class")
Class<? extends Object> bean();
@ApiParam("形式为:List<T> refMethod(List<dependProperty>) , 或者Map<String,String> refMethod(List<dependProperty>)的关联实体方法")
String refMethod() default "listByIds";
@ApiParam("关联实体组成Map的key值")
String refKey() default "id";
@ApiParam("如果method返回值为List<T>使用T.getLabel()如T.getName() 给当前关联实体赋值")
String refLabel() default "name";
@ApiParam("关联实体组成Map的key值类型")
Class<? extends Object> refKeyClass();
@ApiParam("未能成功转换给的默认值")
String defaultValue() default "";
}
以刚刚的申请单为例,解释下各个标注含义。
方法层注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldConversion {
}
没啥好说的,做个控制,不是所有的方法都需要连表查,加上才来查。
好,接下来看下切片处理咋写,思路跟方案S差不多。这部分内容太长,我展示下关键代码,主要是用反射取值,设置,加一些泛型处理。
/**
* Created by tuofan
*/
@SuppressWarnings("unchecked")
public class FieldConvertUtils {
private static Logger logger = LoggerFactory.getLogger(FieldConvertUtils.class);
private FieldConvertUtils() {
}
/**
* @param list
*/
public static <T> void convertList(List list) throws NoSuchFieldException, NoSuchMethodException {
if (CollectionUtils.isEmpty(list)) {
return;
}
if (list.get(0) == null) {
return;
}
Field[] fields = FieldConvertUtils.getFields(list.get(0));
if (fields == null || fields.length == 0) {
return;
}
Map<Field, Map<T, String>> refBeanValueMap = FieldConvertUtils.getFieldFeignValueMap(list, fields);
FieldConvertUtils.convertValue(list, fields, refBeanValueMap);
}
private static Field[] getFields(Object object) {
return object.getClass().getDeclaredFields();
}
/**
* 获取列表中要转换所有key和value
*
* @param list
* @param fields
* @return
*/
private static <T> Map<Field, Map<T, String>> getFieldFeignValueMap(List list, Field[] fields) throws NoSuchFieldException, NoSuchMethodException {
// 存放每个字段,转换前和转换后的对应值
Map<Field, Map<T, String>> refFiledValueMap = Maps.newHashMap();
for (Field field : fields) {
Converted converted = field.getAnnotation(Converted.class);
if (converted == null) {
continue;
}
List<T> keyList = extractList(list, field);
Map<T, String> keys2ValuesMap = convertKeys2Values(keyList, field);
refFiledValueMap.put(field, keys2ValuesMap);
}
return refFiledValueMap;
}
private static <T> Map<T, String> convertKeys2Values(List<T> keys, Field field) throws NoSuchMethodException, NoSuchFieldException {
if (CollectionUtils.isEmpty(keys)) {
return Maps.newHashMap();
}
Converted converted = field.getAnnotation(Converted.class);
Object beanService = SpringUtils.getBean(converted.bean());
Object[] args = {keys};
Method refMethod = getMethod(beanService.getClass(), converted.refMethod());
Class returnClazz = refMethod.getReturnType();
// 返回值是map
if (returnClazz.isAssignableFrom(Map.class)) {
return (Map<T, String>) ReflectionUtils.invokeMethod(refMethod, beanService, args);
}
// 返回值是list
if (returnClazz.isAssignableFrom(Collection.class)) {
Collection collection = (Collection) ReflectionUtils.invokeMethod(refMethod, beanService, args);
if (CollectionUtils.isEmpty(collection)) {
return Maps.newHashMap();
}
return convertList2MapFilterNull(collection, converted.refKey(),
converted.refLabel());
}
logger.error("返回值类型={}暂不支持转换,目前仅支持 Collection 和 Map ", returnClazz.getName());
return Maps.newHashMap();
}
private static <T> Map<T, String> convertList2MapFilterNull(Collection<?> collection, String keyProperty, String valueProperty) throws NoSuchFieldException {
Map<T, String> map = Maps.newHashMap();
for (Object ele : collection) {
Field fKey = ele.getClass().getDeclaredField(keyProperty);
T key = extractTValue(ele, fKey);
Field fValue = ele.getClass().getDeclaredField(valueProperty);
ReflectionUtils.makeAccessible(fValue);
String value = (String) ReflectionUtils.getField(fValue, ele);
map.put(key, value);
}
return map;
}
/**
* 作为beanService 的参数
*/
private static <T> List<T> extractList(List list, Field field) throws NoSuchFieldException {
List<T> keyList = Lists.newArrayList();
Converted converted = field.getAnnotation(Converted.class);
for (Object returnValue : list) {
// 获取要转换的key值
T key = extractKey(returnValue, converted);
if (key != null) {
keyList.add(key);
}
}
return keyList;
}
/**
* 获取key,注解上有dependProperty属性,则取这个属性的值,否则就是当前filed的值
*
* @param returnValue
* @param converted
* @return
*/
private static <T> T extractKey(Object returnValue, Converted converted) throws NoSuchFieldException {
Field field = returnValue.getClass().getDeclaredField(converted.dependProperty());
return extractTValue(returnValue, field);
}
private static <T> T extractTValue(Object returnValue, Field field) throws NoSuchFieldException {
ReflectionUtils.makeAccessible(field);
Object obj = ReflectionUtils.getField(field, returnValue);
if (obj == null) {
return null;
}
return (T) obj;
}
/**
* 根据取到的值进行转换
*
* @param list
* @param refFiledValueMap
*/
private static <T> void convertValue(List list, Field[] fields, Map<Field, Map<T, String>> refFiledValueMap) throws NoSuchFieldException {
for (Object returnValue : list) {
for (Field field : fields) {
Converted converted = field.getAnnotation(Converted.class);
if (converted == null) {
continue;
}
T key = extractKey(returnValue, converted);
ReflectionUtils.makeAccessible(field);
if (refFiledValueMap.containsKey(field) && refFiledValueMap.get(field).containsKey(key)) {
ReflectionUtils.setField(field, returnValue, refFiledValueMap.get(field).get(key));
} else {
ReflectionUtils.setField(field, returnValue, converted.defaultValue());
}
}
}
}
/**
* @param clazzT
* @param methodName
* @return
*/
private static Method getMethod(Class clazzT, String methodName) {
if (clazzT == null || clazzT == Object.class || StringUtils.isEmpty(methodName)) {
return null;
}
for (; clazzT.getSuperclass() != Object.class; clazzT = clazzT.getSuperclass()) {
Method[] methods = clazzT.getDeclaredMethods();
for (Method m : methods) {
if (m.getName().equals(methodName)) {
return m;
}
}
}
return null;
}
}
vo字段上加注解
@ApiModelProperty(value = "审核人ID")
private Long auditorId;
@Converted(dependProperty = "auditorId", refKeyClass = Integer.class, bean = MobileUserService.class, refLabel = "userName")
@ApiModelProperty(value = "审核人姓名")
private String auditorName;
controller上加标签
@PostMapping("listPage")
@FieldConversion
public ResultVO<IPage<ModelVO>> listPage(@RequestBody ModelQuery modelQuery) {
return ** 省略业务逻辑代码 **;
}
总的来说,就是将公共的操作抽象出来,用切片的方式实现,让代码更加整洁。
标签:跳过 time 转移 for eth name 设置 更新 exception
原文地址:https://www.cnblogs.com/tuofan/p/11378856.html