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.

Programmer DD
Programmer DD
Programmer DD
Mastering Multi‑Tenant Architecture with MyBatis‑Plus: A Complete Guide

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: 0

4. 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: true

Multi‑tenant SaaS systems are increasingly important for B‑to‑B services, and mastering these techniques can benefit career development.

JavaMulti‑TenantPaginationMyBatis-Plus
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

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.