之前已经写过一篇文章简要介绍了图数据库Neo4j的概念,没看过的读者可以在此点链接《图数据库Neo4j简介》。本文主要讲解图数据库在真实项目中的实践应用,取自于我参与的真实项目代码。
后端用的是图数据库Neo4j来存节点和关系,前端用的是D3来画图。前后端交互是通过json数据来完成的,即Neo4j查出的结果组装成json后,传递给D3来画图。
1 Neo4j
首先看一下pom文件相关代码,如下:
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<version>1.5.0</version>
</dependency>
如图所示,我用的是neo4j-java-driver连接的Neo4j,当然也可以选择其他方式。例如Spring-Data-Neo4j等连接方式(只不过我所在项目中的spring版本和Spring-Data-Neo4j版本冲突,所以不能使用)。
然后是加载驱动的代码如下:
private static void driverConnect() {
InputStream in = GraphDriverManager.class.getClassLoader().getResourceAsStream(LOCAL);
Properties p = new Properties();
try {
p.load(in);
} catch (IOException e) {
e.printStackTrace();
}
String url = p.getProperty("neo4j.url");
String userName = p.getProperty("neo4j.username");
String passWord = p.getProperty("neo4j.password");
// 使用 basic authentication方式创建连接
try {
driver = GraphDatabase.driver(url, AuthTokens.basic(userName, passWord), Config.build().withMaxConnectionLifetime(30, TimeUnit.MINUTES).withMaxConnectionPoolSize(50).withConnectionAcquisitionTimeout(2, TimeUnit.MINUTES).toConfig());
resetFlag();
} catch (Exception e) {
logger.error("图数据库连接异常",e);
//当数据库连接异常时,把标志位写进文件,做提示用。
ErrorConnectPut();
}
}
java连接Neo4j的方式有三种:bolt、http和https,我选用的是bolt协议。同时为了方便,将Neo4j的url、用户名和密码做成properties文件来读取。
最后就是具体的调用代码了。如下所示:
@Override
public Map<Set<String>, Set<String>> searchAllPathsOfTwoNodes(String firstLabel, String firstName, String secondLabel, String secondName) {
Map<Set<String>, Set<String>> searchResults = null;
Driver driver = driverManager.getDriver();
try (Session session = driver.session()) {
searchResults = session.readTransaction(tx -> {
return standardGraphDao.searchAllPathsOfTwoNodes(tx, firstLabel, firstName, secondLabel, secondName);
});
} catch (Exception e) {
throw new RuntimeException(e);
}
return searchResults;
}
用内部类和lambda表达式的方式调用DAO层的代码。正如在之前文章中所提,应避免写出循环开闭事务的代码,应将循环放进DAO层里。
但是该种写法只是示例写法,用在实际的项目中会有很大的效率问题。假如在该方法中还需要调用test方法,那么不可避免的是test方法中仍然需要获取事务,这就会有嵌套事务的情况出现,在一些方法比较复杂和大数据量的执行下,效率会直线下降。解决办法是统一使用写事务(用写事务来代替读事务用以实现统一的事务获取,目前我还没有发现相关的bug),只有在最外面的方法才获取事务,里面如果有方法调用,则将事务tx一同作为参数传递过去,这样在整个方法中只有一次获取事务的情况出现。
但这样虽然解决了效率问题,在整个service层的开发却变得异常糟糕:充斥着大量的try...catch...业务无关语句。很自然的想到,可以用AOP进行改造,改造结果如下:
@Around("pointCut()")
public Object around(ProceedingJoinPoint jp) {
MethodSignature msig = (MethodSignature) jp.getSignature();
Method method = msig.getMethod();
Object returnType = method.getReturnType();
try {
Field txField = StandardGraphDao.class.getDeclaredField("tx");
txField.setAccessible(true);
Driver driver = driverManager.getDriver();
try (Session session = driver.session()) {
returnType = session.writeTransaction(tx -> {
try {
txField.set(StandardGraphDao.class.newInstance(), tx);
return jp.pr