Skip to content
Refactoring Data Validation with Java 8 Function and SFunction Interfaces

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.

Architect's Guide
Architect's Guide
Architect's Guide

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

GenericsMyBatis PlusRefactoringdata validationJava 8Lambda ExpressionsFunctional InterfacesSFunction
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.