Implementing Data Isolation in Java Applications Using MyBatis Interceptor and Custom Annotations
This article describes a practical approach to achieve environment‑based data isolation in Java services by adding an env field to tables, using a MyBatis interceptor to rewrite SQL, and defining custom annotations with AOP to control the isolation logic, while sharing code snippets and lessons learned.
Data Isolation Background
In a pre‑release, gray, and online environment sharing a single database, each table originally had an env column to distinguish data. Adding the column to dozens of existing tables caused massive data migration and compatibility challenges.
Before Isolation
Only one core table had the env field; other tables lacked it, leading to accidental data leakage from pre‑release to production.
Isolation Transformation
All new tables received an env column initialized to all so that historical data could be accessed by any environment. The application reads the current environment from application.properties and injects it into SQL statements.
SELECT XXX FROM tableName WHERE env = ${environmentValue} and ${condition}Solution Design
The naive approach of adding env to every DO, Mapper, and XML was rejected. Instead, a custom MyBatis interceptor was created to handle the logic centrally.
Business code does not need to be modified.
Avoids repetitive field additions and reduces error risk.
Facilitates future extensions.
Final Implementation
In the interceptor, INSERT statements automatically fill the environment field, and SELECT statements add an env IN (${currentEnv},'all') condition to support both current and historical data.
SELECT xxx FROM ${tableName} WHERE env IN (${currentEnv},'all') AND ${otherCondition}The interceptor uses JSqlParser to rewrite SQL dynamically.
@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 {
// rewrite SQL, fill env parameter, etc.
return invocation.proceed();
}
}Further Refactoring
To avoid scattering environment‑skip logic, a custom annotation @InvokeChainSkipEnvRule with AOP was introduced. Developers annotate entry methods, specifying environments and tables to skip.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeChainSkipEnvRule {
boolean isKip() default true;
String[] skipEnvList() default {};
String[] skipTableList() default {};
}Example usage:
@GetMapping("/importSignedUserData")
@InvokeChainSkipEnvRule(skipEnvList = {"pre"}, skipTableList = {"project"})
public void importSignedUserData(HttpServletRequest request, HttpServletResponse response) {
// ...
}Challenges & Reflections
Issues such as ThreadLocal misuse and context restoration caused null environment values. The solution emphasizes keeping business and infrastructure code separate, using a dedicated ThreadLocal, and limiting modifications to a single interception point.
Key takeaways include the importance of early architectural decisions, avoiding ad‑hoc field additions, and leveraging custom annotations for clean, reusable isolation logic.
Conclusion
The case demonstrates a robust way to achieve both data isolation and selective sharing across environments by combining a MyBatis interceptor with custom annotations, providing a reference for similar backend challenges.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.