当前位置: 首页 > 工具软件 > TDB > 使用案例 >

Apache Jena TDB 增删改查操作

佟和平
2023-12-01

使用Apache Jena TDB 增删改查操作 管理知识图谱三元组
By 龙前尘

实验环境
WIN8、Jena 3.0.1、TDB 3.0.1


转载请注明地址:
http://blog.csdn.net/svenhuayuncheng/article/details/78821457
本文参考文章为:
https://tutorial-academy.com/apache-jena-tdb-crud-operations/

1. Apache Jena TDB CRUD简述

Apache Jena是专门用于语义网本体操作的开源Java框架,其提供RDF和SPARQL API,来查询、修改本体和进行本体推理,并且提供了TDB和Fuseki来存储和管理三元组。

本文主要focus在TDB的CRUD(增删改查)操作。如笔者前文所述:使用Jena-TDB存储RDF本体、知识图谱文件,在进行TDB CRUD操作之前,需要将知识图谱或本体文件持久化到TDB数据文件夹中,之后就可以非常方便地使用API进行操作,且update的信息也会在TDB数据中被持久化保存。

2. 编写相关方法来处理TDB

本文假设用户已经建立了Dataset类对象dataset,作为容器来存储model对象并处理三元组。如果对此有疑问,可参考笔者的文章:使用Jena-TDB存储RDF本体、知识图谱文件

2.1 查询三元组

    /**
     * 查询Model中三元组;
     */
    public List<Statement> getTriplet(String modelName, String subject, String predicate, String object) {
        List<Statement> results = new ArrayList<>();

        Model model = null;

        dataset.begin(ReadWrite.READ);
        try {
            model = dataset.getNamedModel(modelName);

            Selector selector = new SimpleSelector(
                    (subject != null) ? model.createResource(subject) : null,
                    (predicate != null) ? model.createProperty(predicate) : null,
                    (object != null) ? model.createResource(object) : null
            );

            StmtIterator it = model.listStatements(selector);
            while (it.hasNext()) {
                Statement stmt = it.next();
                results.add(stmt);
            }
            dataset.commit();
        } finally {
            if (model != null) model.close();
            dataset.end();
        }
        return results;
    }

2.2 增加三元组

    /**
     * 增加Model中三元组;
     */
    public void addTriplet(String modelName, String subject, String predicate, String object) {
        Model model = null;

        dataset.begin(ReadWrite.WRITE);
        try {
            model = dataset.getNamedModel(modelName);
            //三元组信息不全则不添加;
            if (subject == null || predicate == null || object == null) {
                LOG.warn("三元组信息有缺失,不执行添加操作!");
            } else {
                //存在当前三元组则不添加;
                Selector selector = new SimpleSelector(
                        (subject != null) ? model.createResource(subject) : null,
                        (predicate != null) ? model.createProperty(predicate) : null,
                        (object != null) ? model.createResource(object) : null
                );
                StmtIterator it = model.listStatements(selector);
                if (it.hasNext()) {
                    LOG.warn("已有该三元组,不执行添加操作!");
                } else {
                    Statement stmt = model.createStatement
                            (
                                    model.createResource(subject),
                                    model.createProperty(predicate),
                                    model.createResource(object)
                            );
                    model.add(stmt);
                    model.commit();
                    dataset.commit();
                    LOG.info("添加三元组: " + subject + " " + predicate + " " + object);
                }
            }
        } finally {
            if (model != null)
                model.close();
            dataset.end();
        }
    }

2.3 删除三元组

    /**
     * 删除Model中的三元组;
     */
    public void removeTriplet(String modelName, String subject, String predicate, String object) {
        Model model = null;
        dataset.begin(ReadWrite.WRITE);
        try {
            model = dataset.getNamedModel(modelName);

            //三元组信息不全则不添加;
            if (subject == null || predicate == null || object == null) {
                LOG.warn("三元组信息有缺失,不执行删除操作!");
            }

            else {
                //不存在当前三元组则不执行删除添加;
                Selector selector = new SimpleSelector(
                        (subject != null) ? model.createResource(subject) : null,
                        (predicate != null) ? model.createProperty(predicate) : null,
                        (object != null) ? model.createResource(object) : null
                );
                StmtIterator it = model.listStatements(selector);
                if (!it.hasNext()) {
                    LOG.warn("没有该三元组,不执行删除操作!");
                } else {
                    Statement stmt = model.createStatement
                            (
                                    model.createResource(subject),
                                    model.createProperty(predicate),
                                    model.createResource(object)
                            );
                    model.remove(stmt);
                    dataset.commit();
                    LOG.info("删除三元组: " + subject + " " + predicate + " " + object);
                }
            }
        } finally {
            if (model != null) model.close();
            dataset.end();
        }
    }

2.4 修改三元组
修改三元组可以采取先查找到需要修改的三元组,将其删去,再添加一条新的三元组,来进行三元组信息修改。

3. 编写相关接口和实现类

为了方便系统调用,将TDB CRUD操作封装到接口与相关实现类中。

3.1 TDB CRUD接口

public interface TDBCrudDriver {

    /**
     * 查询Model中三元组;
     */
    List<Statement> getTriplet
    (String modelName, String subject, String predicate, String object);

    /**
     * 增加Model中三元组;
     */
    void addTriplet
    (String modelName, String subject, String predicate, String object);

    /**
     * 删除Model中的三元组;
     */
    void removeTriplet
    (String modelName, String subject, String predicate, String object);

}

3.2 TDB CRUD接口实现类

public class TDBCrudDriverImpl implements TDBCrudDriver {

    public static final Log LOG = LogFactory.getLog(TDBCrudDriverImpl.class);
    public static final String tdb_path = "src\\main\\resources\\data\\kbfile\\TDB";

    @Override
    public List<Statement> getTriplet(String modelName, String subject, String predicate, String object) {

        List<Statement> list = new ArrayList<>();
        //如果不输入model name,则使用系统默认的TDB model;
        if(modelName == null) {
            modelName = "Default_Model";
            LOG.warn("MODEL NAME为空,使用默认model: Default_Model");
        }

        TDBPersistence tdbPersistence = new TDBPersistence(tdb_path);
        //如果TDB中有当前model;
        if (tdbPersistence.findModel(modelName)) {
            list = tdbPersistence.getTriplet(modelName, subject, predicate, object);
            tdbPersistence.closeTDB();
        }

        //如果TDB中没有当前model;
        else
            LOG.warn(modelName + " 不存在,无法查询!");
        return list;
    }

    @Override
    public void addTriplet(String modelName, String subject, String predicate, String object) {

        //如果不输入model name,则使用系统默认的TDB model:TDB_agriculture;
        if(modelName == null) {
            modelName = "Default_Model";
            LOG.warn("MODEL NAME为空,使用默认model: Default_Model");
        }

        TDBPersistence tdbPersistence = new TDBPersistence(tdb_path);
        //如果TDB中有当前model;
        if (tdbPersistence.findModel(modelName)) {
            tdbPersistence.addTriplet(modelName, subject, predicate, object);
            tdbPersistence.closeTDB();
        }

        //如果TDB中没有当前model;
        else
            LOG.warn(modelName + " 不存在,不执行添加操作!");
    }

    @Override
    public void removeTriplet(String modelName, String subject, String predicate, String object) {

        //如果不输入model name,则使用系统默认的TDB model:TDB_agriculture;
        if(modelName == null) {
            modelName = "Default_Model";
            LOG.warn("MODEL NAME为空,使用默认model: Default_Model");
        }

        TDBPersistence tdbPersistence = new TDBPersistence(tdb_path);
        //如果TDB中有当前model;
        if (tdbPersistence.findModel(modelName)) {
            tdbPersistence.removeTriplet(modelName, subject, predicate, object);
            tdbPersistence.closeTDB();
        }

        //如果TDB中没有当前model;
        else
            LOG.warn(modelName + " 不存在,不执行删除操作!");
    }
}

3.3 TDB CRUD单元测试类
编写单元测试模块进行功能测试。

class TDBCrudDriverTest {

    public static String ENTITY_PREFIX = "http://coindb/tupu/resource/";
    public static String PROPERTY_PREFIX = "http://coindb/tupu/property/";

    @Test
    void addTriplet() {

        //model name空缺,表示使用默认model:Default_Model;
        String model_name = "Default_Model";
        String subject = ENTITY_PREFIX + "杨桃";
        String predicate = PROPERTY_PREFIX + "病虫害";
        String object1 = ENTITY_PREFIX + "红蜘蛛";
        String object2 = ENTITY_PREFIX + "炭疽病";

        TDBCrudDriver tdbCrudDriver = new TDBCrudDriverImpl();
        tdbCrudDriver.addTriplet(model_name, subject, predicate, object1);
        tdbCrudDriver.addTriplet(null, subject, predicate, object2);
    }

    @Test
    void getTriplet() {

        //若主、谓、宾中有空缺,则表示该部分不参与匹配;
        //若主、谓、宾全部空缺,则输出所有三元组;
        String subject = ENTITY_PREFIX + "杨桃";
        String predicate = PROPERTY_PREFIX + "病虫害";
        String object = null;
        TDBCrudDriver tdbCrudDriver = new TDBCrudDriverImpl();
        List<Statement> list = tdbCrudDriver.getTriplet(null, subject, predicate, object);
        for (Statement s : list) {
            System.out.println(s);
        }
    }

    @Test
    void removeTriplet() {

        String subject = ENTITY_PREFIX + "杨桃";
        String predicate = PROPERTY_PREFIX + "病虫害";
        String object = ENTITY_PREFIX + "红蜘蛛";

        TDBCrudDriver tdbCrudDriver = new TDBCrudDriverImpl();
        tdbCrudDriver.removeTriplet(null, subject, predicate, object);
    }

}

4. TDB架构

在TDB中删除三元组后,可能在nodes.dat文件中,仍然可以看到被删除的三元组。但是使用相关查询代码,又无法查询到这个三元组。这并不是BUG,而是由于TDB的架构设计导致。

TDB使用一个类似顺序表的容器,存储节点。并且维护一个节点表(Node Table)作为索引,将RDF资源节点映射为一个64位的长整数ID。使用这个64位的ID用来索引节点,当遇到查询时,可以在顺序表容器中快速定位当前节点。

当新的数据被添加时,TDB会在顺序表容器中增加节点,并且在节点表中增加这个新结点的索引。在删除结点时,TDB仅仅在节点表中删除索引,但是在顺序表容器中并没有删除该节点,即节点仍然存在顺序表中。这样,不管有没有节点被删除,顺序表容器一直在增加。

TDB这样设计有两个好处:
1、当前新增节点的64位的长整数ID,表示顺序表容器的长度。为了加快插入速度,顺序表容器是一个地址连续的文件,每次在文件末尾(即最后一个新增节点的长整数ID索引处)增加节点即可。使用ID来索引节点,可以在O(1)时间就找到节点,极大提高查找速度。当一个节点从被删除,如果立刻在顺序表中删除这个节点,就需要重新计算被删除节点之后所有节点的ID,会大大提高计算量,降低查找速度。

2、假设现在一个节点被删除了,系统需要在所有节点中遍历,寻找最不频繁被使用的节点来做“真正的”删除,即从顺序表中删除这个节点,并重新计算后继节点的ID,重写节点表。如果每删除一个节点都这样操作一次,会大大提高升级和删除的相应时间,所以TDB不建议这么做。

用户也许会担心,TDB的删除设计,可能会导致磁盘空间的溢出。但实际上,在现实应用中,三元组的使用,大部分的操作都是新增和查询三元组的操作。如果发生了磁盘空间溢出,建议用户将所有当前三元组读出到.nt文件,将TDB中数据删除,再重新从.nt文件读取并建立TDB文件。当然,发生这种事件的概率,相对来说很低,用户可以不用过于担心。


以上,是笔者使用Jena TDB进行三元组数据增删改查的心得体会,分享之,与大家共同探讨。

 类似资料: