一、多租户
多租户技术(英语:multi-tenancy technology)或称多重租赁技术,是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。
多租户技术可以实现多个租户之间共享系统实例,同时又可以实现租户的系统实例的个性化定制。通过使用多租户技术可以保证系统共性的部分被共享,个性的部分被单独隔离。通过在多个租户之间的资源复用,运营管理维护资源,有效节省开发应用的成本。
多租户技术的实现重点,在于不同租户间应用程序环境的隔离(application context isolation)以及数据的隔离(data isolation),以维持不同租户间应用程序不会相互干扰,同时数据的保密性也够强。
二、TenantLineInnerInterceptor插件使用
在MybatisPlusConfig类中添加多租户插件。
@Configuration
@MapperScan("com.example.demo.*")
public class MybatisPlusConfig {
@Resource
TenantIgnoreTable tenantIgnoreTable;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//多租户插件
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
@Override
public Expression getTenantId() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null ) {
throw new RuntimeException(ContainsExp.TOKEN_406);
}
String tenantId= ((ServletRequestAttributes) requestAttributes).getRequest().getHeader("tenantId");
return new LongValue(tenantId);
}
// 这是 default 方法,默认返回 false 表示所有表都需要拼多租户条件
@Override
public boolean ignoreTable(String tableName) {
// 非http请求,代码业务逻辑,不校验tenantId
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
String tenantId= ((ServletRequestAttributes) requestAttributes).getRequest().getHeader("tenantId");
if(StringUtils.isBlank(tenantId)){
return true;
}
boolean res= false;
for (String tenantIgnoreTableName:tenantIgnoreTable.getTable()) {
res= res|| tenantIgnoreTableName.equals(tableName);
}
return res;
}
}));
// 针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
多租户筛选列表。
@Data
@Component
@ConfigurationProperties(prefix = "tenant.ignore") // 配置文件的前缀
public class TenantIgnoreTable {
/*
* 多租户筛选查询,忽略表列表
*/
private List<String> table;
}
bootstrap.yml配置类。
# 多租户筛选查询,忽略表列表
tenant:
ignore:
#该表列表下均忽略
table: [users, students, teachers, schools...]
实体类Users
/**
* 用户实体对应表 users
*/
@Data
@Accessors(chain = true)
public class Users {
private Long id;
/**
* 租户 ID
*/
private Long tenantId;
private String name;
@TableField(exist = false)
private String addrName;
}
UserMapper
/**
* MP 支持不需要 UsersMapper.xml 这个模块演示内置 CRUD 咱们就不要 XML 部分了
*/
public interface UsersMapper extends BaseMapper<Users> {
/**
* 自定义SQL:默认也会增加多租户条件
* 参考打印的SQL
* @return
*/
Integer myCount();
List<Users> getUserAndAddr(@Param("username") String username);
List<Users> getAddrAndUser(@Param("name") String name);
}
如果你想在某个mapper中的某个方法中屏蔽这个租户隔离方法,可以这么写。
各属性返回 true 表示不走插件(在配置了插件的情况下,不填则默认表示 false)
@InterceptorIgnore(tenantLine = "true")
Users loginById(@Param("userId") Integer userId);
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.demo.repository.UsersMapper">
<select id="myCount" resultType="java.lang.Integer">
select count(1) from users
</select>
<select id="getUserAndAddr" resultType="com.example.demo.entity.Users">
select u.id, u.name, a.name as addr_name
from users u
left join user_addr a on a.user_id=u.id
<where>
<if test="username!=null">
u.name like concat(concat('%',#{username}),'%')
</if>
</where>
</select>
<select id="getAddrAndUser" resultType="com.example.demo.entity.Users">
select a.name as addr_name, u.id, u.name
from user_addr a
left join users u on u.id=a.user_id
<where>
<if test="name!=null">
a.name like concat(concat('%',#{name}),'%')
</if>
</where>
</select>
</mapper>
单元测试
@SpringBootTest
public class TenantTest {
@Resource
private UsersMapper mapper;
@Test
public void aInsert() {
Users user = new Users();
user.setName("一一");
Assertions.assertTrue(mapper.insert(user) > 0);
user = mapper.selectById(user.getId());
Assertions.assertTrue(1 == user.getTenantId());
}
}
三、结果分析
执行insert方法之后,可以看到sql语句中自动拼接了租户屏蔽条件。
总之一定要记住,遇到问题了就看日志,学会看日志了,解决问题的速度也会快很多。
数据库结果。