1.创建一个数据表(包含租户ID字段–tenant_id)
CREATE TABLE`t_sys_user`(`user_id` bigint(50) NOT NULL AUTO_INCREMENT COMMENT'用户ID',`user_name` varchar(30) NOT NULL COMMENT'用户名',`user_password` varchar(128) NOT NULL COMMENT'用户密码',`salt` varchar(64) DEFAULT NULL COMMENT'加密盐',`user_phone` varchar(20) DEFAULT NULL COMMENT'手机号',`user_email` varchar(20) DEFAULT NULL COMMENT'邮箱',`user_title` varchar(20) DEFAULT NULL COMMENT'职称',`creater_id` bigint(20) DEFAULT NULL COMMENT'创建人ID',`creater_name` varchar(30) DEFAULT NULL COMMENT'创建人名称',`creater_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',`updater_id` bigint(20) DEFAULT NULL COMMENT'更新人ID',`updater_name` varchar(30) DEFAULT NULL COMMENT'更新人名称',`updater_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT'更新时间',`role_ids` varchar(200) DEFAULT NULL,`role_names` varchar(300) DEFAULT NULL,`del_tag` int(1) DEFAULT'0' COMMENT'逻辑删除(0 未删除,1 已删除)',`version` bigint(20) DEFAULT'1',`tenant_id` bigint(20) NOT NULL COMMENT'服务商ID',
PRIMARY KEY(`user_id`))ENGINE=InnoDBAUTO_INCREMENT=37 DEFAULTCHARSET=utf8;
2.编写一个缓存租户id的类:
@Component
public class ApiContext{
private static final String KEY_CURRENT_TENANT_ID="KEY_CURRENT_TENANT_ID";
private static final Map<String, Object> mContext= new ConcurrentHashMap<>();
public void setCurrentTenantId(Long providerId){
mContext.put(KEY_CURRENT_TENANT_ID, providerId);}
public LonggetCurrentTenantId(){return(Long) mContext.get(KEY_CURRENT_TENANT_ID);}}
3.在分页的时候,自动在sql拼接租户id的字段作为where后面的条件,具体在mybatis-plus的配置类里面配置,如下:
import com.baomidou.mybatisplus.annotation.DbType;import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;import com.lz.hehuorenservice.common.component.ApiContext;import net.sf.jsqlparser.expression.Expression;import net.sf.jsqlparser.expression.LongValue;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
public class MybatisPlusConfig{
@Autowired
private ApiContext apiContext;
// 最新版 分页插件
@Bean
public MybatisPlusInterceptormybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor= new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//注册乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
/**
* 新多租户插件配置,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存万一出现问题
*/
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(newTenantLineHandler(){
@Override
public ExpressiongetTenantId(){
// 从当前系统上下文中取出当前请求的服务商ID,通过解析器注入到SQL中。
Long currentProviderId= apiContext.getCurrentTenantId();if(null== currentProviderId){
throw new RuntimeException("Get CurrentProviderId error.");}return new LongValue(currentProviderId);}
// 这是 default 方法,默认返回false 表示所有表都需要拼多租户条件
@Override
public boolean ignoreTable(String tableName){returnfalse;
//return"t_sys_user".equalsIgnoreCase(tableName);}}));return interceptor;}}
4.在登录的时候获取到用户的租户ID,并缓存到第二步的类里面,提供给第三步的mybatis-plus的配置类使用,本人是整合在spring security里面,所以在用户的实现类的loadUserByUsername方法里面,获取到用户信息后,把租户id缓存到第二步的类里面。如下配置:
@Service
public class UserServiceImpl extends BaseServiceImpl<User, Long>
implements UserService, UserDetailsService{
@Autowired UserDao userDao;
@Autowired
private ApiContext apiContext;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException{
User user= userDao.getUserByName(userName);
apiContext.setCurrentTenantId(user.getTenantId());if(user== null){
throw new UsernameNotFoundException("账户不存在");}
Set<String> permissions= userDao.getPermissionByUserId(user.getUserId());
user.setPermissions(permissions);return user;}}
5.需要单独排除登录的接口不需要拼接租户id。因为登录之前是无法获取到租户id的,只有在登录成功后,才能获取到租户id,并进行租户id的缓存。所以要在mapper的方法里面的登录方法上,加一个过滤方法的注释@InterceptorIgnore(tenantLine = “1”)。如下:
@Mapper
public interface UserDao extends BaseDao<User, Long>{
@InterceptorIgnore(tenantLine="1")
User getUserByName(String userName);}
完成以上5个步骤,就可以实现多租户的功能。
6.mybatis-plus版本说明:
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.4</version></dependency>
7.期间遇到个问题:
1.使用mybatis-plus的自带的分页的方法查询分页,count(*)的sql统计语句无法拼接上租户id。只有自己实现的分页方法才自动拼接上租户id。(如果路过的高手知道怎么实现,请多多指点)。