Backend Development 9 min read

Implementing Data Isolation with MyBatis Interceptor and Custom Annotations in Java

This article describes how to achieve multi‑environment data isolation in a Java backend by adding an env field to tables, using a MyBatis interceptor with JSqlParser to rewrite SQL, and applying custom annotations and AOP to control environment‑specific logic while preserving existing business code.

Java Captain
Java Captain
Java Captain
Implementing Data Isolation with MyBatis Interceptor and Custom Annotations in Java

The author recounts a real‑world scenario where pre‑release, gray, and production environments share a single database, requiring an env column to distinguish data and a strategy to migrate dozens of existing tables without breaking compatibility.

Initially only one core table had the env field; after a production data incident, all tables needed the field, and historical data had to remain accessible. The solution was to initialize the new env column with the value all so that both old and new data could be queried.

Rather than manually adding the env column to every DO, Mapper, and XML, a custom MyBatis interceptor was created. The interceptor reads the environment value from application.properties and rewrites SQL statements at runtime, inserting the env condition for SELECTs and populating the field for INSERTs.

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

The interceptor solves three problems: it avoids modifying business code, reduces the risk of errors from massive schema changes, and simplifies future extensions.

During development, a bug was discovered where the environment value became null due to improper ThreadLocal handling. The fix involved storing the original value, setting a global environment, executing the business logic, and then restoring the original value.

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

To make the solution more declarative, a custom annotation @InvokeChainSkipEnvRule was introduced. By annotating controller methods with the environments and tables to skip, an AOP aspect reads the annotation and stores the rule in the application context, which the interceptor then uses to decide whether to apply the env filter.

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

The article also discusses limitations of the approach, such as coarse granularity of skipping, the need for annotations only at entry points, and the importance of separating business logic from infrastructure concerns.

In the concluding sections, the author reflects on the need for proper design before coding, the benefits of custom annotations for cross‑cutting concerns, and the importance of refactoring duplicated code to maintain clean, maintainable backend systems.

backendJavaMyBatisCustom AnnotationData IsolationInterceptor
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

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.