Mastering Multi‑Tenant Architecture with MyBatis‑Plus: A Complete Guide
This article explains multi‑tenant concepts, compares three data isolation strategies, and provides a step‑by‑step MyBatis‑Plus implementation—including pagination interceptor configuration, code examples, testing, and ways to exclude specific SQL from tenant filtering—helping developers build scalable SaaS back‑ends.
1. Introduction
Multi‑tenant is a software architecture technique where multiple users share the same system while keeping their data isolated. A typical scenario is an H5 application deployed in different hospital apps; each hospital (tenant) sends its data with a tenant ID so the backend can separate the data.
2. Data Isolation Schemes
Independent Database: One database per tenant, offering the highest isolation and security but increasing cost.
Shared Database – Separate Schema: Tenants share a single database but each has its own schema (database user).
Shared Database – Shared Schema: Tenants share the same database and schema, with a tenant_id column added to tables; this provides the lowest isolation but the lowest cost.
3. Implementation with MyBatis‑Plus
The article chooses scheme three (shared database, shared schema) for minimal server cost. MyBatis‑Plus offers a multi‑tenant solution based on the pagination interceptor.
@Configuration
public class MyBatisPlusConfig {
/**
* Pagination interceptor
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// Create SQL parser list
List<ISqlParser> sqlParserList = new ArrayList<>();
// Create tenant SQL parser
TenantSqlParser tenantSqlParser = new TenantSqlParser();
// Set tenant handler
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId() {
// Return current tenant ID, e.g., from cookie or cache
return new StringValue("jiannan");
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean doTableFilter(String tableName) {
// Return true to filter a specific table, false otherwise
return false;
}
});
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
return paginationInterceptor;
}
}After configuration, all CRUD operations automatically add the tenant_id condition. Example test:
@Test
public void select() {
List<User> users = userMapper.selectList(Wrappers.<User>lambdaQuery().eq(User::getAge, 18));
users.forEach(System.out::println);
}
DEBUG==> Preparing: SELECT id, login_name, name, password, email, salt, sex, age, phone, user_type, status, organization_id, create_time, update_time, version, tenant_id FROM sys_user WHERE sys_user.tenant_id = 'jiannan' AND is_delete = '0' AND age = ?
DEBUG==> Parameters: 18(Integer)
DEBUG<== Total: 04. Specific SQL Filtering
If certain SQL statements should not carry the tenant filter, two approaches are provided:
Configure an ISqlParserFilter in the pagination interceptor to exclude specific mapper methods.
Use the @SqlParser(filter = true) annotation on mapper methods (available only on mapper interfaces).
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
if ("com.example.demo.mapper.UserMapper.selectList".equals(ms.getId())) {
return true;
}
return false;
}
}); public interface UserMapper extends BaseMapper<User> {
@SqlParser(filter = true)
int updateByMyWrapper(@Param(Constants.WRAPPER) Wrapper<User> userWrapper,
@Param("user") User user);
}Enable SQL parser cache in MyBatis‑Plus configuration (for versions ≥3.1.1):
mybatis-plus:
global-config:
sql-parser-cache: trueMulti‑tenant SaaS systems are increasingly important for B‑to‑B services, and mastering these techniques can benefit career development.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
