天天看点

jpa基于表字段的多租户实现初版

  最近项目重构,原有的项目基于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修改为自己的包名

jpa基于表字段的多租户实现初版

6.测试

jpa基于表字段的多租户实现初版
jpa基于表字段的多租户实现初版
jpa基于表字段的多租户实现初版
jpa基于表字段的多租户实现初版