📚项目场景:
修改数据时,允许将非必填字段清空。
⛔问题描述:
由于使用的是Mybatis-Plus
,只能使用LambdaUpdateWrapper
或UpdateWrapper
通过set(column,val)
来将字段清空;因为字段太多导致大量set放在一个方法,不符合部分规范,所以封装了一个SqlUtils
工具来对字段进行set。
有人会问,为什么不用Mybatis-Plus
的@TableField(updateStrategy = FieldStrategy.IGNORED)
?这不是可以直接加上注解修改的时候就会自己添加置空进去吗?
使用TableField
注解确实可以达到你说的效果,但是得区分项目的整体情况;如果整个项目都是使用Mybatis-Plus
的LambdaUpdateWrapper
来对数据的状态或者价格等字段进行更新的话,那么这个注解的可行性就少了一部分。因为有些场景下你只需要对某个字段进行更新而不是更新所有字段,例如:我这是一张申请单据,对应人送审成功,我需要更新未处理
状态到处理中
,这种情况下我们是不需要更新状态字段之外的其他字段,如果使用这种方式则会把其他数据清空。除非在更新前查一遍数据,然后拿这份数据去修改,这样对程序和数据库来说增加压力!
Java
/**
* sql处理工具
*
* @author Smallink *
* @version Id: SqlUtils, v 0.1 2023/8/22 11:59 Smallink Exp $
*/
@Slf4j
@Component
public class SqlUtils<T> {
private static final int FLAG_SERIALIZABLE = 1;
private static final List<String> DEFAULT_EXCLUSION_FIELD = Arrays.asList("id", "createBy", "createName", "createTime", "updateBy", "updateName", "updateTime");
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @return LambdaUpdateChainWrapper
*/
public static UpdateWrapper packSqlUtils(UpdateWrapper<?> wrapper, Object object) {
return (UpdateWrapper) packSqlUtils(wrapper, object, false, new ArrayList<>());
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @param fieldList 不需要拼入的字段
* @return LambdaUpdateChainWrapper
*/
public static UpdateWrapper packSqlUtils(UpdateWrapper<?> wrapper, Object object, List<String> fieldList) {
return (UpdateWrapper) packSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @param ignoreNullValue 是否忽略值为空的字段
* @param fieldList 不需要拼入的字段
* @return LambdaUpdateChainWrapper
*/
public static Update packSqlUtils(Update wrapper, Object object, boolean ignoreNullValue, List<String> fieldList) {
if (ObjectUtils.isEmpty(object) || ObjectUtils.isEmpty(wrapper)) {
log.info("mandatory parameters cannot be empty");
throw new BizException(ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getErrCode(), ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getMsg());
}
// 添加默认排除字段
fieldList.addAll(DEFAULT_EXCLUSION_FIELD);
Class<?> aClass = object.getClass();
// 将对象转换成Map
Map<String, Object> objectMap = BeanUtil.beanToMap(object, new LinkedHashMap<>(), ignoreNullValue, key -> fieldList.contains(key) ? null : StrUtil.toUnderlineCase(key));
// 将对象当需要修改值写入
objectMap.forEach((key, value) -> {
String fieldType = getFieldType(CamelUnderLineUtils.underLineToCamel(key), aClass);
if (StringUtils.isNotEmpty(fieldType)) {
wrapper.set(key, value);
}
});
return wrapper;
}
// --------------------------------------------- Lambda
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @return LambdaUpdateChainWrapper
*/
public LambdaUpdateWrapper packLambdaSqlUtils(LambdaUpdateWrapper<T> wrapper, T object) {
return (LambdaUpdateWrapper) packLambdaSqlUtils(wrapper, object, false, new ArrayList<>());
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @return LambdaUpdateChainWrapper
*/
public LambdaUpdateChainWrapper packLambdaSqlUtils(LambdaUpdateChainWrapper<T> wrapper, T object) {
return (LambdaUpdateChainWrapper) packLambdaSqlUtils(wrapper, object, false, new ArrayList<>());
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @param fieldList 不需要拼入的字段
* @return LambdaUpdateChainWrapper
*/
public LambdaUpdateWrapper packLambdaSqlUtils(LambdaUpdateWrapper<T> wrapper, T object, List<String> fieldList) {
return (LambdaUpdateWrapper) packLambdaSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @param fieldList 不需要拼入的字段
* @return LambdaUpdateChainWrapper
*/
public LambdaUpdateChainWrapper packLambdaSqlUtils(LambdaUpdateChainWrapper<T> wrapper, T object, List<String> fieldList) {
return (LambdaUpdateChainWrapper) packLambdaSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
}
/**
* 将为空字段拼入sql
*
* @param wrapper 封装器
* @param object 需要拼入对象
* @param ignoreNullValue 是否忽略值为空的字段
* @param fieldList 不需要拼入的字段
* @return LambdaUpdateChainWrapper
*/
public Update packLambdaSqlUtils(Update<?, SFunction<T, ?>> wrapper, T object, boolean ignoreNullValue, List<String> fieldList) {
if (ObjectUtils.isEmpty(object) || ObjectUtils.isEmpty(wrapper)) {
log.info("mandatory parameters cannot be empty");
throw new BizException(ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getErrCode(), ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getMsg());
}
// 添加默认排除字段
fieldList.addAll(DEFAULT_EXCLUSION_FIELD);
Class<?> aClass = object.getClass();
// 将对象转换成Map
Map<String, Object> objectMap = BeanUtil.beanToMap(object, new LinkedHashMap<>(), ignoreNullValue, key -> fieldList.contains(key) ? null : key);
// 将对象当需要修改值写入
objectMap.forEach((key, value) -> {
String fieldType = getFieldType(key, aClass);
SFunction sFunction = StringToFunction(key, fieldType, aClass);
wrapper.set(sFunction, value);
});
return wrapper;
}
/**
* 将字段名转换为对应的函数式
*
* @param name 字段名
* @param fieldType 字段类型
* @param entityClass 类
* @return
*/
private static SFunction StringToFunction(String name, String fieldType, Class<?> entityClass) {
Class<?> rType = getRType(fieldType);
SFunction func;
String getMethod = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
final MethodHandles.Lookup lookup = MethodHandles.lookup();
//po的返回Integer的一个方法
MethodType methodType = MethodType.methodType(rType, entityClass);
final CallSite site;
try {
//方法名叫做:getSecretLevel 转换为 SFunction function interface对象
site = LambdaMetafactory.altMetafactory(lookup,
"invoke",
MethodType.methodType(SFunction.class),
methodType,
lookup.findVirtual(entityClass, getMethod, MethodType.methodType(rType)),
methodType, FLAG_SERIALIZABLE);
func = (SFunction) site.getTarget().invokeExact();
//数据小于这个级别的都查出来
// mpjLambdaWrapper.le(func, secretLevel);
return func;
} catch (Throwable e) {
log.error("获取getSecretLevel方法错误", e);
}
return null;
}
/**
* 类型转换
*
* @param fieldType 类型
* @return
*/
private static Class<?> getRType(String fieldType) {
Class<?> rtype = null;
switch (fieldType) {
case "class java.lang.String":
rtype = String.class;
break;
case "class java.lang.Integer":
rtype = Integer.class;
break;
case "class java.lang.Double":
rtype = Double.class;
break;
case "class java.lang.Boolean":
rtype = Boolean.class;
break;
case "class java.util.Date":
rtype = Date.class;
break;
case "class java.time.LocalDate":
rtype = LocalDate.class;
break;
case "class java.time.LocalDateTime":
rtype = LocalDateTime.class;
break;
case "class java.math.BigDecimal":
rtype = BigDecimal.class;
break;
case "class java.lang.Long":
rtype = Long.class;
break;
}
return rtype;
}
/**
* 获取字段类型
*
* @param fieldName 字段名
* @param clazz 类
* @return
*/
private static String getFieldType(String fieldName, Class<?> clazz) {
Field[] fields = ReflectUtils.getAllFields(clazz);
for (Field f : fields) {
boolean exist = false;
TableField tableField = f.getAnnotation(TableField.class);
if (tableField != null) {
exist = !tableField.exist();
}
if (fieldName.equals(f.getName()) && !exist) {
f.setAccessible(true);
return f.getGenericType().toString();
}
}
return null;
}
}
工具是支持传入参数来排除指定字段修改为空(没有选择使用自定义注解,自定义注解增加了功能的复杂难度与维护,所以使用了最便捷的方式
),以下是示例 需要结合SqlUtils
工具看
Java
@Resource
private SqlUtils sqlUtils;
// 对象中不需要封装的字段名,
public static List<String> SALE_QUOTE_EXCLUSION_FIELD = Arrays.asList("customerNo","customerName","quoteStatus","productCategory","productType","deliveryMethod");
public BusinessResponse updateQuote(Object obj) {
LambdaUpdateWrapper<Object> detailPOLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
// 调用SqlUtils工具来封装需要置空的字段
sqlUtils.packLambdaSqlUtils(detailPOLambdaUpdateWrapper, obj, SALE_QUOTE_DATEIL_EXCLUSION_FIELD);
detailPOLambdaUpdateWrapper.eq(BasePO::getId, id);
baseMapper.update(new Object(), detailPOLambdaUpdateWrapper);
}
除了指定排除字段之外,还有默认字段所以使用了add()来添加,然后就导致了异常:
乍一看,这不是空指针嘛,so easy啊(内心os:明明就有值,怎么会报这个错误呢?而且编译时也没有报错)
仔细一瞧,这UnsupportedOperationException
是个什么玩意?
Java
java.lang.UnsupportedOperationException: null
at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_151]
at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_151]
🔍原因分析:
秉持出现问题,解决问题的思想 (哈哈哈
)。断点调试后,还是有问题;接下来就是看源码了,请展示:
Java
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
private static final long serialVersionUID = -2764017481108945198L;
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
@Override
public int size() {
return a.length;
}
@Override
public Object[] toArray() {
return a.clone();
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
int size = size();
if (a.length < size)
return Arrays.copyOf(this.a, size,
(Class<? extends T[]>) a.getClass());
System.arraycopy(this.a, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
@Override
public E get(int index) {
return a[index];
}
@Override
public E set(int index, E element) {
E oldValue = a[index];
a[index] = element;
return oldValue;
}
@Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
@Override
public boolean contains(Object o) {
return indexOf(o) != -1;
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(a, Spliterator.ORDERED);
}
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (E e : a) {
action.accept(e);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
E[] a = this.a;
for (int i = 0; i < a.length; i++) {
a[i] = operator.apply(a[i]);
}
}
@Override
public void sort(Comparator<? super E> c) {
Arrays.sort(a, c);
}
}
可以从源码中看出,此ArrayList
非彼java.util.ArrayList
Arrays.asList()使用的是类部类中的ArrayList
;它继承的是AbstractList
,并没有实现AbstractList
里面的add()
方法!!!
看到这里相信各位同学应该知道问题了,没错就是没有实现里面的add()
所以导致了《空指针》
异常🌚
💡问题解决:
既然知道了问题,那么就好解决了!
换一个初始化List集合的工具就行啦!
我换成了hutool的CollectionUtil.toList
📝问题总结:
经验太少...哈哈哈哈