最近项目重构,原有的项目基于mybatis的多租户无法使用,需要迁移的jpa上来,本来想尝试用
spring.jpa.properties.hibernate.multiTenancy=DISCRIMINATOR来做发现还没有实现
顾考虑使用jpa的过滤器对入库的sql进行拦截后再塞入
1.所有的租户id都是通过head参数获取的,建立拦截器放入线程的本地变量中。
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String tenantId = request.getHeader("TenantId");
System.out.println("-tenantId:-" + tenantId);
TenantContext.setTenantId(StringUtils.isBlank(tenantId) ? TenantConst.DEFAULT_TENANTID : tenantId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
TenantContext.remove();
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
2.存放租户id的上下文
public class TenantContext {
// 构造方法私有化
private TenantContext() {
}
private static final ThreadLocal<String> context = new ThreadLocal<>();
/**
* 存放租户信息
*
* @param tenantInfo
*/
public static void setTenantId(String tenantInfo) {
context.set(tenantInfo);
}
/**
* 获取租户信息
*
* @return
*/
public static String getTenantId() {
return context.get();
}
/**
* 清除当前线程内引用,防止内存泄漏
*/
public static void remove() {
context.remove();
}
}
3.jpa的过滤器
public class MyJpaInterceptor implements StatementInspector {
/**
* 当前数据库的方言
*/
private String dialect="mysql";
/**
* 多租户字段名称
*/
private String tenantIdField="tenant_id";
/**
* 需要识别多租户字段的表名称列表 --目前先写死
*/
private Set<String> tableSet = new HashSet<>();
/**
* sql语句工具
*/
private SqlConditionUtil sqlConditionUtil;
/**
* 给sql语句where添加租户id过滤条件
*
* @param sql 要添加过滤条件的sql语句
* @param tenantId 当前的租户id
* @return 添加条件后的sql语句
*/
private String addTenantCondition(String sql, String tenantId) {
if (StringUtils.isBlank(sql) || StringUtils.isBlank(tenantId)) {
return sql;
}
List<SQLStatement> statementList = SQLUtils.parseStatements(sql, dialect);
if (statementList == null || statementList.size() == 0) {
return sql;
}
SQLStatement sqlStatement = statementList.get(0);
/**
* 多租户条件字段决策器
*/
ITableFieldConditionDecision conditionDecision = new ITableFieldConditionDecision() {
@Override
public boolean isAllowNullValue() {
return false;
}
@Override
public boolean adjudge(String tableName, String fieldName) {
tableSet.add("t_book");
if (tableSet != null && tableSet.contains(tableName)) {
return true;
}
return false;
}
};
sqlConditionUtil = new SqlConditionUtil(conditionDecision);
sqlConditionUtil.addStatementCondition(sqlStatement, tenantIdField, tenantId);
return SQLUtils.toSQLString(statementList, dialect);
}
@Override
public String inspect(String s) {
System.out.println("sql拦截器:-----"+s);
String tenantId = TenantContext.getTenantId();
System.out.println("---tenantId:------->" + tenantId);
//租户id为空时不做处理
if (StringUtils.isBlank(tenantId)) {
return s;
}
//把新sql设置到boundSql
String newSql = addTenantCondition(s, tenantId);
System.out.println("--------newSql:---" + newSql);
return newSql;
}
}
4.实体类
@Entity(name = "t_book")
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String author;
@Column(nullable = false, updatable = false)
private String tenantId;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", tenantId='" + tenantId + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}
5.配置文件 xxx修改为自己的包名
6.测试