// 判断用户 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 方法内部,统一了异常抛出行为,避免了在多个地方处理相同的逻辑错误,减少了潜在的错误源。