教學課程:在 Java 中使用規則運算式 (regex) 搜尋字串

適用於:SQL Server 2019 (15.x) 和更新版本

本教學課程示範如何使用 SQL Server 語言延伸模組來建立一個會從 SQL Server 接收兩個資料行 (ID 和 text) 的 Java 類別,以及一個作為輸入參數的規則運算式。 此類別會將兩個資料行傳回 SQL Server (ID 和 text)。

針對文字資料行中傳送至 Java 類別的指定文字,此程式碼會檢查指定的規則運算式是否已完成,並連同原始識別碼一起傳回該文字。

此範例程式碼會使用可檢查文字是否包含 Javajava 一字的規則運算式。

必要條件

使用 javac 的命令列編譯對於本教學課程就已經足夠。

建立範例資料

首先,建立新資料庫,並用 IDtext 資料行填入 testdata 資料表。

CREATE DATABASE javatest;
GO

USE javatest;
GO

CREATE TABLE testdata (
    [id] INT NOT NULL,
    [text] NVARCHAR(100) NOT NULL
);
GO

-- Insert data into test table
INSERT INTO testdata ([id], [text])
VALUES (1, 'This sentence contains java');

INSERT INTO testdata ([id], [text])
VALUES (2, 'This sentence does not');

INSERT INTO testdata ([id], [text])
VALUES (3, 'I love Java!');
GO

建立主類別

在此步驟中,建立名為 RegexSample.java 的類別檔案,並將下列 Java 程式碼複製到該檔案中。

此主類別將匯入 SDK,也就是說,在步驟 1 中下載的 jar 檔案必須可從這個類別搜尋。

package pkg;

import com.microsoft.sqlserver.javalangextension.PrimitiveDataset;
import com.microsoft.sqlserver.javalangextension.AbstractSqlServerExtensionExecutor;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.regex.*;

public class RegexSample extends AbstractSqlServerExtensionExecutor {
    private Pattern expr;

    public RegexSample() {
        // Setup the expected extension version, and class to use for input and output dataset
        executorExtensionVersion = SQLSERVER_JAVA_LANG_EXTENSION_V1;
        executorInputDatasetClassName = PrimitiveDataset.class.getName();
        executorOutputDatasetClassName = PrimitiveDataset.class.getName();
    }

    public PrimitiveDataset execute(PrimitiveDataset input, LinkedHashMap<String, Object> params) {
        // Validate the input parameters and input column schema
        validateInput(input, params);

        int[] inIds = input.getIntColumn(0);
        String[] inValues = input.getStringColumn(1);
        int rowCount = inValues.length;

        String regexExpr = (String)params.get("regexExpr");
        expr = Pattern.compile(regexExpr);

        System.out.println("regex expression: " + regexExpr);

        // Lists to store the output data
        LinkedList<Integer> outIds = new LinkedList<Integer>();
        LinkedList<String> outValues = new LinkedList<String>();

        // Evaluate each row
        for(int i = 0; i < rowCount; i++) {
            if (check(inValues[i])) {
                outIds.add(inIds[i]);
                outValues.add(inValues[i]);
            }
        }

        int outputRowCount = outValues.size();

        int[] idOutputCol = new int[outputRowCount];
        String[] valueOutputCol = new String[outputRowCount];

        // Convert the list of output columns to arrays
        outValues.toArray(valueOutputCol);

        ListIterator<Integer> it = outIds.listIterator(0);
        int rowId = 0;

        System.out.println("Output data:");
        while (it.hasNext()) {
            idOutputCol[rowId] = it.next().intValue();

            System.out.println("ID: " + idOutputCol[rowId] + " Value: " + valueOutputCol[rowId]);
            rowId++;
        }

        // Construct the output dataset
        PrimitiveDataset output = new PrimitiveDataset();

        output.addColumnMetadata(0, "ID", java.sql.Types.INTEGER, 0, 0);
        output.addColumnMetadata(1, "Text", java.sql.Types.NVARCHAR, 0, 0);

        output.addIntColumn(0, idOutputCol, null);
        output.addStringColumn(1, valueOutputCol);

        return output;
    }

    private void validateInput(PrimitiveDataset input, LinkedHashMap<String, Object> params) {
        // Check for the regex expression input parameter
        if (params.get("regexExpr") == null) {
            throw new IllegalArgumentException("Input parameter 'regexExpr' is not found");
        }

        // The expected input schema should be at least 2 columns, (INTEGER, STRING)
        if (input.getColumnCount() < 2) {
            throw new IllegalArgumentException("Unexpected input schema, schema should be an (INTEGER, NVARCHAR or VARCHAR)");
        }

        // Check that the input column types are expected
        if (input.getColumnType(0) != java.sql.Types.INTEGER &&
                (input.getColumnType(1) != java.sql.Types.VARCHAR && input.getColumnType(1) == java.sql.Types.NVARCHAR )) {
            throw new IllegalArgumentException("Unexpected input schema, schema should be an (INTEGER, NVARCHAR or VARCHAR)");
        }
    }

    private boolean check(String text) {
        Matcher m = expr.matcher(text);

        return m.find();
    }
}

編譯並建立 .jar 檔案

將您的類別和相依性封裝到 .jar 檔案中。 當您建置或編譯專案時,大部分的 Java IDE (例如 Eclipse 或 IntelliJ) 都支援產生 .jar 檔案。 將 .jar 檔案命名為 regex.jar

如果您不是使用 Java IDE,可以手動建立一個 .jar 檔案。 如需詳細資訊,請參閱從類別檔案建立 Java .jar 檔案

注意

本教學課程使用封裝。 類別最上方的 package pkg; 行可確保已編譯的程式碼會儲存在稱為 pkg 的子資料夾中。 如果您使用的是 IDE,已編譯的程式碼會自動儲存在此資料夾中。 如果您使用 javac 手動編譯類別,則必須將已編譯的程式碼放在 pkg 資料夾中。

建立外部語言

您必須在資料庫中建立外部語言。 外部語言是資料庫範圍的物件,也就是說,需要為您想要在其中使用的每個資料庫建立 Java 之類的外部語言。

在 Windows 上建立外部語言

如果您使用的是 Windows,請依照下列步驟建立適用於 Java 的外部語言。

  1. 建立包含延伸模組的 .zip 檔案。

    在 Windows 上安裝 SQL Server 時,Java 延伸模組 .zip 檔案會安裝在此位置中:[SQL Server install path]\MSSQL\Binn\java-lang-extension.zip。 此 zip 檔案包含 javaextension.dll

  2. 從 .zip 檔案建立外部語言 Java:

    CREATE EXTERNAL LANGUAGE Java
    FROM
    (CONTENT = N'[SQL Server install path]\MSSQL\Binn\java-lang-extension.zip', FILE_NAME = 'javaextension.dll',
    ENVIRONMENT_VARIABLES = N'{"JRE_HOME":"<path to JRE>"}' );
    GO
    

在 Linux 上建立外部語言

在安裝過程中,延伸模組 .tar.gz 檔案會儲存在下列路徑下:/opt/mssql-extensibility/lib/java-lang-extension.tar.gz

若要建立外部語言 Java,請在 Linux 上執行下列 T-SQL 陳述式:

CREATE EXTERNAL LANGUAGE Java
FROM (CONTENT = N'/opt/mssql-extensibility/lib/java-lang-extension.tar.gz', file_name = 'javaextension.so',
ENVIRONMENT_VARIABLES = N'{"JRE_HOME":"<path to JRE>"}' );
GO

執行外部語言的權限

若要執行 Java 程式碼,使用者必須獲授與該特定語言的外部指令碼執行。

如需詳細資訊,請參閱 CREATE EXTERNAL LANGUAGE

建立外部程式庫

使用 CREATE EXTERNAL LIBRARY 為您的 .jar 檔案建立外部程式庫。 SQL Server 可存取 .jar 檔案,而且您不需要將任何特殊存取權限設定為 classpath

在此範例中,建立兩個外部程式庫。 一個用於 SDK,另一個用於 RegEx Java 程式碼。

  1. SDK jar 檔案 mssql-java-lang-extension.jar 會安裝為 WINDOWS 和 Linux 上 SQL Server 2019 (15.x) 和更新版本的一部分。

    • Windows 上的預設安裝路徑:<instance installation home directory>\MSSQL\Binn\mssql-java-lang-extension.jar

    • Linux 上的預設安裝路徑:/opt/mssql/lib/mssql-java-lang-extension.jar

    此程式碼也是開放原始碼,而且可以在 SQL Server 語言延伸模組 GitHub 存放庫中找到。 如需詳細資訊,請參閱適用於 SQL Server 的 Microsoft Extensibility SDK for Java

  2. 建立適用於 SDK 的外部程式庫。

    CREATE EXTERNAL LIBRARY sdk
    FROM (CONTENT = '<OS specific path from above>/mssql-java-lang-extension.jar')
    WITH (LANGUAGE = 'Java');
    GO
    
  3. 建立適用於 RegEx 程式碼的外部程式庫。

    CREATE EXTERNAL LIBRARY regex
    FROM (CONTENT = '<path>/regex.jar')
    WITH (LANGUAGE = 'Java');
    GO
    

設定權限

注意

如果您在上一個步驟中使用外部程式庫,請略過此步驟。 建議的方式是從您的 .jar 檔案建立外部程式庫。

如果不想使用外部程式庫,則必須設定所需的權限。 只有在處理序識別可存取您的程式碼時,指令碼執行才會成功。 您可以在安裝指南中找到設定權限的詳細資訊。

在 Linux 上

將 classpath 的讀取/執行權限授與 mssql_satellite 使用者。

在 Windows 上

在包含已編譯之 Java 程式碼的資料夾中,將「讀取和執行」權限授與 SQLRUserGroup所有應用程式封裝 SID。

整個樹狀結構都必須擁有權限 (從根父系到最後一個子資料夾)。

  1. 以滑鼠右鍵按一下資料夾 (例如 C:\myJavaCode),然後選擇 [內容]>[安全性]。
  2. 選取編輯
  3. 選取 [新增]。
  4. 在 [選取使用者、電腦、服務帳戶或群組] 中:
    1. 選取 [物件類型],並確定已選取 [內建安全性原則] 和 [群組]
    2. 選取 [位置],選取清單頂端的本機電腦名稱。
  5. 輸入 SQLRUserGroup,檢查名稱,然後選取 [確定] 以新增群組。
  6. 輸入 ALL APPLICATION PACKAGES,檢查名稱,然後選取 [確定] 以新增。 如果名稱無法解析,請重新瀏覽「位置」步驟。 SID 在您電腦的本機上。

請確定這兩個安全性識別都有資料夾以及 pkg 子資料夾的 [讀取和執行] 權限。

呼叫 Java 類別

建立一個可呼叫 sp_execute_external_script 的預存程序,以便從 SQL Server 呼叫 Java 程式碼。 在 script 參數中,定義您要呼叫的 package.class。 在下列程式碼中,類別屬於名為 pkg 的套件,以及名為 RegexSample.java 的類別檔案。

注意

此程式碼不會定義要呼叫的方法。 根據預設,將呼叫 execute 方法。 也就是說,如果您想要能夠從 SQL Server 呼叫類別,則必須遵循 SDK 介面,並在您的 Java 類別中實作執行方法。

預存程序會採用輸入查詢 (輸入資料集) 和規則運算式,並傳回滿足指定規則運算式的資料列。 它會使用可檢查文字是否包含 Javajava 字詞的規則運算式 [Jj]ava

CREATE OR ALTER PROCEDURE [dbo].[java_regex]
    @expr NVARCHAR(200), @query NVARCHAR(400)
AS
BEGIN
    --Call the Java program by giving the package.className in @script
    --The method invoked in the Java code is always the "execute" method
    EXEC sp_execute_external_script @language = N'Java',
        @script = N'pkg.RegexSample',
        @input_data_1 = @query,
        @params = N'@regexExpr nvarchar(200)',
        @regexExpr = @expr
    WITH result sets((
        ID INT,
        TEXT NVARCHAR(100)
    ));
END
GO

--Now execute the above stored procedure and provide the regular expression and an input query
EXECUTE [dbo].[java_regex] N'[Jj]ava',
    N'SELECT id, text FROM testdata'
GO

結果

執行呼叫之後,您應該會獲得包含其中兩個資料列的結果集。

Java 範例結果的螢幕擷取畫面。

如果您收到錯誤

  • 當您編譯類別時,pkg 子資料夾應該包含全部三個類別的已編譯器程式碼。

  • 如果您沒有使用外部程式庫,請檢查每個資料夾的權限 (從 rootpkg 子資料夾),以確保執行外部處理序的安全性識別擁有讀取和執行程式碼的權限。