详解基于MybatisPlus两步实现多租户方案

yizhihongxing

下面是详解基于MybatisPlus两步实现多租户方案的完整攻略。

什么是多租户?

多租户是指在同一个系统中,不同的租户使用相同的软件系统,但是每个租户的数据是独立的。比如,在一个基于云计算架构的SaaS应用中,不同的企业或用户使用同一套软件服务,但是每个企业或用户的数据是相互隔离的,这就是多租户。实现多租户需要解决数据隔离的问题,保证不同租户之间的数据不能混淆。

MybatisPlus多租户实现

MybatisPlus是一款基于Mybatis的ORM框架,提供了许多方便的增删改查功能。其提供的实现多租户功能的方式也非常简单。

第一步:配置多租户过滤器

MybatisPlus提供了多租户过滤器的抽象类,我们只需要继承该类并重写过滤器方法即可。在该过滤器中,我们需要根据当前用户的租户信息,设置数据过滤的参数,比如添加查询条件,或者添加数据权限过滤等。

示例代码:

@Component
public class MyTenantHandler extends TenantHandler {

    @Autowired
    private UserService userService;

    @Override
    public Expression getTenantId(boolean select) {
        // 根据当前登录用户获取租户ID
        User user = userService.getCurrentUser();
        return new LongValue(user.getTenantId());
    }

    @Override
    public String getTenantIdColumn() {
        return "tenant_id";
    }

    @Override
    public boolean doTableFilter(String tableName) {
        // 如果是系统内置的表,则不进行租户过滤
        return !"user".equalsIgnoreCase(tableName);
    }
}

在上面的代码中,我们创建了一个名为MyTenantHandler的多租户过滤器。其中,重写了getTenantId()方法,返回当前用户的租户ID;重写了getTenantIdColumn()方法,返回使用的租户ID列名,比如“tenant_id”;重写了doTableFilter()方法,用于过滤系统内置表,保证这些表不被租户过滤器影响。

需要注意的是,该过滤器需要注册到MybatisPlus的全局配置对象中,以生效。注册代码示例:

@EnableTransactionManagement
@Configuration
@MapperScan("com.example.demo.*.mapper")
public class MybatisPlusConfig {

    // 注册多租户过滤器
    @Bean
    public MyTenantHandler myTenantHandler() {
        return new MyTenantHandler();
    }

    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor interceptor = new PaginationInterceptor();
        // 设置多租户过滤器
        interceptor.setSqlParserList(Collections.singletonList(new TenantSqlParser().setTenantHandler(myTenantHandler())));
        return interceptor;
    }
}

在上述代码中,我们创建了一个MybatisPlus的配置类,通过@EnableTransactionManagement和@MapperScan标记该类,启用事务管理和Mapper扫描功能。同时,注册了MyTenantHandler的Bean,并且创建了一个PaginationInterceptor,并设置了租户过滤器,即TenantSqlParser。

第二步:设置实体类的租户字段注解

在我们的实体类中,需要标记哪个字段是租户字段,用于在SQL语句中生成相应的查询语句或更新语句。这里我们使用MybatisPlus提供的@TableField注解实现。

示例代码:

@Data
@TableName("user")
public class User {

    // 主键ID
    @TableId(type = IdType.AUTO)
    private Long id;

    // 用户名
    private String username;

    // 密码
    private String password;

    // 租户ID
    @TableField(value = "tenant_id")
    private Long tenantId;
}

在上述代码中,我们使用@Data和@TableName注解表示该类是一个实体类,并且指定了表名“user”。我们还使用@TableId注解表示主键,并且使用@TableField注解标记租户字段“tenant_id”。

这样,在我们执行SQL语句的时候,MybatisPlus将自动根据多租户过滤器中设置的参数,自动在SQL语句中添加相应的条件,以过滤出当前租户的数据。

示例

示例1:普通查询

假设我们有一个UserMapper接口,用于查询用户信息。如下所示:

@Mapper
public interface UserMapper extends BaseMapper<User> {

    @Select("select * from user")
    List<User> selectAll();
}

对于该接口的普通查询,我们只需要在MybatisPlus的全局配置对象中设置租户过滤器,然后执行这个查询方法就可以了。

示例代码:

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> getAllUsers() {
        return userMapper.selectAll();
    }
}

在上述代码中,我们创建了一个名为UserServiceImpl的服务类,并通过@Autowired注解注入了UserMapper的Bean对象。在getAllUsers()方法中,我们只需要执行selectAll()方法,MybatisPlus就会自动执行租户过滤,返回当前租户的数据。

示例2:动态查询

假设我们需要根据动态条件查询用户信息,比如根据用户名模糊查询和租户ID过滤。如下所示:

@Mapper
public interface UserMapper extends BaseMapper<User> {

    @Select("<script>" +
            "select * from user " +
            "where 1=1 " +
            "<if test='username != null and username != \"\"'>" +
            "and username like concat('%', #{username}, '%') " +
            "</if>" +
            "</script>")
    List<User> selectUsersByUsername(@Param("username") String username);
}

在该查询中,我们使用了MybatisPlus提供的动态SQL功能,使用了