JPA first version of multi-tenant implementation based on table fields

  Recently, the project was restructured. The original project's multi-tenancy based on mybatis cannot be used. The jpa that needs to be migrated is uploaded. I originally wanted to try it.

spring.jpa.properties.hibernate.multiTenancy=DISCRIMINATOR for discovery has not been implemented yet

Consider using jpa filters to intercept the incoming sql and then insert it into the database.

1. All tenant IDs are obtained through the head parameter, and the interceptor is established and placed in the local variable of the thread.

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. The context that stores the tenant 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 filter

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 class

@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. Modify the configuration file xxx to your own package name

 

6. Test

 

 

 ​​​​​​​

 

 

Guess you like

Origin blog.csdn.net/u012440725/article/details/127263424