Secure Sensitive Data in Spring Boot with MyBatis: Annotation‑Based Encryption Interceptor

This tutorial explains how to transparently encrypt and decrypt sensitive fields such as ID numbers, phone numbers, and real names in a Spring Boot application by leveraging MyBatis plugins, custom annotations, and interceptor implementations for both input and output data.

Java Interview Crash Guide
Java Interview Crash Guide
Java Interview Crash Guide
Secure Sensitive Data in Spring Boot with MyBatis: Annotation‑Based Encryption Interceptor

In real‑world projects, storing sensitive information like ID numbers, phone numbers, and real names requires encryption, but manually handling encryption in business code is error‑prone and forces developers to know encryption rules.

This article demonstrates a complete solution using Spring Boot, MyBatis, a custom interceptor, and annotations to automatically encrypt data before it is persisted and decrypt it when it is read.

1. What Is a MyBatis Plugin

MyBatis allows plugins to intercept specific points in the execution of mapped statements. The framework can intercept the following components:

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

By tapping into these interception points, we can apply cross‑cutting concerns such as encryption to the parameters, results, or the SQL itself.

2. Implementing Annotation‑Based Sensitive Data Encryption Interceptor

2.1 Design Idea

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

2.2 Defining Annotations

We create a class‑level annotation to mark entities that contain sensitive data and a field‑level annotation to mark the specific fields that 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 and AES Implementation

An EncryptUtil interface abstracts the encryption algorithm, allowing future extensions (e.g., switching from AES to PBE).

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

The AES implementation uses a custom AESUtil (not shown) to perform the actual encryption.

@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 sf = field.getAnnotation(SensitiveField.class);
            if (sf != null) {
                field.setAccessible(true);
                Object obj = field.get(paramsObject);
                if (obj instanceof String) {
                    String value = (String) obj;
                    field.set(paramsObject, aesUtil.encrypt(value));
                }
            }
        }
        return paramsObject;
    }
}

2.4 Input‑Parameter Encryption Interceptor

The interceptor implements MyBatis's Interceptor interface and is activated on the ParameterHandler.setParameters method.

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

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler ph = (ParameterHandler) invocation.getTarget();
        Field parameterField = ph.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        Object paramObj = parameterField.get(ph);
        if (paramObj != null) {
            Class<?> clazz = paramObj.getClass();
            SensitiveData sd = AnnotationUtils.findAnnotation(clazz, SensitiveData.class);
            if (sd != null) {
                Field[] fields = clazz.getDeclaredFields();
                encryptUtil.encrypt(fields, paramObj);
            }
        }
        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;
}
@Component
public class AESDecrypt implements DecryptUtil {
    @Autowired
    AESUtil aesUtil;

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

2.6 Output‑Result Decryption Interceptor

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

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (result == null) return null;
        if (result instanceof ArrayList) {
            ArrayList<?> list = (ArrayList<?>) result;
            if (!list.isEmpty() && needToDecrypt(list.get(0))) {
                for (Object item : list) {
                    decryptUtil.decrypt(item);
                }
            }
        } else if (needToDecrypt(result)) {
            decryptUtil.decrypt(result);
        }
        return result;
    }

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

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

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

3. Annotating Entity Fields

By marking an entity class with @SensitiveData and its sensitive fields with @SensitiveField, MyBatis will automatically encrypt values before they are persisted and decrypt them when they are 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.

Spring BootMyBatisannotationsensitive data
Java Interview Crash Guide
Written by

Java Interview Crash Guide

Dedicated to sharing Java interview Q&A; follow and reply "java" to receive a free premium Java interview guide.

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.