Implementing Sensitive Data Encryption and Decryption in Spring Boot with MyBatis Interceptors and Custom Annotations

This article demonstrates how to automatically encrypt sensitive fields such as ID numbers and phone numbers before storing them in a database and decrypt them after retrieval by using Spring Boot, MyBatis plugins, and custom annotations, eliminating manual encryption logic in business code.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Implementing Sensitive Data Encryption and Decryption in Spring Boot with MyBatis Interceptors and Custom Annotations

In production projects, sensitive data such as ID numbers, phone numbers, and real names need to be encrypted before being stored in the database, and manually encrypting/decrypting in business code is error‑prone.

This article explains how to use Spring Boot, MyBatis interceptor, and custom annotations to automatically encrypt data before persistence and decrypt it after retrieval.

What is a MyBatis Plugin

MyBatis plugins allow interception at various points of the SQL execution lifecycle, including Executor, ParameterHandler, ResultSetHandler, and StatementHandler.

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

By leveraging this mechanism, we can insert encryption logic into the parameter handling phase and decryption logic into the result handling phase.

Implementing Annotation‑Based Sensitive Data Encryption/Decryption

2.1 Design Idea

Two interceptors are required: one for encrypting input parameters (ParameterHandler) and one for decrypting output results (ResultSetHandler).

2.2 Define Annotations

Define a class‑level annotation @SensitiveData and a field‑level annotation @SensitiveField to mark which POJOs and fields need encryption.

@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData { }

@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField { }

2.3 Encryption Interface

Create an EncryptUtil interface to abstract encryption algorithms, allowing future extensions such as PBE.

public interface EncryptUtil {
    <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}

An AES implementation ( AESEncrypt) uses a custom AESUtil to encrypt String fields.

@Component
public class AESEncrypt implements EncryptUtil {
    @Autowired
    AESUtil aesUtil;

    @Override
    public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (sensitiveField != null) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                if (object instanceof String) {
                    String value = (String) object;
                    field.set(paramsObject, aesUtil.encrypt(value));
                }
            }
        }
        return paramsObject;
    }
}

2.4 Input Parameter Encryption Interceptor

The interceptor is annotated with @Intercepts targeting ParameterHandler.setParameters. It retrieves the parameter object, checks for @SensitiveData, and invokes the encryption utility.

@Slf4j
@Component
@Intercepts({
    @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class EncryptInterceptor implements Interceptor {
    private final EncryptDecryptUtil encryptUtil;

    @Autowired
    public EncryptInterceptor(EncryptDecryptUtil encryptUtil) {
        this.encryptUtil = encryptUtil;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            Class<?> clazz = parameterObject.getClass();
            SensitiveData sensitiveData = AnnotationUtils.findAnnotation(clazz, SensitiveData.class);
            if (sensitiveData != null) {
                Field[] declaredFields = clazz.getDeclaredFields();
                encryptUtil.encrypt(declaredFields, parameterObject);
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) { }
}

2.5 Decryption Interface and AES Implementation

public interface DecryptUtil {
    <T> T decrypt(T result) throws IllegalAccessException;
}

The AES implementation ( AESDecrypt) mirrors the encryption logic but calls aesUtil.decrypt.

@Component
public class AESDecrypt implements DecryptUtil {
    @Autowired
    AESUtil aesUtil;

    @Override
    public <T> T decrypt(T result) throws IllegalAccessException {
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (sensitiveField != null) {
                field.setAccessible(true);
                Object object = field.get(result);
                if (object instanceof String) {
                    field.set(result, aesUtil.decrypt((String) object));
                }
            }
        }
        return result;
    }
}

2.6 Output Result Decryption Interceptor

The interceptor targets ResultSetHandler.handleResultSets, processes both list and single‑object results, and applies the decryption utility when the result class is annotated with @SensitiveData.

@Slf4j
@Component
@Intercepts({
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor implements Interceptor {
    @Autowired
    DecryptUtil aesDecrypt;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        if (resultObject == null) {
            return null;
        }
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    aesDecrypt.decrypt(result);
                }
            }
        } else {
            if (needToDecrypt(resultObject)) {
                aesDecrypt.decrypt(resultObject);
            }
        }
        return resultObject;
    }

    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
        return sensitiveData != null;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) { }
}

With the annotations placed on entity classes and fields, MyBatis will automatically encrypt data before it is sent to the database and decrypt it after it is read, without any changes to business logic.

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.

BackendMyBatisInterceptorSpringBootannotationencryption
Code Ape Tech Column
Written by

Code Ape Tech Column

Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn

0 followers
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.