Mybatis plus advanced function - Multi tenant function implementation

Posted by setaside on Sun, 16 Jan 2022 16:49:40 +0100

1, Introduction

Let's explain what multi tenancy is and what scenarios use multi tenancy.

Multi tenancy is a software architecture technology. In a multi-user environment, there is a common system, and attention should be paid to the isolation between data.

Take a practical example: Xiaobian has developed a set of H5 program, which is applied to the APP of different hospitals. When hospital patients download the hospital APP and enter the corresponding H5 page, the APP will transfer user related data to Xiaobian. The hospital identification (tenant ID) shall be carried during transmission, so that the small staff can isolate the data.

When different tenants use the same set of programs, a case of data isolation needs to be considered here.

There are three schemes for data isolation:

1. Independent database: simply speaking, a tenant uses a database. This kind of data isolation level is the highest, the security is the best, but the cost is increased.

2. Shared database and isolated data schema: multiple tenants use the same data schema, but each tenant corresponds to a schema (database user).

3. Shared database and shared data Schema: the same database and Schema are used, but the field of tenant ID is added in the table. This shared data has the highest degree and the lowest isolation level.
2, Concrete implementation

Scheme 3 is adopted here, that is, shared database and shared data architecture, because this scheme has the lowest server cost, but increases the development cost.

Therefore, MP provides a multi tenant solution. The implementation method is based on the paging plug-in. The specific implementation code is as follows:

@Configuration
public class MyBatisPlusConfig {
 
 
    /**
     * Paging plug-in
     *
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
 
        // Create SQL parser collection
        List<ISqlParser> sqlParserList = new ArrayList<>();
 
        // Create tenant SQL parser
        TenantSqlParser tenantSqlParser = new TenantSqlParser();
 
        // Set tenant processor
        tenantSqlParser.setTenantHandler(new TenantHandler() {
            @Override
            public Expression getTenantId() {
                // Set the current tenant ID. in fact, you can take it from the cookie or cache
                return new StringValue("jiannan");
            }
 
            @Override
            public String getTenantIdColumn() {
                // Column name corresponding to database tenant ID
                return "tenant_id";
            }
 
            @Override
            public boolean doTableFilter(String tableName) {
                // Do you want to filter a table
              /*  List<String> tableNameList = Arrays.asList("sys_user");
                if (tableNameList.contains(tableName)){
                    return true;
                }*/
                return false;
            }
        });
 
        sqlParserList.add(tenantSqlParser);
        paginationInterceptor.setSqlParserList(sqlParserList);
 
 
        return paginationInterceptor;
    }
 
}

After configuration, the MP will automatically add the tenant ID no matter the query, add, modify or delete methods. The test is as follows:

@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

3, Specific SQL filtering

If some SQL in the program does not need to add the representation of tenant ID and needs to filter specific SQL, you can do it in the following two ways:

Method 1: configure the ISqlParserFilter parser in the configuration paging plug-in. If there are many SQL configurations, it is troublesome and not recommended.

paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
    @Override
    public boolean doFilter(MetaObject metaObject) {
        MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
        // Corresponding methods in Mapper and dao
        if("com.example.demo.mapper.UserMapper.selectList".equals(ms.getId())){
            return true;
        }
        return false;
    }
});

Method 2: in the form of tenant annotation, it can only act on Mapper's method at present.

public interface UserMapper extends BaseMapper<User> {
 
 
    /**
     * Custom Wrapper modification
     *
     * @param userWrapper Conditional constructor
     * @param user        Modified object parameters
     * @return
     */
    @SqlParser(filter = true)
    int updateByMyWrapper(@Param(Constants.WRAPPER) Wrapper<User> userWrapper, @Param("user") User user);
 
}
# Enable SQL parsing cache annotation to take effect. If your MP version is 3.1.1 or above, it does not need to be configured
mybatis-plus:
  global-config:
    sql-parser-cache: true

Topics: Java Back-end