Backend Development 11 min read

Implementing Environment Isolation in MyBatis with a Custom Interceptor and Annotation

This article describes how to achieve data environment isolation in a Java application by creating a custom MyBatis interceptor and annotation, allowing automatic SQL modification to handle env fields, reducing code changes, improving safety, and supporting flexible deployment across pre‑release, gray, and production environments.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Implementing Environment Isolation in MyBatis with a Custom Interceptor and Annotation

The author shares a practical experience of isolating data environments in a Java project that shares a single database across pre‑release, gray, and production environments. Each table originally had an env column to distinguish data, but many tables lacked this field, causing accidental data leakage.

1. Historical Background

1.1 Data Isolation

All environments used the same database with an env column indicating the environment. A diagram illustrates the layout.

1.2 Before Isolation

Initially only one core table had the env column; other tables did not. An operation in the pre‑release environment affected online data, prompting the addition of the env column to dozens of tables while preserving existing data.

1.3 Isolation Refactor

Historical data could not be distinguished, so the new env field was initialized to all , allowing both pre‑release and online to access old records.

The SQL executed after refactor looks like:

SELECT XXX FROM tableName WHERE env = ${environmentValue} and ${condition}

1.4 Isolation Strategy

The naive approach would be to add env to every DO, Mapper, and XML file, which the author rejects. Instead, a custom MyBatis interceptor is used to handle the field uniformly.

1.5 Final Implementation

In the interceptor, SQL is rewritten: during inserts the env value is filled, and during queries the condition env = ${currentEnv} is changed to env in (${currentEnv},'all') to support historical data.

SELECT xxx FROM ${tableName} WHERE env in (${currentEnv},'all') AND ${otherCondition}

The interceptor reads the environment value from application.properties and uses JSqlParser to modify the SQL.

@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
@Component
public class EnvIsolationInterceptor implements Interceptor {
    ...
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (SqlCommandType.INSERT == sqlCommandType) {
            try {
                // rewrite SQL and fill env parameter
                insertMethodProcess(invocation, boundSql);
            } catch (Exception e) {
                log.error("parser insert sql exception, boundSql is:" + JSON.toJSONString(boundSql), e);
                throw e;
            }
        }
        return invocation.proceed();
    }
}

2. Evolution

2.1 Business Requirements

New requirements emerged, such as matching environment differences with partner PRC interfaces and sharing data between certain environments.

SELECT * FROM ${tableName} WHERE bizId = ${bizId} and env in (?, 'all')

2.2 Initial Discussion

A junior developer asked how to proceed; suggestions included skipping the env check or using annotations to mark specific methods.

2.3 Implementation Details

The solution involves hard‑coding env handling around methods, using ThreadLocal to store the original filter, setting the global env, executing business logic, and restoring the original value.

// 1. Save original env
String oriFilterEnv = UserHolder.getUser().getFilterEnv();

// 2. Set global env
UserHolder.getUser().setFilterEnv(globalConfigDTO.getAllEnv());

// ... business code ...

// 3. Restore original env
UserHolder.getUser().setFilterEnv(oriFilterEnv);

2.4 Error Analysis

The null env value was traced to multiple ThreadLocal manipulations where a called method cleared the context before the caller accessed it.

2.5 Proliferation of Code

Similar env‑handling snippets appeared throughout the codebase, indicating a need for refactoring.

3. Refactoring

3.1 Challenges

MyBatis interceptors cannot directly identify service‑layer calls; stack inspection is required.

3.2 Problem List

Avoid modifying existing methods.

Separate concerns; do not mix business logic with infrastructure.

Make changes in a single place.

Promote reuse instead of copy‑paste.

3.3 Solution Analysis

Use an independent ThreadLocal separate from user context.

Define a custom annotation + AOP to declare skip rules.

Consider call‑graph optimization for nested calls.

3.4 Usage Example

Annotate a controller method to skip env checks for the project table in the pre environment:

@InvokeChainSkipEnvRule(skipEnvList = {"pre"}, skipTableList = {"project"})
public void importSignedUserData(...) { ... }

3.5 Annotation Definition

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeChainSkipEnvRule {
    boolean isKip() default true;
    String[] skipEnvList() default {};
    String[] skipTableList() default {};
}

3.6 Limitations

Granularity is coarse; the whole chain for a table is skipped.

Annotation can only be placed on entry points, not internal calls.

4. Summary and Reflection

4.1 Isolation Summary

The case demonstrates a practical approach to both data isolation and sharing by using a custom MyBatis interceptor and annotation‑driven AOP.

4.2 Coding Summary

Modify a single location instead of scattering changes.

Leverage custom annotations for reusable logic.

Maintain clear boundaries between business and infrastructure code.

4.3 Scenario Summary

Custom annotation + AOP is a flexible pattern applicable to many cross‑cutting concerns.

4.4 Reflections

Consider proper technical design or separate databases from the start.

Reject unreasonable requirements when possible.

Design before coding to avoid ad‑hoc solutions.

4.5 Final Thoughts

The author reflects on the emotional toll of constantly patching code and the importance of thoughtful engineering.

javaaopMyBatisInterceptorannotationenvironment-isolation
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

login 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.