本体的存储方法或称本体持久化,大致分为基于内存的方式、基于文件的方式、基于数据库的方式和专门的管理工具方式4种(傅柱等, 2013)。其中,基于数据库的方式又有基于关系数据库、基于面向对象数据库、基于Native XML数据库和基于NoSQL的三元组数据库(Triple Store)4种主要方式。基于数据库的本体持久化方式充分利用了数据库的安全可靠(数据保密、数据完整性、并发控制、故障恢复等)、高效、易于管理并易于与应用系统集成等优点,是主要的本体持久化方式。
在本体中,数据被表示为一系列由主语(subject)、谓词(predicate)和宾语(object)组成的陈述(statement),即三元组(triple)的集合。基于关系数据库的本体持久化使用二维表对本体的三元组进行处理,不可避免地需要对本体中的复杂关系进行不自然的分解,而查询时又需要将基于图的查询转换为关系查询(傅柱等, 2013) ,需要进行大量的关联、连接操作,因而,现有基于关系数据库的本体存储方法都存在大规模存储、更新、修改和查询效率低、数据库操作代价大等问题(李勇和李跃龙, 2008)。因此,效率、性能更优越的专门或扩展了RDF存储、查询甚至推理能力的非关系型三元组数据库(Triple Store,或称图数据库),如GraphDB (OWLIM) [1]、Virtuoso Universal Server[2]、AllegroGraph[3]、Jena TDB(Triple DB)等(Rohloff et al., 2007)目前已逐渐成为本体存储主流工具。本文采用基于Jena TDB的方式。
TDB存储的本体数据集由node表、Triple和Quad索引、prefixes表组成,存放在指定的文件系统目录下。TDB采用B+树维护三种基本形式的Triple索引:SPO、POS和OSP(S、P、O分别代表Subject、Predicate和Object)。若存在命名图(Named Graph),则同时维护相应的Quad索引(G表示Graph):GOSP、SPOG、GSPO、OSPG、GPOS和POSG。
如上图所示,本体应用程序首先通过URI(Uniform Resource Identifier)地址映射文件实现本体URI与其对应的本体模块文件存放的文件系统地址的映射,然后使用自定义的Java类TDBPortal通过程序或配置文件读取各本体模块文件持久化到TDB。TDB中数据可通过TDBPortal实现增删改查操作,和进一步应用于本体查询与推理等操作;或作为SPARQL(SPARQL Protocol and RDF Query Language)服务器Jena Fuseki的数据源,对外提供基于HTTP的SPARQL查询服务。
下面介绍TDBPortal
1 package cn.geodata.ont.tdb; 2 3 import java.util.ArrayList; 4 import java.util.Iterator; 5 import java.util.List; 6 7 import org.apache.commons.lang3.StringUtils; 8 import org.apache.jena.riot.RDFDataMgr; 9 import org.slf4j.Logger; 10 import org.slf4j.LoggerFactory; 11 12 import cn.geodata.ont.file.OntFile; 13 14 import com.hp.hpl.jena.ontology.OntModel; 15 import com.hp.hpl.jena.query.Dataset; 16 import com.hp.hpl.jena.query.ReadWrite; 17 import com.hp.hpl.jena.rdf.model.Model; 18 import com.hp.hpl.jena.rdf.model.ModelFactory; 19 import com.hp.hpl.jena.tdb.TDBFactory; 20 import com.hp.hpl.jena.tdb.base.file.Location; 21 22 /** 23 * @TODO TDB CRUD操作,包含事务 24 * @author Zhiwei HOU 25 * @date 2015年12月2日 26 */ 27 public class TDBPortal 28 { 29 final static Logger logger = LoggerFactory.getLogger(TDBPortal.class); 30 31 // 必须close 32 Dataset ds = null; 33 34 /** 35 * 连接TDB 36 * 37 * @param tdbPath TDB目录或配置文件tdb-assembler.ttl路径. tdbPath可以通过配置文件进行设置 38 * @param useAssemblerFile 是否使用配置文件连接 39 */ 40 public TDBPortal(String tdbPath, boolean useAssemblerFile) 41 { 42 if (!useAssemblerFile) 43 { 44 Location location = Location.create(tdbPath); 45 ds = TDBFactory.createDataset(location); 46 } 47 else 48 ds = TDBFactory.assembleDataset(tdbPath); 49 } 50 51 public TDBPortal(String tdbPath) 52 { 53 Location location = Location.create(tdbPath); 54 ds = TDBFactory.createDataset(location); 55 } 56 57 /** 58 * 往模型中添加内容。不载入引用本体 59 * 60 * @param modelUri 本体的uri 61 * @param sourcePath 本体文件实际地址 62 * @param override 是否覆盖 63 * @return 64 * @Houzw at 2016年4月1日下午11:36:13 65 */ 66 public int loadModel(String modelUri, String sourcePath, Boolean isOverride) 67 { 68 Model model = null; 69 ds.begin(ReadWrite.WRITE); 70 try 71 { 72 if (ds.containsNamedModel(modelUri)) 73 { 74 if (isOverride)// 覆盖 75 { 76 removeModel(modelUri);//只是移除地址,实际数据不会移除 77 loadModel(modelUri, sourcePath, false); 78 } 79 } 80 else 81 { 82 model = ds.getNamedModel(modelUri);// 没有则创建一个,model不会为null 83 model.begin(); 84 RDFDataMgr.read(model, sourcePath); 85 model.commit(); 86 } 87 // 已有,但是不覆盖,则直接返回 88 ds.commit(); 89 logger.info("本体模型数据已经导入"); 90 return 1; 91 } 92 catch (Exception e) 93 { 94 return 0; 95 } 96 finally 97 { 98 if (model != null) 99 model.close(); 100 ds.end(); 101 } 102 } 103 104 /** 105 * 导入本体。OntModel不支持事务。同时载入引用本体 106 * 107 * @param modelUri 模型uri 108 * @param sourcePath 本体文件(集成文件)地址 109 * @param override 是否覆盖 110 * @return 111 * @Houzw at 2016年4月1日下午11:36:09 112 */ 113 public int loadOntModel(String modelUri, String sourcePath, Boolean isOverride) 114 { 115 OntModel model = ModelFactory.createOntologyModel();// 不支持事务 116 ds.begin(ReadWrite.WRITE); 117 try 118 { 119 if (ds.containsNamedModel(modelUri)) 120 { 121 if (isOverride)// 覆盖 122 { 123 removeModel(modelUri); 124 loadOntModel(modelUri, sourcePath, false); 125 } 126 } 127 else 128 { 129 model = OntFile.loadOntModelWithLocMapper(sourcePath);//导入本体文件 130 ds.addNamedModel(modelUri, model); 131 132 } 133 // 已有,但是不覆盖,则直接返回 134 ds.commit(); 135 System.out.println(modelUri + " 已导入"); 136 logger.info(modelUri + " 已导入"); 137 return 1; 138 } 139 catch (Exception e) 140 { 141 System.out.println(e.getLocalizedMessage()); 142 logger.error(e.getLocalizedMessage()); 143 return 0; 144 } 145 finally 146 { 147 ds.end(); 148 } 149 } 150 151 152 public Model getDefaultModel() 153 { 154 ds.begin(ReadWrite.READ); 155 Model model; 156 try 157 { 158 model = ds.getDefaultModel(); 159 ds.commit(); 160 } 161 finally 162 { 163 ds.end(); 164 } 165 return model; 166 } 167 168 /** 169 * 获取指定模型 170 */ 171 public Model getModel(String modelUri) 172 { 173 Model model = null; 174 ds.begin(ReadWrite.READ); 175 try 176 { 177 model = ds.getNamedModel(modelUri); 178 } 179 finally 180 { 181 ds.end(); 182 } 183 return model; 184 } 185 186 187 public void loadDefaultModel(String sourcePath) 188 { 189 Model model = null; 190 ds.begin(ReadWrite.WRITE); 191 try 192 { 193 model = ds.getDefaultModel(); 194 model.begin(); 195 if (!StringUtils.isBlank(sourcePath)) 196 RDFDataMgr.read(model, sourcePath); 197 model.commit(); 198 ds.commit(); 199 } 200 finally 201 { 202 if (model != null) 203 model.close(); 204 ds.end(); 205 } 206 } 207 208 209 public void removeModel(String modelUri) 210 { 211 if (!ds.isInTransaction()) 212 ds.begin(ReadWrite.WRITE); 213 try 214 { 215 ds.removeNamedModel(modelUri); 216 ds.commit(); 217 System.out.println(modelUri + " 已被移除"); 218 logger.info(modelUri + " 已被移除"); 219 } 220 finally 221 { 222 ds.end(); 223 } 224 } 225 226 /** 227 * 列出所有模型的uri 228 */ 229 public List<String> listModels() 230 { 231 ds.begin(ReadWrite.READ); 232 List<String> uriList = new ArrayList<>(); 233 try 234 { 235 Iterator<String> names = ds.listNames();// DefaultModel没有name 236 String name = null; 237 while (names.hasNext()) 238 { 239 name = names.next(); 240 uriList.add(name); 241 } 242 } 243 finally 244 { 245 ds.end(); 246 } 247 return uriList; 248 } 249 250 /** 251 * 必须关闭TDB连接 252 */ 253 254 public void close() 255 { 256 ds.close(); 257 } 258 }
以上简单介绍了基于Jena TDB的本体存储。目前我对AssemblerFile配置文件的配置还没有深入的研究,了解的朋友可以告诉我,O(∩_∩)O谢谢
水平有限,错误难免,多指教。TDB 的具体内容可查阅其官方文档
[1] http://ontotext.com/products/graphdb/
[2] http://www.openlinksw.com/
[3] http://franz.com/