Java + Fuseki 實戰:一鍵創建圖譜、自動推理隱含知識

在企業級知識圖譜系統中,數據的全生命週期管理(創建、寫入、更新、刪除、查詢)和語義推理能力是實現智能決策的關鍵。Apache Jena Fuseki 作為標準的 SPARQL 服務,提供了完整的 HTTP 接口支持。本文將通過完整代碼示例,演示如何在 Java 應用中:

  1. 動態創建 Fuseki 數據集(Dataset)
  2. 對數據集執行增刪改查操作(SPARQL UPDATE/QUERY)
  3. 結合本體(Ontology)與推理機,實現隱含知識的自動挖掘

所有示例均基於 Kubernetes 環境中部署的 Fuseki 服務,並啓用 Shiro 安全認證,具備生產可用性。


文章目錄

  • Java + Fuseki 實戰:一鍵創建圖譜、自動推理隱含知識
  • 一、Fuseki 服務準備
  • 1. 部署要求
  • 2. 權限説明
  • 二、Java 依賴配置
  • 三、核心功能實現
  • 1. 創建數據集(Dataset)
  • 2. 數據寫入與更新(SPARQL UPDATE)
  • 3. 數據查詢(SPARQL SELECT)
  • 4. 推理增強查詢
  • 四、完整演示:公司員工知識圖譜
  • 1. 本體定義(ontology.ttl)
  • 2. 完整可運行代碼
  • 五、運行説明
  • 六、總結

一、Fuseki 服務準備

1. 部署要求

  • Fuseki 服務已通過 Kubernetes 部署,啓用 HTTPS 與 Basic Auth
  • 管理員賬號:admin / securepassword
  • 服務地址:https://fuseki.example.com

2. 權限説明

  • POST /$/datasets:需 admin 權限(用於創建數據集)
  • POST /{dataset}/update:需 writeadmin 權限
  • GET /{dataset}/query:需 readwriteadmin 權限

二、Java 依賴配置

<dependency>
    <groupId>org.apache.jena</groupId>
    <artifactId>jena-arq</artifactId>
    <version>4.9.0</version>
</dependency>

使用 jena-arq 即可滿足所有客户端操作需求,無需引入 Fuseki 服務端模塊。


三、核心功能實現

1. 創建數據集(Dataset)

Fuseki 支持通過管理 API 動態創建 TDB2 類型數據集:

import org.apache.jena.web.HttpOp;
import java.net.http.HttpResponse;

public class FusekiAdminClient {
    private final String baseUrl;
    private final String authHeader;

    public FusekiAdminClient(String baseUrl, String username, String password) {
        this.baseUrl = baseUrl;
        String credentials = username + ":" + password;
        this.authHeader = "Basic " + java.util.Base64.getEncoder()
            .encodeToString(credentials.getBytes());
    }

    public void createDataset(String datasetName) {
        String url = baseUrl + "/$/datasets?dbType=tdb2&dbName=" + datasetName;
        try {
            HttpResponse<String> response = HttpOp.execHttpPost(url, null, "text/plain");
            if (response.statusCode() == 200) {
                System.out.println("Dataset '" + datasetName + "' created successfully.");
            } else {
                throw new RuntimeException("Failed to create dataset: " + response.statusCode());
            }
        } catch (Exception e) {
            throw new RuntimeException("Create dataset error", e);
        }
    }
}

該操作會在 Fuseki 的 databases/ 目錄下創建 datasetName 子目錄,並註冊服務路徑 /datasetName


2. 數據寫入與更新(SPARQL UPDATE)

使用 UpdateExecution 執行插入、刪除等操作:

import org.apache.jena.update.UpdateExecution;
import org.apache.jena.update.UpdateFactory;

public class FusekiDataClient {
    private final String updateEndpoint;
    private final String authHeader;

    public FusekiDataClient(String baseUrl, String dataset, String username, String password) {
        this.updateEndpoint = baseUrl + "/" + dataset + "/update";
        String credentials = username + ":" + password;
        this.authHeader = "Basic " + java.util.Base64.getEncoder()
            .encodeToString(credentials.getBytes());
    }

    public void executeUpdate(String sparqlUpdate) {
        var request = UpdateFactory.create(sparqlUpdate);
        try (var ue = UpdateExecution.service(updateEndpoint)
                .addHeader("Authorization", authHeader)
                .build()) {
            ue.execute();
        }
    }
}

3. 數據查詢(SPARQL SELECT)

import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryFactory;
import org.apache.jena.query.ResultSet;

public class FusekiQueryClient {
    private final String queryEndpoint;
    private final String authHeader;

    public FusekiQueryClient(String baseUrl, String dataset, String username, String password) {
        this.queryEndpoint = baseUrl + "/" + dataset + "/query";
        String credentials = username + ":" + password;
        this.authHeader = "Basic " + java.util.Base64.getEncoder()
            .encodeToString(credentials.getBytes());
    }

    public ResultSet select(String sparqlQuery) {
        var query = QueryFactory.create(sparqlQuery);
        var qexec = QueryExecution.service(queryEndpoint)
            .addHeader("Authorization", authHeader)
            .build();
        return qexec.execSelect();
    }
}

4. 推理增強查詢

Fuseki 本身不支持運行時推理。推理需在客户端完成:

  1. 從 Fuseki 查詢原始數據
  2. 在本地加載本體(Ontology)
  3. 構建推理模型(InfModel)
  4. 在推理模型上執行查詢
import org.apache.jena.rdf.model.InfModel;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.reasoner.Reasoner;
import org.apache.jena.reasoner.ReasonerRegistry;

public class ReasoningQueryClient extends FusekiQueryClient {
    private final Model ontologyModel;

    public ReasoningQueryClient(String baseUrl, String dataset, String username, String password, String ontologyTurtle) {
        super(baseUrl, dataset, username, password);
        this.ontologyModel = ModelFactory.createDefaultModel();
        this.ontologyModel.read(new java.io.StringReader(ontologyTurtle), null, "TURTLE");
    }

    public ResultSet selectWithReasoning(String sparqlQuery) {
        // 1. 從 Fuseki 獲取原始數據
        var resultSet = select("CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }");
        var dataModel = ModelFactory.createDefaultModel();
        dataModel.getGraph().add(resultSet.getResultModel().getGraph());

        // 2. 合併數據與本體
        var combined = ModelFactory.createUnion(dataModel, ontologyModel);

        // 3. 創建 RDFS 推理模型
        Reasoner reasoner = ReasonerRegistry.getRDFSReasoner();
        InfModel infModel = ModelFactory.createInfModel(reasoner, combined);

        // 4. 在推理模型上查詢
        var query = QueryFactory.create(sparqlQuery);
        return QueryExecution.create(query, infModel).execSelect();
    }
}

⚠️ 注意:此模式適用於中小規模數據集。超大規模數據應考慮服務端推理(如使用 GraphDB、Virtuoso 等支持推理的引擎)。


四、完整演示:公司員工知識圖譜

1. 本體定義(ontology.ttl)

@prefix ex: <http://example.org/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

ex:技術部員工 rdfs:subClassOf ex:公司員工 .
ex:公司員工 rdfs:subClassOf ex:享有電腦補貼者 .

2. 完整可運行代碼

import org.apache.jena.query.ResultSet;
import org.apache.jena.query.ResultSetFormatter;
import org.apache.jena.update.UpdateExecution;
import org.apache.jena.update.UpdateFactory;
import org.apache.jena.web.HttpOp;

import java.net.http.HttpResponse;
import java.util.Base64;

public class FusekiKnowledgeGraphDemo {

    public static void main(String[] args) {
        String FUSEKI_URL = "https://fuseki.example.com";
        String ADMIN_USER = "admin";
        String ADMIN_PASS = "securepassword";
        String DATASET_NAME = "company-kg";

        // 1. 創建數據集
        createDataset(FUSEKI_URL, ADMIN_USER, ADMIN_PASS, DATASET_NAME);

        // 2. 寫入原始數據
        String insertData = """
            PREFIX ex: <http://example.org/>
            INSERT DATA {
                ex:Alice a ex:技術部員工 .
            }
            """;
        executeUpdate(FUSEKI_URL, DATASET_NAME, ADMIN_USER, ADMIN_PASS, insertData);

        // 3. 普通查詢(無推理)
        System.out.println("=== 普通查詢:Alice 是公司員工嗎? ===");
        String query1 = """
            PREFIX ex: <http://example.org/>
            ASK { ex:Alice a ex:公司員工 }
            """;
        boolean directAnswer = executeAsk(FUSEKI_URL, DATASET_NAME, ADMIN_USER, ADMIN_PASS, query1);
        System.out.println("答案: " + (directAnswer ? "是" : "否")); // 輸出:否

        // 4. 推理增強查詢
        System.out.println("\n=== 推理查詢:Alice 是公司員工嗎? ===");
        String ontology = """
            @prefix ex: <http://example.org/> .
            @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
            ex:技術部員工 rdfs:subClassOf ex:公司員工 .
            """;
        ReasoningQueryClient reasoner = new ReasoningQueryClient(
            FUSEKI_URL, DATASET_NAME, ADMIN_USER, ADMIN_PASS, ontology
        );
        boolean inferredAnswer = reasoner.selectWithReasoning(query1).hasNext();
        System.out.println("答案: " + (inferredAnswer ? "是" : "否")); // 輸出:是

        // 5. 查詢所有享有電腦補貼者
        System.out.println("\n=== 推理查詢:誰享有電腦補貼? ===");
        String query2 = """
            PREFIX ex: <http://example.org/>
            SELECT ?person WHERE {
                ?person a ex:享有電腦補貼者 .
            }
            """;
        ResultSet rs = reasoner.selectWithReasoning(query2);
        ResultSetFormatter.out(System.out, rs, null);
    }

    // --- 工具方法 ---
    private static void createDataset(String baseUrl, String user, String pass, String name) {
        String url = baseUrl + "/$/datasets?dbType=tdb2&dbName=" + name;
        String auth = "Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes());
        try {
            HttpResponse<String> resp = HttpOp.execHttpPost(url, null, "text/plain");
            if (resp.statusCode() != 200) throw new RuntimeException("Create failed");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static void executeUpdate(String baseUrl, String dataset, String user, String pass, String update) {
        String endpoint = baseUrl + "/" + dataset + "/update";
        String auth = "Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes());
        var req = UpdateFactory.create(update);
        try (var ue = UpdateExecution.service(endpoint).addHeader("Authorization", auth).build()) {
            ue.execute();
        }
    }

    private static boolean executeAsk(String baseUrl, String dataset, String user, String pass, String query) {
        String endpoint = baseUrl + "/" + dataset + "/query";
        String auth = "Basic " + Base64.getEncoder().encodeToString((user + ":" + pass).getBytes());
        try (var qexec = QueryExecution.service(endpoint).addHeader("Authorization", auth).build()) {
            return qexec.execAsk();
        }
    }
}

// 推理客户端(內嵌)
class ReasoningQueryClient {
    private final String queryEndpoint;
    private final String authHeader;
    private final org.apache.jena.rdf.model.Model ontologyModel;

    public ReasoningQueryClient(String baseUrl, String dataset, String username, String password, String ontologyTurtle) {
        this.queryEndpoint = baseUrl + "/" + dataset + "/query";
        this.authHeader = "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes());
        this.ontologyModel = org.apache.jena.rdf.model.ModelFactory.createDefaultModel();
        this.ontologyModel.read(new java.io.StringReader(ontologyTurtle), null, "TURTLE");
    }

    public org.apache.jena.query.ResultSet selectWithReasoning(String sparqlQuery) {
        // 獲取原始數據
        try (var qexec = org.apache.jena.query.QueryExecution.service(queryEndpoint)
                .addHeader("Authorization", authHeader)
                .build()) {
            var dataModel = org.apache.jena.rdf.model.ModelFactory.createDefaultModel();
            dataModel.getGraph().add(qexec.execConstruct().getGraph());

            // 合併 + 推理
            var combined = org.apache.jena.rdf.model.ModelFactory.createUnion(dataModel, ontologyModel);
            var reasoner = org.apache.jena.reasoner.ReasonerRegistry.getRDFSReasoner();
            var infModel = org.apache.jena.rdf.model.ModelFactory.createInfModel(reasoner, combined);

            return org.apache.jena.query.QueryExecution.create(
                org.apache.jena.query.QueryFactory.create(sparqlQuery), infModel
            ).execSelect();
        }
    }
}

五、運行説明

  1. 替換配置
  • FUSEKI_URL:你的 Fuseki 服務地址
  • ADMIN_USER / ADMIN_PASS:管理員憑據
  • 確保 Fuseki 版本 >= 4.7.0(支持 POST /$/datasets
  1. 輸出預期
=== 普通查詢:Alice 是公司員工嗎? ===
答案: 否

=== 推理查詢:Alice 是公司員工嗎? ===
答案: 是

=== 推理查詢:誰享有電腦補貼? ===
---------------------
| person            |
=====================
| <http://example.org/Alice> |
---------------------

六、總結

本文展示了在 Java 中操作 Fuseki 的完整技術棧:

  • 動態創建數據集:通過管理 API 實現自動化部署
  • 標準 SPARQL 操作:增刪改查符合 W3C 規範
  • 客户端推理增強:在不改造服務端的前提下實現語義推理

該方案適用於:

  • 需要自動化管理多租户知識圖譜的平台
  • 對數據安全性要求高的企業環境
  • 希望在現有 Fuseki 基礎上引入推理能力的場景

侷限性:客户端推理需全量拉取數據,不適合超大規模圖譜。對於十億級三元組場景,建議評估支持服務端推理的商業圖數據庫(如 GraphDB、Virtuoso)。

通過本文的代碼模板,開發者可快速構建具備全生命週期管理與智能推理能力的知識圖譜應用。