Refactoring Data Validation with Java 8 Function and SFunction Interfaces
This article shows how to eliminate repetitive database validation code by creating generic reusable methods using Java 8 functional interfaces and MyBatis Plus SFunction, demonstrating ensureColumnValueValid, validateColumnValueMatchesExpected, and validateColumnValueInExpectedList with before-and-after comparisons.
Introduction
In Java development, repetitive validation logic often leads to bloated, hard-to-maintain code. Java 8's functional programming features, especially the Function<T, R> interface and MyBatis Plus's SFunction (a lambda wrapper), provide a powerful way to abstract and reuse such logic.
Background: The Problem of Duplicate Validation
Typical validation methods for checking entity existence (e.g., user ID, department ID) are nearly identical, differing only in the entity type, DAO, and error message. This results in copy-paste code that is difficult to maintain.
// Before: repetitive methods
public void checkUserExistence(String userId) {
User user = userDao.findById(userId);
if (user == null) {
throw new RuntimeException("用户ID无效");
}
}
public void checkDeptExistence(String deptId) {
Dept dept = deptDao.findById(deptId);
if (dept == null) {
throw new RuntimeException("部门ID无效");
}
}Java 8 Functional Interfaces and SFunction
Function<T, R>takes an input of type T and returns R. MyBatis Plus's SFunction further encapsulates lambda expressions, enabling type-safe property references like User::getId.
Practical Refactoring: ensureColumnValueValid
A generic method that validates any entity column value:
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));
}Parameters: value to check, column extractor (e.g., User::getId), query executor (e.g., userDao::getOne), and error message template.
Comparison: Before vs. After
After refactoring, a single method call replaces each duplicated validation:
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.getSupplieId(), Supplie::getId, supplierDao::getOne, "供应商ID无效");
// proceed with order creation
}The refactored version drastically reduces code volume, clarifies intent, and centralizes error handling.
Core Advantages
Code Reuse: One generic method handles all column validity checks.
Clear Intent: Method signature and lambda arguments make the purpose obvious.
Flexibility: Callers supply only the varying parts; the underlying query logic stays hidden.
Maintainability: Changes to validation rules (e.g., exception type, logging) are made in one place.
Extending the Pattern: More Complex Validations
validateColumnValueMatchesExpected
Verifies that a queried column equals an expected value (e.g., user role before promotion).
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);
if (!Objects.equals(actualValue, expectedValue))
throw new RuntimeException(String.format(errorMessage, expectedValue, actualValue));
}Usage example: ensure a user's current role is "普通用户" before upgrading to admin.
validateColumnValueMatchesExpected(User::getRoleType, "普通用户",
User::getId, userId, userMapper::getOne,
"用户角色不是普通用户,无法升级为管理员!");validateColumnValueInExpectedList
Checks that a column value belongs to a predefined list (e.g., order status must be cancellable).
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);
}Usage example: verify order status is in a cancellable list.
List<String> cancelableStatuses = Arrays.asList(
OrderStatusEnum.WAITING_PAYMENT.getValue(),
OrderStatusEnum.WAITING_DELIVERY.getValue());
validateColumnValueInExpectedList(Order::getStatus, cancelableStatuses,
Order::getOrderId, orderId, orderMapper::selectOne,
"订单当前状态不允许取消!");Conclusion
By leveraging Java 8 functional interfaces and MyBatis Plus SFunction, developers can eliminate boilerplate validation code, improve readability, and centralize error handling. The pattern scales from simple existence checks to complex multi-value comparisons, demonstrating the power of functional abstraction in everyday backend development.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
