Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

时间:2022-10-19 23:51:50

1 应用开发概述

基于数据传输效率以及接口自定义等特殊性需求,我们暂时放弃使用Neo4j服务器版本,而是在Neo4j嵌入式版本的基础上进行一些封装性的开发。封装的重点,是解决Neo4j嵌入式版本EmbeddedGraphDatabase中不能同时创建多个实例指向同一个数据库的问题。如果开发人员使用Neo4j嵌入式版本作为数据库,要想实现多个程序共享一个数据库,这将是一个不可回避的问题。本手册给出的解决方案是“构建一个中间服务层,提供各种接口方法,指向同一个数据库实例;其他客户端程序通过中间服务层与Neo4j嵌入式数据库进行通信”。因为我们已经从Neo4j官方声明中得知:Neo4j嵌入式实例可以在多个线程*享。

系统框架如下图所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

  • Neo4j Java 工具包

Neo4j Java 工具包是对Neo4j嵌入式版本Java API的二次封装,根据业务类型划分为Node(Relationship)、Index、Path和Cypher等四种工具集。

  • 管理工具Server端

之所以称其为Server端,是因为其中包含了对RMI Server的管理。此管理工具的主要功能包括图数据库信息管理(包括新建、删除、统计图数据库等)、图数据库数据管理(包括Node数据管理、Relationship数据管理等)、数据导入导出(包括Neo4j与oracle、mysql和excel等之间的数据转换)、RMI Server监控管理等。

管理工具Server端只能部署在Neo4j数据库服务器上,并且只能部署一套程序,否则将违背Neo4j图数据库单例的原则。

  • RMI Service(服务)

RMI Server(服务),是在Neo4j Java 工具包的基础上,设计的一套接口服务,分为Server端和Client端。Server端用于接口方法的实现和监控管理,Client端用于接口方法的定义和分发(供其他外部系统使用)。总体设计思路,是将Neo4j中的关键对象(Node、Relationship、Path、Direction等)进行可序列化的封装,通过远程调用服务返回给客户端使用。

  • 管理工具Client端

管理工具Client端,是基于RMI Client设计的Neo4j数据管理工具,主要功能包括图数据库信息查看功能、图数据库数据管理功能、数据导入导出功能等。管理工具Client端可以部署多套。

2 Neo4j Java 工具包

以上所有功能(工具包、RMI Service以及管理工具)都包含在两个Java项目中,项目结构如下图所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

hnepri-neo4j-common为工具包项目,包括Neo4j Java 工具包、RMI Server端和管理工具Server端;hnepri-neo4j-client为客户端项目,包括RMI Client端和管理工具Client端。

下面主要介绍Neo4j Java 工具包的几个封装关键点,其中,下图为各个工具类之间的关联效果图。

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

2.1 Node操作工具类(GraphNodeUtil)

Node操作工具类的主要功能包括Node节点和Relationship关系的创建、编辑、删除、查询,以及Label和Property的管理等。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

2.2 Index操作工具类(GraphIndexUtil)

Index操作工具类的主要功能包括Node和Relationship相关索引信息的创建、编辑、删除、查询,以及基于索引查询Node节点和Relationship关系等。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

2.3 Path操作工具类(GraphPathUtil)

Path操作工具类的主要功能包括针对Path的检索操作,包括路径深度遍历、两点之间路径寻址等接口方法。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。相关接口方法截图如下所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

2.4 Cypher操作工具类(GraphCypherUtil)

Cypher操作工具类是对Cypher查询语言的封装,主要包括针对Node、Relationship和Path的自定义查询操作。另外,也包括了多种方式的分页查询。对于其中的部分接口方法,根据实际情况和需要则进行了事务处理。

其中,最基本的接口方法是executeQuery方法,执行Cypher查询语句,将查询结果以Properties的形式存在在List中,然后再由其他接口方法显式地转换为Node、Relationship、Path或者其他基本类型使用。

        /**
* 执行Cypher查询语句,将检索结果封装进Properties列表中。
* @param query cypher查询语句
* @param params cypher查询语句参数集合
* @return
*/
public List<Properties> executeQuery(String query, Map<String,Object> params) {
GraphTimerModel timer = GraphTimerModel.create();
List<Properties> propertiesList = new ArrayList<Properties>();
if(StringUtils.isBlank(query)) {
return propertiesList;
}
ExecutionResult result = null;
if(params == null || params.size() == 0) {
result = this.getExecutionEngine().execute(query);
} else {
result = this.getExecutionEngine().execute(query, params);
}
LogInfoUtil.printLog(query); for (Map<String, Object> row : result ) {
Properties properties = new Properties();
for ( Entry<String, Object> column : row.entrySet()){
properties.put(column.getKey(), column.getValue());
}
propertiesList.add(properties);
}
timer.initEndTime();
timer.printTimeInfo();
return propertiesList;
}
举例如下所示:
String query = "START n=node(*) WHERE n.name=’tom’ RETURN n AS NODE_ENTRY";
List<Properties> list = this.executeQuery(query);
for(Properties properties : list) {
nodeList.add((Node)properties.get("NODE_ENTRY"));
}

相关接口方法截图如下所示:

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

Neo4j图数据库管理系统开发笔记之一:Neo4j Java 工具包

2.5 动态生成RelationshipType(GraphRelTypeUtil)

动态生成RelationshipType是构建RMI服务必须首先要解决的一个关键点,因为RMI要求Server端与Client端之间的传输对象必须是可序列化的对象,而Neo4j API中的接口类和枚举是无法真正序列化的,这也是我们在RMI Service中对相关实体进行封装的根本原因。

所谓动态生成RelationshipType,就是可根据字符串类型的关系类型,生成符合Neo4j Java API要求的枚举类型RelationshipType。关键代码如下表所示:

 package com.hnepri.neo4j.common.util;

 import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map; import org.apache.commons.lang3.StringUtils;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Transaction; import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.ReflectionFactory; /**
* Description: 图数据库关系类型工具类。<br>
* 1、可根据关系类型名称字符串动态生成关系类型枚举。<br>
* 2、可管理动态生成的关系类型枚举列表。
* Copyright: Copyright (c) 2015<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2015-11-01编写
* @version 1.0
*/
public class GraphRelTypeUtil { /**
* 构造函数。<br>
* 初始化对应的图数据库服务对象实例。
* @param graphDBService
*/
public GraphRelTypeUtil(GraphDatabaseService graphDBService) {
this.graphDBService = graphDBService;
} private GraphDatabaseService graphDBService = null;
/**
* 获取对应的图数据库服务对象实例。
* @return
*/
public GraphDatabaseService getGraphDBService() {
return this.graphDBService;
} /**
* 构建事务。
* @return
*/
public Transaction createTransaction() {
return this.getGraphDBService().beginTx();
} private GraphIndexUtil indexManager = null;
/**
* 获取图数据库索引信息管理器。
* @return
*/
public GraphIndexUtil getIndexManager() {
if(this.indexManager == null) {
this.indexManager = new GraphIndexUtil(this.getGraphDBService());
}
return this.indexManager;
} private Map<String,RelationshipType> relationshipTypeList = null;
/**
* 获取已动态生成的关系枚举类型列表。
* @return
*/
@SuppressWarnings("deprecation")
public Map<String, RelationshipType> getRelationshipTypeList() {
if(this.relationshipTypeList == null) {
this.relationshipTypeList = new HashMap<String, RelationshipType>(); Iterator<RelationshipType> iterator = this.getGraphDBService().getRelationshipTypes().iterator();
while(iterator.hasNext()) {
RelationshipType relType = iterator.next();
String relTypeName = relType.name();
this.relationshipTypeList.put(relTypeName, relType);
}
}
return this.relationshipTypeList;
} /**
* 根据关系类型名称动态生成图数据库关系枚举类型。
* @param relTypeName
* @return
*/
public RelationshipType create(String relTypeName) {
if(StringUtils.isBlank(relTypeName)) {
return null;
}
if(this.getRelationshipTypeList().containsKey(relTypeName) == false) {
addEnum(relTypeName);
RelationshipType relType = RelationshipTypeEnum.valueOf(relTypeName);
this.getRelationshipTypeList().put(relTypeName, relType);
return relType;
} else {
return this.getRelationshipTypeList().get(relTypeName);
}
} /**
* 根据关系类型名称,获取对应的关系枚举类型。
* @param relTypeName
* @return
*/
public RelationshipType get(String relTypeName) {
if(StringUtils.isBlank(relTypeName)) {
return null;
}
return this.create(relTypeName);
} /**
* 根据关系类型名称,删除对应的关系枚举类型。
* @param relTypeName
* @return
*/
public void remove(String relTypeName) {
if(this.getRelationshipTypeList().containsKey(relTypeName)) {
this.getRelationshipTypeList().remove(relTypeName);
}
} /**
* 根据关系类型名称列表,初始化关系类型枚举列表。
* @param relTypeNameList
*/
public void init(List<String> relTypeNameList) {
if(relTypeNameList == null || relTypeNameList.size() == 0) {
return;
}
for(String relTypeName : relTypeNameList) {
create(relTypeName);
}
} private static ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory(); private static void setFailsafeFieldValue(Field field, Object target, Object value) throws NoSuchFieldException,IllegalAccessException {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
int modifiers = modifiersField.getInt(field); modifiers &= ~Modifier.FINAL;
modifiersField.setInt(field, modifiers); FieldAccessor fa = reflectionFactory.newFieldAccessor(field, false);
fa.set(target, value);
} private static void blankField(Class<?> enumClass, String fieldName) throws NoSuchFieldException, IllegalAccessException {
for (Field field : Class.class.getDeclaredFields()) {
if (field.getName().contains(fieldName)) {
AccessibleObject.setAccessible(new Field[] { field }, true);
setFailsafeFieldValue(field, enumClass, null);
break;
}
}
} private static void cleanEnumCache(Class<?> enumClass) throws NoSuchFieldException, IllegalAccessException {
blankField(enumClass, "enumConstantDirectory");
blankField(enumClass, "enumConstants");
} private static ConstructorAccessor getConstructorAccessor(Class<?> enumClass, Class<?>[] additionalParameterTypes) throws NoSuchMethodException {
Class<?>[] parameterTypes = new Class[additionalParameterTypes.length + 2];
parameterTypes[0] = String.class;
parameterTypes[1] = int.class;
System.arraycopy(additionalParameterTypes, 0, parameterTypes, 2, additionalParameterTypes.length);
return reflectionFactory.newConstructorAccessor(enumClass.getDeclaredConstructor(parameterTypes));
} private static Object makeEnum(Class<?> enumClass, String value, int ordinal, Class<?>[] additionalTypes, Object[] additionalValues) throws Exception {
Object[] parms = new Object[additionalValues.length + 2];
parms[0] = value;
parms[1] = Integer.valueOf(ordinal);
System.arraycopy(additionalValues, 0, parms, 2, additionalValues.length);
return enumClass.cast(getConstructorAccessor(enumClass, additionalTypes).newInstance(parms));
} @SuppressWarnings("unchecked")
private static <T extends Enum<?>> void addEnum(String enumName) {
if (!Enum.class.isAssignableFrom(RelationshipTypeEnum.class)) {
throw new RuntimeException("class " + RelationshipTypeEnum.class + " is not an instance of Enum");
} Field valuesField = null;
Field[] fields = RelationshipTypeEnum.class.getDeclaredFields();
for (Field field : fields) {
if (field.getName().contains("$VALUES")) {
valuesField = field;
break;
}
}
AccessibleObject.setAccessible(new Field[] { valuesField }, true); try { T[] previousValues = (T[]) valuesField.get(RelationshipTypeEnum.class);
List<T> values = new ArrayList<T>(Arrays.asList(previousValues)); T newValue = (T) makeEnum(RelationshipTypeEnum.class, enumName, values.size(), new Class<?>[] {}, new Object[] {});
values.add(newValue);
setFailsafeFieldValue(valuesField, null, values.toArray((T[]) Array.newInstance(RelationshipTypeEnum.class, 0)));
cleanEnumCache(RelationshipTypeEnum.class);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e.getMessage(), e);
}
}
} /**
* Description: 图数据库关系类型枚举。
* Copyright: Copyright (c) 2015<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2015-11-01编写
* @version 1.0
*/
enum RelationshipTypeEnum implements RelationshipType {
NONE
}

2.6 图数据库操作模板工具类(GraphTemplateUtil)

图数据库操作模板工具类,主要负责对以上工具集的组织和调用管理,以保证开发人员在调用习惯上遵循Neo4j嵌入式图数据库单例服务的原则。关键代码如下表所示:

 package com.hnepri.neo4j.common.util;

 import java.io.File;
import java.util.HashMap;
import java.util.Map; import org.apache.commons.lang3.StringUtils;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.factory.GraphDatabaseFactory;
import org.neo4j.io.fs.FileUtils; import com.hnepri.neo4j.client.form.graph.bean.GraphConfigOption;
import com.hnepri.neo4j.form.graph.util.GraphManageUtil; /**
* Description: Neo4j图数据库操作模板类。<br>
* Copyright: Copyright (c) 2015<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2015-11-01 编写
* @version 1.0
*/
public class GraphTemplate { private String graphDBPath = "";
/**
* 获取图数据库路径。
* @return
*/
public String getGraphDBPath() {
return graphDBPath;
}
private GraphDatabaseService graphDBService = null;
/**
* 获取图数据库服务实例对象。
* @return
*/
public GraphDatabaseService getGraphDBService() {
if(StringUtils.isBlank(this.getGraphDBPath())) {
try {
throw new Exception("警告:没有配置图数据库路径信息!");
} catch (Exception e) {
e.printStackTrace();
}
}
if(this.graphDBService == null) {
this.graphDBService = new GraphDatabaseFactory().newEmbeddedDatabase(this.getGraphDBPath());
registerShutdownHook();
}
return this.graphDBService;
} /**
* 清除图数据库数据文件信息。
*/
public void clearGraphDB() {
try {
FileUtils.deleteRecursively(new File(this.getGraphDBPath()));
if(graphTemplateList.containsKey(this.getGraphDBPath())) {
graphTemplateList.remove(this.getGraphDBPath());
}
} catch (Exception ex) {
ex.printStackTrace();
}
} /**
* 注册图数据库关闭钩子。
*/
public void registerShutdownHook() {
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run(){
getGraphDBService().shutdown();
}
});
} /**
* 构造函数。初始化图数据库路径。
* @param graphDBPath
*/
private GraphTemplate(String graphDBPath) {
this.graphDBPath = graphDBPath;
} private static Map<String, GraphTemplate> graphTemplateList = new HashMap<String, GraphTemplate>();
/**
* 根据图数据库路径信息,获取对应的GraphTemplate对象实例。<br>
* 目的是采用GraphTemplate的单例模式。
* @param graphPath
* @return
*/
public static GraphTemplate getInstance(String graphPath) {
if(graphTemplateList.containsKey(graphPath) == false) {
GraphTemplate template = new GraphTemplate(graphPath);
graphTemplateList.put(graphPath, template);
}
return graphTemplateList.get(graphPath);
} /**
* 根据图数据库名称标示信息,获取对应的GraphTemplate对象实例。<br>
* 目的是采用GraphTemplate的单例模式。
* @param graphName
* @return
*/
public static GraphTemplate getInstanceByName(String graphName) {
if(GraphManageUtil.getGraphConfigOptionList().containsKey(graphName) == false) {
return null;
}
GraphConfigOption option = GraphManageUtil.getGraphConfigOptionList().get(graphName);
return getInstance(option.getPath());
} /**
* 构建事务。
* @return
*/
public Transaction createTransaction() {
return this.getGraphDBService().beginTx();
} private GraphIndexUtil indexUtil = null;
/**
* 获取图数据库索引信息管理功能库。
* @return
*/
public GraphIndexUtil getIndexUtil() {
if(this.indexUtil == null) {
this.indexUtil = new GraphIndexUtil(this.getGraphDBService());
}
return this.indexUtil;
} private GraphNodeUtil nodeUtil = null;
/**
* 获取图数据库节点信息管理功能库。
* @return
*/
public GraphNodeUtil getNodeUtil() {
if(this.nodeUtil == null) {
this.nodeUtil = new GraphNodeUtil(this.getGraphDBService());
}
return this.nodeUtil;
} private GraphCypherUtil cypherUtil = null;
/**
* 获取图数据库Cypher查询语言功能库。
* @return
*/
public GraphCypherUtil getCypherUtil() {
if(this.cypherUtil == null) {
this.cypherUtil = new GraphCypherUtil(this.getGraphDBService());
}
return this.cypherUtil;
} private GraphPathUtil pathUtil = null;
/**
* 获取图数据库Path信息功能库。
* @return
*/
public GraphPathUtil getPathUtil() {
if(this.pathUtil == null) {
this.pathUtil = new GraphPathUtil(this.getGraphDBService());
}
return this.pathUtil;
} private GraphRelTypeUtil relTypeUtil = null;
/**
* 获取图数据库关系类型信息功能库。
* @return
*/
public GraphRelTypeUtil getRelTypeUtil() {
if(this.relTypeUtil == null) {
this.relTypeUtil = new GraphRelTypeUtil(this.getGraphDBService());
}
return this.relTypeUtil;
}
}

2.7 数据分页模型GraphPageModel

数据分页模型主要是基于Cypher查询语言中的SKIP和LIMIT而设计的针对Node和Relationship的分页处理模型。关键代码如下表所示:

 package com.hnepri.neo4j.common.model;

 import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List; import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship; import com.hnepri.common.util.LogInfoUtil; /**
* Description: 图数据库数据分页模型类。<br>
* 利用此类可分页管理Node数据和Relationship数据等。
* Copyright: Copyright (c) 2015<br>
* Company: 河南电力科学研究院智能电网所<br>
* @author shangbingbing 2015-11-01编写
* @version 1.0
*/
public class GraphPageModel implements Serializable {
private static final long serialVersionUID = 330410716100946538L;
private int pageSize = 10;
private int pageIndex = 1;
private int prevPageIndex = 1;
private int nextPageIndex = 1;
private int pageCount = 0;
private int pageFirstRowIndex = 1;
private boolean hasNextPage = true;
private int totalCount = 0;
private long startTime = System.currentTimeMillis();
private long endTime = System.currentTimeMillis();
private List<Node> nodeList = new ArrayList<Node>();
private List<Relationship> relationshipList = new ArrayList<Relationship>(); /**
* 分页对象构造函数
* @param pageSize 每页记录数
*/
public GraphPageModel(int pageSize) {
this.pageSize = pageSize;
} /**
* 获取分页记录数量
* @return
*/
public int getPageSize() {
return pageSize;
}
/**
* 获取当前页序号
* @return
*/
public int getPageIndex() {
return pageIndex;
}
/**
* 设置当前页序号
* @param pageIndex
*/
public void setPageIndex(int pageIndex) {
if(pageIndex <= 0) {
pageIndex = 1;
}
this.pageIndex = pageIndex;
}
/**
* 获取分页总数
* @return
*/
public int getPageCount() {
if(this.getTotalCount() == 0) {
this.pageCount = 0;
} else {
int shang = this.getTotalCount() / this.getPageSize();
int yu = this.getTotalCount() % this.getPageSize();
if(yu > 0) {
shang += 1;
}
this.pageCount = shang;
}
return pageCount;
}
/**
* 获取每页的第一行序号
* @return
*/
public int getPageFirstRowIndex() {
this.pageFirstRowIndex = (this.pageIndex - 1) * this.getPageSize() + 1;
return pageFirstRowIndex;
}
/**
* 获取上一页序号
* @return
*/
public int getPrevPageIndex() {
if(this.pageIndex > 1) {
this.prevPageIndex = this.pageIndex - 1;
} else {
this.prevPageIndex = 1;
}
return prevPageIndex;
}
/**
* 获取下一页序号
* @return
*/
public int getNextPageIndex() {
if(this.pageIndex < this.pageCount) {
this.nextPageIndex = this.pageIndex + 1;
} else {
this.nextPageIndex = this.pageCount;
}
return nextPageIndex;
}
/**
* 跳转到下一页
*/
public void nextPage() {
if(this.totalCount == 0 || this.getPageCount() == 0) {
this.pageIndex = 1;
} else {
if(this.pageIndex < this.pageCount) {
this.pageIndex = this.pageIndex + 1;
} else {
this.pageIndex = this.pageCount;
}
}
}
/**
* 跳转到上一页
*/
public void prevPage() {
if(this.pageIndex > 1) {
this.pageIndex = this.pageIndex - 1;
} else {
this.pageIndex = 1;
}
}
/**
* 获取是否有下一页
* @return
*/
public boolean isHasNextPage() {
if(this.pageIndex < this.getPageCount()) {
this.hasNextPage = true;
} else {
this.hasNextPage = false;
}
return hasNextPage;
}
/**
* 获取总记录数
*/
public int getTotalCount() {
return totalCount;
}
/**
* 获取总记录数
* @param totalCount
*/
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
/**
* 初始化起始时间(毫秒)
*/
public void initStartTime() {
this.startTime = System.currentTimeMillis();
}
/**
* 初始化截止时间(毫秒)
*/
public void initEndTime() {
this.endTime = System.currentTimeMillis();
}
/**
* 获取毫秒格式的耗时信息
* @return
*/
public String getTimeIntervalByMilli() {
return String.valueOf(this.endTime - this.startTime) + "毫秒";
}
/**
* 获取秒格式的耗时信息
* @return
*/
public String getTimeIntervalBySecond() {
double interval = (this.endTime - this.startTime)/1000.0;
DecimalFormat df = new DecimalFormat("#.##");
return df.format(interval) + "秒";
}
/**
* 打印时间信息
*/
public void printTimeInfo() {
LogInfoUtil.printLog("起始时间:" + this.startTime);
LogInfoUtil.printLog("截止时间:" + this.endTime);
LogInfoUtil.printLog("耗费时间:" + this.getTimeIntervalBySecond());
}
/**
* 获取Node检索结果列表
* @return
*/
public List<Node> getNodeList() {
return nodeList;
}
/**
* 获取Relationship检索结果列表
* @return
*/
public List<Relationship> getRelationshipList() {
return relationshipList;
}
}

【未完待续】

下一篇  Neo4j图数据库应用开发之二:RMI Service开发