Six Years In, This Java Code Made Me Cringe

The article recounts a six‑year veteran’s encounter with a tangled Java data‑isolation implementation, explains how a custom MyBatis interceptor and annotation were used to inject and filter an env field across dozens of tables, and reflects on the code smell and refactoring lessons learned.

Architect's Guide
Architect's Guide
Architect's Guide
Six Years In, This Java Code Made Me Cringe

1. Historical Background

1.1 Data Isolation

In the pre‑release, gray, and online environments a single database is shared. Each table contains an env column indicating the environment, as shown in the diagram.

1.2 Before Isolation

Initially only one core table had the env column; the other twenty‑plus tables lacked it. An operation in the pre‑release environment once polluted production data, prompting the addition of the env column to all tables.

1.3 Isolation Refactor

Historical data are hard to separate, so the new env field is initialized to all, allowing both pre‑release and online environments to access old records.

1.4 Bad Approach

The worst practice is to add the env column to every DO, Mapper, and XML file individually.

1.5 Solution

A custom MyBatis interceptor handles the env logic uniformly, so business code (DO, Mapper, XML) does not need to be modified.

Business code remains untouched.

Avoids the massive effort and high error rate of adding the column manually.

Facilitates future extensions.

1.6 Implementation

SQL is rewritten to include the environment condition:

SELECT XXX FROM tableName WHERE env = ${environment} AND ${condition}

During insertion the env value is set to all for compatibility, and queries use an IN clause:

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

Interceptor code (partial):

@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 {
                insertMethodProcess(invocation, boundSql);
            } catch (Exception exception) {
                log.error("parser insert sql exception, boundSql is:" + JSON.toJSONString(boundSql), exception);
                throw exception;
            }
        }
        return invocation.proceed();
    }
}

1.7 Error Cause

Investigation revealed that multiple places manipulate a ThreadLocal holding the environment. When method B clears the context after returning to method A, A receives a null env value.

1.8 Proliferation

Similar env‑handling code appears throughout the business layer:

String oriFilterEnv = UserHolder.getUser().getFilterEnv();
UserHolder.getUser().setFilterEnv(globalConfigDTO.getAllEnv());
UserHolder.getUser().setFilterEnv(oriFilterEnv);

1.9 Open Questions

Does this violate the Open‑Closed Principle?

What about missed fields?

Are other teams using similar shortcuts?

Is business logic properly separated from functional code?

Is storing the env in the user context appropriate?

2. Evolution

2.1 Business Requirements

PRC interface needs different env handling for upstream partners.

Some environments share data (pre‑release, gray).

Developers want to correct online data from pre‑release.

2.2 Initial Discussion

A junior developer asked for guidance. Suggestions included skipping env checks, concatenating all conditions, or using annotations to mark specific methods.

2.3 Implementation Details

Hard‑coded env list leads to queries like:

SELECT * FROM ${tableName} WHERE env IN ('pre','gray','online','all') AND ${otherCondition}

2.4 Annotation Usage

Define a custom annotation to declare skip rules:

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

Apply it on controller methods:

@InvokeChainSkipEnvRule(skipEnvList = {"pre"}, skipTableList = {"project"})
@GetMapping("/importSignedUserData")
public void importSignedUserData(HttpServletRequest request, HttpServletResponse response) {
    // ...
}

2.5 Limitations

The rule applies to the whole call chain, giving coarse granularity.

Annotations can only be placed on entry points; internal calls should avoid them.

3. Refactoring Thoughts

3.1 Difficulties

MyBatis interceptors cannot directly obtain the service‑layer method; they must inspect the stack trace to locate the caller.

3.2 Guidelines

Do not modify existing methods unless necessary.

Keep business and functional code separate.

Aim for minimal changes – a single place should implement the logic.

Ensure the refactored code is reusable, not a copy‑paste solution.

3.3 Implementation Analysis

Use an independent ThreadLocal instead of mixing with user context.

Combine annotation with AOP to parse parameters and enforce the rule.

Consider optimization for recursive or repeated calls.

3.4 Use Case

Skip env check for the project table in pre‑release:

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

3.5 Summary of Scenarios

Distributed lock via custom annotation.

Compliance parameter validation using OGNL expressions.

Interface data permission based on user roles.

Routing strategies by annotating handlers.

4. Final Thoughts

4.1 Isolation Summary

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

4.2 Coding Summary

Modify in one place instead of scattering similar code.

Leverage custom annotations to encapsulate common logic.

Compromise when needed, but keep clear boundaries.

4.3 Reflection

Early design decisions, such as using separate databases or rejecting the requirement outright, could avoid the technical debt observed.

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.

JavaMyBatisCustom AnnotationData IsolationInterceptorRefactoring
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.