闽公网安备 35020302035485号
// 判断用户 ID 是否有效
public void checkUserExistence(String userId) {
User user = userDao.findById(userId);
if (user == null) {
throw new RuntimeException("用户ID无效");
}
}
// 堆代码 duidaima.com
// 判断部门 ID 是否有效
public void checkDeptExistence(String deptId) {
Dept dept = deptDao.findById(deptId);
if (dept == null) {
throw new RuntimeException("部门ID无效");
}
}
Java 8 的魔法棒:函数式接口/**
* 确认数据库字段值有效(通用)
*
* @param <V> 待验证值的类型
* @param valueToCheck 待验证的值
* @param columnExtractor 实体类属性提取函数
* @param queryExecutor 单条数据查询执行器
* @param errorMessage 异常提示信息模板
*/
public static <T, R, V> void ensureColumnValueValid(V valueToCheck, SFunction<T, R> columnExtractor, SFunction<LambdaQueryWrapper<T>, T> queryExecutor, String errorMessage) {
if (valueToCheck == null) return;
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
wrapper.select(columnExtractor);
wrapper.eq(columnExtractor, valueToCheck);
wrapper.last("LIMIT 1");
T entity = queryExecutor.apply(wrapper);
R columnValue = columnExtractor.apply(entity);
if (entity == null || columnValue == null)
throw new DataValidationException(String.format(errorMessage, valueToCheck));
}
这个方法接受一个待验证的值、一个实体类属性提取函数、一个单行数据查询执行器和一个异常信息模板作为参数。通过这四个参数,不仅能够进行针对特定属性的有效性检查,而且还能生成具有一致性的异常信息。// 判断用户 ID 是否有效
public void checkUserExistence(String userId) {
User user = userDao.findById(userId);
if (user == null) {
throw new RuntimeException("用户ID无效");
}
}
// 判断部门 ID 是否有效
public void checkDeptExistence(String deptId) {
Dept dept = deptDao.findById(deptId);
if (dept == null) {
throw new RuntimeException("部门ID无效");
}
}
使用 Function 改造后public void assignTaskToUser(AddOrderDTO dto) {
ensureColumnValueValid(dto.getUserId(), User::getId, userDao::getOne, "用户ID无效");
ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效");
ensureColumnValueValid(dto.getCustomerId(), Customer::getId, customerDao::getOne, "客户ID无效");
ensureColumnValueValid(dto.getDeptId(), Dept::getId, deptDao::getOne, "部门ID无效");
ensureColumnValueValid(dto.getSupplieId(), Supplie::getId, supplierDao::getOne, "供应商ID无效");
// 现在可以确信客户存在
Customer cus = customerDao.findById(dto.getCustomerId());
// 创建订单的逻辑...
}
对比上述两段代码,我们发现后者不仅大幅减少了代码量,而且通过函数式编程,表达出更为清晰的逻辑意图,可读性和可维护性都有所提高。灵活性和扩展性: 当校验规则发生变化时,只需要调整 ensureColumnValueValid 方法或其内部实现,所有调用该方法的地方都会自动受益,提高了系统的灵活性和扩展性。
通过上述的实践,我们见识到了函数式编程在简化数据校验逻辑方面的威力。但这只是冰山一角,我们可以根据不同的业务场景,继续扩展和完善校验逻辑,实现更多样化的校验需求。以下两个示例展示了如何在原有基础上进一步深化,实现更复杂的数据比较和验证功能。
/**
* 验证查询结果中指定列的值是否与预期值匹配
*
* @param <T> 实体类型
* @param <R> 目标列值的类型
* @param <C> 查询条件列值的类型
* @param targetColumn 目标列的提取函数,用于获取想要验证的列值
* @param expectedValue 期望的列值
* @param conditionColumn 条件列的提取函数,用于设置查询条件
* @param conditionValue 条件列对应的值
* @param queryMethod 执行查询的方法引用,返回单个实体对象
* @param errorMessage 验证失败时抛出异常的错误信息模板
* @throws RuntimeException 当查询结果中目标列的值与预期值不匹配时抛出异常
*/
public static <T, R, C> void validateColumnValueMatchesExpected(
SFunction<T, R> targetColumn, R expectedValue,
SFunction<T, C> conditionColumn, C conditionValue,
SFunction<LambdaQueryWrapper<T>, T> queryMethod,
String errorMessage) {
// 创建查询包装器,选择目标列并设置查询条件
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
wrapper.select(targetColumn);
wrapper.eq(conditionColumn, conditionValue);
// 执行查询方法
T one = queryMethod.apply(wrapper);
// 如果查询结果为空,则直接返回,视为验证通过(或忽略)
if (one == null) return;
// 获取查询结果中目标列的实际值
R actualValue = targetColumn.apply(one);
// 比较实际值与预期值是否匹配,这里假设notMatch是一个自定义方法用于比较不匹配情况
boolean doesNotMatch = notMatch(actualValue, expectedValue);
if (doesNotMatch) {
// 若不匹配,则根据错误信息模板抛出异常
throw new RuntimeException(String.format(errorMessage, expectedValue, actualValue));
}
}
// 假设的辅助方法,用于比较值是否不匹配,根据实际需要实现
private static <R> boolean notMatch(R actual, R expected) {
// 示例简单实现为不相等判断,实际情况可能更复杂
return !Objects.equals(actual, expected);
}
这个方法允许我们指定一个查询目标列(targetColumn)、预期值(expectedValue)、查询条件列(conditionColumn)及其对应的条件值(conditionValue),并提供一个查询方法(queryMethod)来执行查询。如果查询到的列值与预期不符,则抛出异常,错误信息通过 errorMessage 参数定制。validateColumnValueMatchesExpected(User::getRoleType, "普通用户", User::getId, userId, userMapper::getOne, "用户角色不是普通用户,无法升级为管理员!");
/**
* 验证查询结果中指定列的值是否位于预期值列表内
*
* @param <T> 实体类型
* @param <R> 目标列值的类型
* @param <C> 查询条件列值的类型
* @param targetColumn 目标列的提取函数,用于获取想要验证的列值
* @param expectedValueList 期望值的列表
* @param conditionColumn 条件列的提取函数,用于设置查询条件
* @param conditionValue 条件列对应的值
* @param queryMethod 执行查询的方法引用,返回单个实体对象
* @param errorMessage 验证失败时抛出异常的错误信息模板
* @throws RuntimeException 当查询结果中目标列的值不在预期值列表内时抛出异常
*/
public static <T, R, C> void validateColumnValueInExpectedList(
SFunction<T, R> targetColumn, List<R> expectedValueList,
SFunction<T, C> conditionColumn, C conditionValue,
SFunction<LambdaQueryWrapper<T>, T> queryMethod,
String errorMessage) {
LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
wrapper.select(targetColumn);
wrapper.eq(conditionColumn, conditionValue);
T one = queryMethod.apply(wrapper);
if (one == null) return;
R actualValue = targetColumn.apply(one);
if (actualValue == null) throw new RuntimeException("列查询结果为空");
if (!expectedValueList.contains(actualValue)) {
throw new RuntimeException(errorMessage);
}
}
这个方法接受一个目标列(targetColumn)、一个预期值列表(expectedValueList)、查询条件列(conditionColumn)及其条件值(conditionValue),同样需要一个查询方法(queryMethod)。如果查询到的列值不在预期值列表中,则触发异常。
// 假设 OrderStatusEnum 枚举了所有可能的订单状态,cancelableStatuses 包含可取消的状态 List<String> cancelableStatuses = Arrays.asList(OrderStatusEnum.WAITING_PAYMENT.getValue(), OrderStatusEnum.WAITING_DELIVERY.getValue()); // 验证订单状态是否在可取消状态列表内 validateColumnValueInExpectedList(Order::getStatus, cancelableStatuses, Order::getOrderId, orderId, orderMapper::selectOne, "订单当前状态不允许取消!");
通过这两个扩展方法,我们不仅巩固了函数式编程在减少代码重复、提升代码灵活性方面的优势,还进一步证明了通过抽象和泛型设计,可以轻松应对各种复杂的业务校验需求,使代码更加贴近业务逻辑,易于理解和维护。
异常处理集中于 ensureColumnValueValid 方法内部,统一了异常抛出行为,避免了在多个地方处理相同的逻辑错误,减少了潜在的错误源。