最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot基于Mybatis攔截器和JSqlParser實(shí)現(xiàn)數(shù)據(jù)隔離

 更新時(shí)間:2024年04月01日 08:32:52   作者:月初_Seth  
本文將介紹如何在 Spring Boot 項(xiàng)目中利用Mybatis的強(qiáng)大攔截器機(jī)制結(jié)合JSqlParser,一個(gè)功能豐富的 SQL 解析器,來輕松實(shí)現(xiàn)數(shù)據(jù)隔離的目標(biāo),本文根據(jù)示例展示如何根據(jù)當(dāng)前的運(yùn)行環(huán)境來實(shí)現(xiàn)數(shù)據(jù)隔離,需要的朋友可以參考下

在構(gòu)建多租戶系統(tǒng)或需要數(shù)據(jù)權(quán)限控制的應(yīng)用時(shí),數(shù)據(jù)隔離是一個(gè)關(guān)鍵問題,而解決這一問題的有效方案之一是在項(xiàng)目的數(shù)據(jù)庫訪問層實(shí)現(xiàn)數(shù)據(jù)過濾。本文將介紹如何在 Spring Boot 項(xiàng)目中利用Mybatis的強(qiáng)大攔截器機(jī)制結(jié)合JSqlParser ——一個(gè)功能豐富的 SQL 解析器,來輕松實(shí)現(xiàn)數(shù)據(jù)隔離的目標(biāo)。本文根據(jù)示例展示如何根據(jù)當(dāng)前的運(yùn)行環(huán)境來實(shí)現(xiàn)數(shù)據(jù)隔離。

工具介紹

Mybatis攔截器

Mybatis 支持在 SQL 執(zhí)行的不同階段攔截并插入自定義邏輯。

本文將通過攔截 StatementHandler 接口的 prepare方法修改SQL語句,實(shí)現(xiàn)數(shù)據(jù)隔離的目的。

JSqlParser

JSqlParser 是一個(gè)開源的 SQL 語句解析工具,它可以對(duì) SQL 語句進(jìn)行解析、重構(gòu)等各種操作:

  • 能夠?qū)?SQL 字符串轉(zhuǎn)換成一個(gè)可操作的抽象語法樹(AST),這使得程序能夠理解和操作 SQL 語句的各個(gè)組成部分。
  • 根據(jù)需求對(duì)解析出的AST進(jìn)行修改,比如添加額外的過濾條件,然后再將AST轉(zhuǎn)換回SQL字符串,實(shí)現(xiàn)需求定制化的SQL語句構(gòu)建。

SELECT語法樹簡(jiǎn)圖:

詳細(xì)步驟

1. 導(dǎo)入依賴

Mybatis 依賴:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

JSqlParser 依賴:

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.6</version>
</dependency>

注意: 如果項(xiàng)目選擇了 Mybatis Plus 作為數(shù)據(jù)持久層框架,那么就無需另外添加 Mybatis 和 JSqlParser 的依賴。Mybatis Plus 自身已經(jīng)包含了這兩項(xiàng)依賴,并且保證了它們之間的兼容性。重復(fù)添加這些依賴可能會(huì)引起版本沖突,從而干擾項(xiàng)目的穩(wěn)定性。

2. 定義一個(gè)攔截器

攔截所有 query 語句并在條件中加入 env 條件

import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.*;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.values.ValuesStatement;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
@Intercepts(
        {
                @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
        }
)
public class DataIsolationInterceptor implements Interceptor {
    /**
     * 從配置文件中環(huán)境變量
     */
    @Value("${spring.profiles.active}")
    private String env;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        //確保只有攔截的目標(biāo)對(duì)象是 StatementHandler 類型時(shí)才執(zhí)行特定邏輯
        if (target instanceof StatementHandler) {
            StatementHandler statementHandler = (StatementHandler) target;
            // 獲取 BoundSql 對(duì)象,包含原始 SQL 語句
            BoundSql boundSql = statementHandler.getBoundSql();
            String originalSql = boundSql.getSql();
            String newSql = setEnvToStatement(originalSql);
            // 使用MetaObject對(duì)象將新的SQL語句設(shè)置到BoundSql對(duì)象中
            MetaObject metaObject = SystemMetaObject.forObject(boundSql);
            metaObject.setValue("sql", newSql);
        }
        // 執(zhí)行SQL
        return invocation.proceed();
    }

    private String setEnvToStatement(String originalSql) {
        net.sf.jsqlparser.statement.Statement statement;
        try {
            statement = CCJSqlParserUtil.parse(originalSql);
        } catch (JSQLParserException e) {
            throw new RuntimeException("EnvironmentVariableInterceptor::SQL語句解析異常:"+originalSql);
        }
        if (statement instanceof Select) {
            Select select = (Select) statement;
            PlainSelect selectBody = select.getSelectBody(PlainSelect.class);
            if (selectBody.getFromItem() instanceof Table) {
                Expression newWhereExpression;
                if (selectBody.getJoins() == null || selectBody.getJoins().isEmpty()) {
                    newWhereExpression = setEnvToWhereExpression(selectBody.getWhere(), null);
                } else {
                    // 如果是多表關(guān)聯(lián)查詢,在關(guān)聯(lián)查詢中新增每個(gè)表的環(huán)境變量條件
                    newWhereExpression = multipleTableJoinWhereExpression(selectBody);
                }
                // 將新的where設(shè)置到Select中
                selectBody.setWhere(newWhereExpression);
            } else if (selectBody.getFromItem() instanceof SubSelect) {
                // 如果是子查詢,在子查詢中新增環(huán)境變量條件
                // 當(dāng)前方法只能處理單層子查詢,如果有多層級(jí)的子查詢的場(chǎng)景需要通過遞歸設(shè)置環(huán)境變量
                SubSelect subSelect = (SubSelect) selectBody.getFromItem();
                PlainSelect subSelectBody = subSelect.getSelectBody(PlainSelect.class);
                Expression newWhereExpression = setEnvToWhereExpression(subSelectBody.getWhere(), null);
                subSelectBody.setWhere(newWhereExpression);
            }

            // 獲得修改后的語句
            return select.toString();
        } else if (statement instanceof Insert) {
            Insert insert = (Insert) statement;
            setEnvToInsert(insert);

            return insert.toString();
        } else if (statement instanceof Update) {
            Update update = (Update) statement;
            Expression newWhereExpression = setEnvToWhereExpression(update.getWhere(),null);
            // 將新的where設(shè)置到Update中
            update.setWhere(newWhereExpression);

            return update.toString();
        } else if (statement instanceof Delete) {
            Delete delete = (Delete) statement;
            Expression newWhereExpression = setEnvToWhereExpression(delete.getWhere(),null);
            // 將新的where設(shè)置到delete中
            delete.setWhere(newWhereExpression);

            return delete.toString();
        }
        return originalSql;
    }

    /**
     * 將需要隔離的字段加入到SQL的Where語法樹中
     * @param whereExpression SQL的Where語法樹
     * @param alias 表別名
     * @return 新的SQL Where語法樹
     */
    private Expression setEnvToWhereExpression(Expression whereExpression, String alias) {
        // 添加SQL語法樹的一個(gè)where分支,并添加環(huán)境變量條件
        AndExpression andExpression = new AndExpression();
        EqualsTo envEquals = new EqualsTo();
        envEquals.setLeftExpression(new Column(StringUtils.isNotBlank(alias) ? String.format("%s.env", alias) : "env"));
        envEquals.setRightExpression(new StringValue(env));
        if (whereExpression == null){
            return envEquals;
        } else {
            // 將新的where條件加入到原where條件的右分支樹
            andExpression.setRightExpression(envEquals);
            andExpression.setLeftExpression(whereExpression);
            return andExpression;
        }
    }

    /**
     * 多表關(guān)聯(lián)查詢時(shí),給關(guān)聯(lián)的所有表加入環(huán)境隔離條件
     * @param selectBody select語法樹
     * @return 新的SQL Where語法樹
     */
    private Expression multipleTableJoinWhereExpression(PlainSelect selectBody){
        Table mainTable = selectBody.getFromItem(Table.class);
        String mainTableAlias = mainTable.getAlias().getName();
        // 將 t1.env = ENV 的條件添加到where中
        Expression newWhereExpression = setEnvToWhereExpression(selectBody.getWhere(), mainTableAlias);
        List<Join> joins = selectBody.getJoins();
        for (Join join : joins) {
            FromItem joinRightItem = join.getRightItem();
            if (joinRightItem instanceof Table) {
                Table joinTable = (Table) joinRightItem;
                String joinTableAlias = joinTable.getAlias().getName();
                // 將每一個(gè)join的 tx.env = ENV 的條件添加到where中
                newWhereExpression = setEnvToWhereExpression(newWhereExpression, joinTableAlias);
            }
        }
        return newWhereExpression;
    }

    /**
     * 新增數(shù)據(jù)時(shí),插入env字段
     * @param insert Insert 語法樹
     */
    private void setEnvToInsert(Insert insert) {
        // 添加env列
        List<Column> columns = insert.getColumns();
        columns.add(new Column("env"));
        // values中添加環(huán)境變量值
        List<SelectBody> selects = insert.getSelect().getSelectBody(SetOperationList.class).getSelects();
        for (SelectBody select : selects) {
            if (select instanceof ValuesStatement){
                ValuesStatement valuesStatement = (ValuesStatement) select;
                ExpressionList expressions = (ExpressionList) valuesStatement.getExpressions();
                List<Expression> values = expressions.getExpressions();
                for (Expression expression : values){
                    if (expression instanceof RowConstructor) {
                        RowConstructor rowConstructor = (RowConstructor) expression;
                        ExpressionList exprList = rowConstructor.getExprList();
                        exprList.addExpressions(new StringValue(env));
                    }
                }
            }
        }
    }
}

3. 測(cè)試

Select

Mapper:

<select id="queryAllByOrgLevel" resultType="com.lyx.mybatis.entity.AllInfo">
    SELECT a.username,a.code,o.org_code,o.org_name,o.level
    FROM admin a left join organize o on a.org_id=o.id
    WHERE a.dr=0 and o.level=#{level}
</select>

剛進(jìn)入攔截器時(shí),Mybatis 解析的 SQL 語句:

SELECT a.username,a.code,o.org_code,o.org_name,o.level
        FROM admin a left join organize o on a.org_id=o.id
        WHERE a.dr=0 and o.level=?

執(zhí)行完 setEnvToStatement(originalSql) 方法后,得到的新 SQL 語句:

SELECT a.username, a.code, o.org_code, o.org_name, o.level 
FROM admin a LEFT JOIN organize o ON a.org_id = o.id 
WHERE a.dr = 0 AND o.level = ? AND a.env = 'test' AND o.env = 'test'

Insert

剛進(jìn)入攔截器時(shí),Mybatis 解析的 SQL 語句:

INSERT INTO admin  ( id, username, code,   org_id )  VALUES (  ?, ?, ?,   ?  )

執(zhí)行完 setEnvToInsert(insert) 方法后,得到的新 SQL 語句:

INSERT INTO admin (id, username, code, org_id, env) VALUES (?, ?, ?, ?, 'test')

Update

剛進(jìn)入攔截器時(shí),Mybatis 解析的 SQL 語句:

UPDATE admin  SET username=?, code=?,   org_id=?  WHERE id=?

執(zhí)行完 setWhere(newWhereExpression) 方法后,得到的新 SQL 語句:

UPDATE admin SET username = ?, code = ?, org_id = ? WHERE id = ? AND env = 'test'

Delete

剛進(jìn)入攔截器時(shí),Mybatis 解析的 SQL 語句:

DELETE FROM admin WHERE id=?

執(zhí)行完 setWhere(newWhereExpression) 方法后,得到的新 SQL 語句:

DELETE FROM admin WHERE id = ? AND env = 'test'

4. 為什么要攔截 StatementHandler 接口的 prepare 方法?

可以注意到,在這個(gè)例子中定義攔截器時(shí) @Signature 注解中攔截的是 StatementHandler 接口的 prepare 方法,為什么攔截的是 prepare 方法而不是 query update 方法?為什么攔截 query update 方法修改 SQL 語句后仍然執(zhí)行的是原 SQL ?

這是因?yàn)?SQL 語句是在 prepare 方法中被構(gòu)建和參數(shù)化的。prepare 方法是負(fù)責(zé)準(zhǔn)備 PreparedStatement 對(duì)象的,這個(gè)對(duì)象表示即將要執(zhí)行的 SQL 語句。在 prepare 方法中可以對(duì) SQL 語句進(jìn)行修改,而這些修改將會(huì)影響最終執(zhí)行的 SQL 。

queryupdate 方法是在 prepare 方法之后被調(diào)用的。它們主要的作用是執(zhí)行已經(jīng)準(zhǔn)備好的 PreparedStatement 對(duì)象。在這個(gè)階段,SQL 語句已經(jīng)被創(chuàng)建并綁定了參數(shù)值,所以攔截這兩個(gè)方法并不能改變已經(jīng)準(zhǔn)備好的 SQL 語句。

簡(jiǎn)單來說,如果想要修改SQL語句的內(nèi)容(比如增加 WHERE 子句、改變排序規(guī)則等),那么需要在 SQL 語句被準(zhǔn)備之前進(jìn)行攔截,即在 prepare 方法的執(zhí)行過程中進(jìn)行。

以下是 MyBatis 執(zhí)行過程中的幾個(gè)關(guān)鍵步驟:

  • 解析配置和映射文件: MyBatis 啟動(dòng)時(shí),首先加載配置文件和映射文件,解析里面的 SQL 語句。
  • 生成 StatementHandlerBoundSql : 當(dāng)執(zhí)行一個(gè)操作,比如查詢或更新時(shí),MyBatis 會(huì)創(chuàng)建一個(gè) StatementHandler 對(duì)象,并包裝了 BoundSql 對(duì)象,后者包含了即將要執(zhí)行的 SQL 語句及其參數(shù)。
  • 執(zhí)行 prepare 方法: StatementHandlerprepare 方法被調(diào)用,完成 PreparedStatement 的創(chuàng)建和參數(shù)設(shè)置。
  • 執(zhí)行 queryupdate : 根據(jù)執(zhí)行的是查詢操作還是更新操作,MyBatis 再調(diào)用 queryupdate 方法來實(shí)際執(zhí)行 SQL 。
  • 通過在 prepare 方法進(jìn)行攔截,我們可以在 SQL 語句被最終確定之前更改它,從而使修改生效。如果在 queryupdate 方法中進(jìn)行攔截,則無法更改 SQL 語句,只能在執(zhí)行前后進(jìn)行其他操作,比如日志記錄或者結(jié)果處理。

以上就是SpringBoot基于Mybatis攔截器和JSqlParser實(shí)現(xiàn)數(shù)據(jù)隔離的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Mybatis JSqlParser數(shù)據(jù)隔離的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Java包裝類及自動(dòng)裝箱拆箱

    詳解Java包裝類及自動(dòng)裝箱拆箱

    這篇文章主要介紹了Java包裝類及自動(dòng)裝箱拆箱,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 在SpringBoot中使用MongoDB完成數(shù)據(jù)存儲(chǔ)

    在SpringBoot中使用MongoDB完成數(shù)據(jù)存儲(chǔ)

    本文主要介紹了在SpringBoot中如惡化使用MongoDB完成數(shù)據(jù)存儲(chǔ),接下來這篇我們將圍繞MongoDB進(jìn)行,MongoDB是一個(gè)開源的,面向文檔的NoSQL數(shù)據(jù)庫管理系統(tǒng),使用類似JSON的BSON(二進(jìn)制JSON)格式來存儲(chǔ)數(shù)據(jù),具有靈活的數(shù)據(jù)模型和強(qiáng)大的查詢功能,需要的朋友可以參考下
    2023-11-11
  • java實(shí)現(xiàn)查找文本內(nèi)容替換功能示例

    java實(shí)現(xiàn)查找文本內(nèi)容替換功能示例

    文本替換幾乎是所有文本編輯器都支持的功能,但是要限制在編輯其中才可以執(zhí)行該功能。本實(shí)例實(shí)現(xiàn)了制定文本文件的內(nèi)容替換,并且不需要再編輯其中打開文本文件
    2014-02-02
  • 解決springboot利用ConfigurationProperties注解配置數(shù)據(jù)源無法讀取配置信息問題

    解決springboot利用ConfigurationProperties注解配置數(shù)據(jù)源無法讀取配置信息問題

    今天在學(xué)習(xí)springboot利用ConfigurationProperties注解配置數(shù)據(jù)源的使用遇到一個(gè)問題無法讀取配置信息,發(fā)現(xiàn)全部為null,糾結(jié)是哪里出了問題呢,今天一番思考,問題根源找到,下面把我的解決方案分享到腳本之家平臺(tái),感興趣的朋友一起看看吧
    2021-05-05
  • SpringBoot+vue實(shí)現(xiàn)token認(rèn)證登錄過程

    SpringBoot+vue實(shí)現(xiàn)token認(rèn)證登錄過程

    本文介紹了如何在SpringBoot和Vue.js中實(shí)現(xiàn)基于JWT的token認(rèn)證登錄機(jī)制,包括后端生成和驗(yàn)證JWT,前端發(fā)送請(qǐng)求攜帶Token以及使用路由守衛(wèi)保護(hù)認(rèn)證路由
    2026-01-01
  • SpringBoot配置Access-Control-Allow-Origin教程

    SpringBoot配置Access-Control-Allow-Origin教程

    文章介紹了三種配置Spring Boot跨域訪問的方法:1. 使用過濾器;2. 在WebConfig配置文件中設(shè)置;3. 通過注解配置,作者分享了個(gè)人經(jīng)驗(yàn),并鼓勵(lì)讀者支持腳本之家
    2025-03-03
  • Spring?緩存在項(xiàng)目中的使用詳解

    Spring?緩存在項(xiàng)目中的使用詳解

    Spring?緩存機(jī)制,Cache接口為緩存的組件規(guī)范定義,包擴(kuò)緩存的各種操作(添加緩存、刪除緩存、修改緩存等),本文給大家介紹Spring?緩存在項(xiàng)目中的使用,感興趣的朋友一起看看吧
    2025-05-05
  • idea中mapper如何快速跳轉(zhuǎn)到xml插件

    idea中mapper如何快速跳轉(zhuǎn)到xml插件

    這篇文章主要介紹了idea中mapper如何快速跳轉(zhuǎn)到xml插件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • 關(guān)于Java的Character類詳解

    關(guān)于Java的Character類詳解

    這篇文章主要介紹了關(guān)于Java的Character類詳解,Java中的Character類是一個(gè)包裝類,用于封裝一個(gè)基本數(shù)據(jù)類型char的值,它提供了一些靜態(tài)方法來操作字符,需要的朋友可以參考下
    2023-05-05
  • Java面試問題知識(shí)點(diǎn)總結(jié)

    Java面試問題知識(shí)點(diǎn)總結(jié)

    本文主要介紹并且整理了Java面試知識(shí)點(diǎn)總結(jié),,有需要的小伙伴可以參考下
    2017-04-04

最新評(píng)論

河源市| 彭阳县| 安达市| 嫩江县| 瓦房店市| 东至县| 怀安县| 寻甸| 台北市| 商都县| 阿勒泰市| 革吉县| 勐海县| 江门市| 嘉定区| 札达县| 西藏| 隆化县| 抚州市| 萍乡市| 定襄县| 鄂伦春自治旗| 黄陵县| 梅州市| 保康县| 商河县| 龙州县| 新泰市| 济宁市| 余干县| 如东县| 崇明县| 根河市| 衡阳市| 札达县| 镇平县| 通州区| 芷江| 从江县| 连山| 即墨市|