新技能-大数据利器之图数据库neo4j:4. 开发实操

neo4j 开发模式

neo4j开发模式有两种:

  1. Bolt嵌入式开发模式,提供本地客户端jar包,由java端内嵌调用,操作neo4j数据库。
  2. 远程REST API模式,neo4j设计之初只考虑java使用,后期为兼容其他语言,增加接口调用。

本人主要使用Bolt模式开发,所以rest api模式不做多讲,感兴趣的同学可以参考官方文档。

REST API模式

主要采用postman工具做演示:

  1. 采用post模式,请求链接为:http://127.0.0.1:7474/db/data/transaction/commit,选择Basic Auth模式,配置neo4j登录用户名及密码
    在这里插入图片描述
  2. 配置请求头信息,Content-Type:application/json
    在这里插入图片描述
  3. 输入请求参数,获取当前数据库里面的node数量
    {“statements” : [ { “statement” : “match (n) return count(n)” } ]}
    在这里插入图片描述
  4. 最终响应
    在这里插入图片描述

Bolt嵌入式开发

采用spring-boot工程开发,如下贴出主要代码:

  1. 配置pom.xml文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.rzx</groupId>
	<artifactId>neo4j</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>neo4j</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<fastjson.version>1.2.7</fastjson.version>
	</properties>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.3.RELEASE</version> <!-- lookup parent from repository -->
	</parent>
	<dependencies>
		<!--springboot 基础 start -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<!--<scope>test</scope> -->
		</dependency>
		<!--springboot 基础 end -->

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-neo4j</artifactId>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>${fastjson.version}</version>
		</dependency>
		
		<dependency>
			<groupId>ru.yandex.clickhouse</groupId>
			<artifactId>clickhouse-jdbc</artifactId>
			<version>0.1.53</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
	</dependencies>
</project>

  1. 修改application.properties,新增如下配置
    spring.data.neo4j.uri=bolt://localhost:7687
    spring.data.neo4j.username=neo4j
    spring.data.neo4j.password=rzx1218

此时程序已经初始化完毕,接下来就是要开发工作了

开发实操-节点操作

  1. 新建节点实体类
package com.rzx.neo4j.entity;

import lombok.Getter;
import lombok.Setter;

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;

/**
 * sheeps
 * 
 * @author yangwei
 *
 */
@NodeEntity
@Getter
@Setter
public class Sheeps {

	@Id
	@GeneratedValue
	public Long id;
	/**
	 * 名称
	 */
	private String name;

	/**
	 * 角色
	 */
	private String role;

	@Override
	public String toString() {
		return "{name:\"" + name + "\",role:\"" + role + "\"" + ",id:" + id + "}";
	}

}

  1. 新建dao层
package com.rzx.neo4j.dao;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;

import com.rzx.neo4j.entity.Sheeps;

@Repository
public interface SheepsDao extends Neo4jRepository<Sheeps, Long> {

	@Query(value = "match p=((a:Sheeps{name:{0}})-[*..5]->()) return p",
			countQuery= "match p=((a:Sheeps{name:{0}})-[*..5]->()) return  count(p)")
	Page<Sheeps> findPage(String name, Pageable pageable);
	
	Sheeps findByName(String name);
	
	
}

  1. 新增service层
package com.rzx.neo4j.service;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import com.rzx.neo4j.dao.SheepsDao;
import com.rzx.neo4j.entity.Sheeps;

@Service
public class SheepsService {

	@Autowired
	private SheepsDao dao;

	/**
	 * 查询所有的sheep数据
	 * @return sheep数据集合
	 */
	public List<Sheeps> findAllSheep() {
		List<Sheeps> list = new ArrayList<Sheeps>();
		dao.findAll().forEach(new Consumer<Sheeps>() {

			@Override
			public void accept(Sheeps t) {
				list.add(t);
			}
		});
		return list;
	}

	/**
	 * 根据名称分页查询sheep数据
	 * @param name 名称 
	 * @return 分页对象
	 */
	public Page<Sheeps> findPageByName(String name) {
		Pageable pageable = PageRequest.of(0, 10);
		return dao.findPage(name, pageable);
	}
	
	/**
	 * 新增sheep数据
	 * @param name
	 * @param role
	 */
	public void save(String name,String role) {
		Sheeps sheeps = new Sheeps();
		sheeps.setName(name);
		sheeps.setRole(role);
		dao.save(sheeps);
	}
	
	/**
	 * 根据ID删除sheeps数据
	 * @param id
	 */
	public void delete(Long id) {
		dao.deleteById(id);
	}
}

  1. 操作层
package com.rzx.neo4j.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.rzx.neo4j.entity.Sheeps;
import com.rzx.neo4j.service.SheepsRelationshipService;
import com.rzx.neo4j.service.SheepsService;

/**
 * sheeps数据控制层
 * @author yangwei
 *
 */
@RestController
@RequestMapping("/api/1.0/sheep")
public class SheepsController {

	@Autowired
	SheepsService sheepsService;

	@Autowired
	SheepsRelationshipService sheepsRelationshipService;

	@GetMapping("/findAllSheep")
	public List<Sheeps> findAllSheep() {
		return sheepsService.findAllSheep();
	}
	@GetMapping("/findPageByName")
	public Page<Sheeps> findPageByName(String name) {
		return sheepsService.findPageByName(name);
	}
	@GetMapping("/sava")
	public void sava(String name, String role) {
		sheepsService.save(name, role);
	}
	@GetMapping("/delete")
	public void delete(Long id) {
		sheepsService.delete(id);
	}
}

以上就完成了对neo4j数据库节点的操作了,主要包含对节点增删改查操作。

开发实操-关系操作

  1. 新增一个夫妻关系的实体类
package com.rzx.neo4j.entity;

import lombok.Getter;
import lombok.Setter;

import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;

@Getter
@Setter
@RelationshipEntity(type = "夫妻")
public class FuqiRelationship {
	
	@Id
	@GeneratedValue
	private Long id; // 关系id

	
	@StartNode
	private Sheeps start; // 开始节点

	@EndNode
	private Sheeps end; // 结束节点
}

  1. 新增dao层
package com.rzx.neo4j.dao;

import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;

import com.rzx.neo4j.entity.FuqiRelationship;

@Repository
public interface FuqiSheepsRelationshipDao extends Neo4jRepository<FuqiRelationship, Long> {

}
  1. 新增service层
package com.rzx.neo4j.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.neo4j.driver.Record;
import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.Value;
import org.neo4j.driver.types.Node;
import org.neo4j.driver.types.Path;
import org.neo4j.driver.types.Relationship;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.rzx.neo4j.dao.FuqiSheepsRelationshipDao;
import com.rzx.neo4j.dao.SheepsDao;
import com.rzx.neo4j.entity.FuqiRelationship;
import com.rzx.neo4j.entity.Sheeps;

@Service
public class SheepsRelationshipService {

	@Autowired
	private FuqiSheepsRelationshipDao dao;

//	@Autowired
//	private FuziSheepsRelationshipDao fuziSheepsRelationshipDao;
//
//	@Autowired
//	private XiongdiSheepsRelationshipDao xiongdiSheepsRelationshipDao;

	@Autowired
	private SheepsDao sheepsDao;

	@Autowired
	private Session session;

	/**
	 * 新增一个关系
	 * @param startName 开始节点名称
	 * @param endName 结束节点名称
	 * @param relationship 创建关系
	 */
	public void sava(String startName, String endName, String relationship) {
		Sheeps startNode = sheepsDao.findByName(startName);
		Sheeps endNode = sheepsDao.findByName(endName);
		switch (relationship) {
		case "夫妻":
			FuqiRelationship sheepsRelationship = new FuqiRelationship();
			sheepsRelationship.setEnd(endNode);
			sheepsRelationship.setStart(startNode);
			dao.save(sheepsRelationship);
			break;
//		case "父子":
//			FuziRelationship fuziRelationship = new FuziRelationship();
//			fuziRelationship.setEnd(endNode);
//			fuziRelationship.setStart(startNode);
//			fuziSheepsRelationshipDao.save(fuziRelationship);
//			break;
//		case "兄弟":
//			XiongdiRelationship xiongdiRelationship = new XiongdiRelationship();
//			xiongdiRelationship.setEnd(endNode);
//			xiongdiRelationship.setStart(startNode);
//			xiongdiSheepsRelationshipDao.save(xiongdiRelationship);
//			break;
		default:
			break;
		}

	}

	/**
	 * 根据开始节点查询关联关系节点,可指定结束节点查询
	 * @param startNode 开始节点名称
	 * @param endNode 结束节点名称
	 * @return
	 */
	public Map<String, List<Map<String, Object>>> findByNameAndRelation(String startNode, String endNode) {
		String cypherSql = String.format("match r = ((a:Sheeps{name:\"%s\"}) - [*..5] -> ()) return r", startNode);
		if (StringUtils.isNotEmpty(endNode)) {
			cypherSql = String.format("match (a:Sheeps {name:\"%s\"}) - [b] -> (c:Sheeps {name:\"%s\"}) return a,b,c", startNode, endNode);
		}
		Result result = session.run(cypherSql);
		Map<String, List<Map<String, Object>>> resultMap = new HashMap<String, List<Map<String, Object>>>();
		List<Map<String, Object>> nodesList = new ArrayList<Map<String, Object>>();
		List<Map<String, Object>> relationshipList = new ArrayList<Map<String, Object>>();
		Map<Long, String> nodeIds = new HashMap<Long, String>(); // 保存id,用于去重
		Map<Long, String> relationIds = new HashMap<Long, String>(); // 保存id,用于去重
		while (result.hasNext()) {
			Record record = result.next();
			List<Value> values = record.values();
			for (Value value : values) {
				if ("NODE".equals(value.type().name())) {
					Node node = value.asNode();
					if (!nodeIds.containsKey(node.id())) {
						Map<String, Object> map = setMap(node);
						nodesList.add(map);
						nodeIds.put(node.id(), "");
					}
				} else if ("RELATIONSHIP".equals(value.type().name())) {
					Relationship relationship = value.asRelationship();
					Map<String, Object> map = setMap(relationship);
					relationshipList.add(map);
				} else if ("PATH".equals(value.type().name())) {
					Path path = value.asPath();
					Iterable<Node> nodes = path.nodes();
					for (Node node : nodes) {
						if (!nodeIds.containsKey(node.id())) {
							Map<String, Object> map = setMap(node);
							nodesList.add(map);
							nodeIds.put(node.id(), "");
						}
					}
					Iterable<Relationship> relationships = path.relationships();
					for (Relationship relationship : relationships) {
						if (!relationIds.containsKey(relationship.id())) {
							Map<String, Object> map = setMap(relationship);
							relationshipList.add(map);
							relationIds.put(relationship.id(), "");
						}
					}
				}
			}
		}
		resultMap.put("relationship", relationshipList);
		resultMap.put("nodes", nodesList);
		relationIds.clear();
		relationIds = null;
		nodeIds.clear();
		nodeIds = null;
		return resultMap;
	}

	/**
	 * 设置关系map
	 * @param relationship
	 * @return
	 */
	private Map<String, Object> setMap(Relationship relationship) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.putAll(relationship.asMap());
		map.put("type", relationship.type());
		map.put("id", relationship.id());
		map.put("startNodeId", relationship.startNodeId());
		map.put("endNodeId", relationship.endNodeId());
		return map;
	}

	/**
	 * 设置节点map
	 * @param node
	 * @return
	 */
	private Map<String, Object> setMap(Node node) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.putAll(node.asMap());
		map.put("id", node.id());
		return map;
	}

	/**
	 * 删除节点关系
	 * @param id
	 */
	public void delete(Long id) {
		dao.deleteById(id);
//		xiongdiSheepsRelationshipDao.deleteById(id);
//		fuziSheepsRelationshipDao.deleteById(id);
	}

}

  1. 新增控制层
    package com.rzx.neo4j.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.rzx.neo4j.entity.Sheeps;
import com.rzx.neo4j.service.SheepsRelationshipService;
import com.rzx.neo4j.service.SheepsService;

/**

  • sheeps关系数据控制层
  • @author yangwei

*/
@RestController
@RequestMapping("/api/1.0/sheep")
public class SheepsController {
@Autowired
SheepsRelationshipService sheepsRelationshipService;

@GetMapping("/savaRelation")
public void savaRelation(String startName, String endName, String relationship) {
	sheepsRelationshipService.sava(startName, endName, relationship);
}

@GetMapping("/deleteRelation")
public void deleteRelation(Long id) {
	sheepsRelationshipService.delete(id);
}

@GetMapping("/findByNameAndRelation")
public Object findByNameAndRelation(String startNode, String endNode) {
	return sheepsRelationshipService.findByNameAndRelation(startNode, endNode);
}

}

复杂关系查询或复杂命令定制开发

节点间的增删改比较简单,都是一对一操作,而比较复杂的操作,spring-jpa-data不太擅长,则需要补充功能

  1. 新增一个类,创建neo4j session对象
package com.rzx.neo4j.config;

import org.neo4j.driver.AuthTokens;
import org.neo4j.driver.Driver;
import org.neo4j.driver.GraphDatabase;
import org.neo4j.driver.Session;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Neo4jSourceConfig {

	@Value("${spring.data.neo4j.uri}")
	private String url;

	@Value("${spring.data.neo4j.username}")
	private String username;

	@Value("${spring.data.neo4j.password}")
	private String password;

	@Bean(name = "session")
	public Session neo4jSession() {
		Driver driver = GraphDatabase.driver(url, AuthTokens.basic(username, password));
		return driver.session();
	}
}

  1. service层定制开发
	@Autowired
	private Session session;
/**
	 * 根据开始节点查询关联关系节点,可指定结束节点查询
	 * @param startNode 开始节点名称
	 * @param endNode 结束节点名称
	 * @return
	 */
	public Map<String, List<Map<String, Object>>> findByNameAndRelation(String startNode, String endNode) {
		String cypherSql = String.format("match r = ((a:Sheeps{name:\"%s\"}) - [*..5] -> ()) return r", startNode);
		if (StringUtils.isNotEmpty(endNode)) {
			cypherSql = String.format("match (a:Sheeps {name:\"%s\"}) - [b] -> (c:Sheeps {name:\"%s\"}) return a,b,c", startNode, endNode);
		}
		Result result = session.run(cypherSql);
		// 最终汇聚结果集
		Map<String, List<Map<String, Object>>> resultMap = new HashMap<String, List<Map<String, Object>>>();
		// node节点集合
		List<Map<String, Object>> nodesList = new ArrayList<Map<String, Object>>();
		// 关系节点集合
		List<Map<String, Object>> relationshipList = new ArrayList<Map<String, Object>>();
		Map<Long, String> nodeIds = new HashMap<Long, String>(); // 保存id,用于去重
		Map<Long, String> relationIds = new HashMap<Long, String>(); // 保存id,用于去重
		while (result.hasNext()) {
			Record record = result.next();
			List<Value> values = record.values();
			for (Value value : values) {
				// 节点数据解析
				if ("NODE".equals(value.type().name())) {
					Node node = value.asNode();
					if (!nodeIds.containsKey(node.id())) {
						Map<String, Object> map = setMap(node);
						nodesList.add(map);
						nodeIds.put(node.id(), "");
					}
				// 关系数据解析
				} else if ("RELATIONSHIP".equals(value.type().name())) {
					Relationship relationship = value.asRelationship();
					Map<String, Object> map = setMap(relationship);
					relationshipList.add(map);
				// 关联路径数据解析,包含节点数据和节点关系数据
				} else if ("PATH".equals(value.type().name())) {
					Path path = value.asPath();
					// 获取节点数据,并解析
					Iterable<Node> nodes = path.nodes();
					for (Node node : nodes) {
						if (!nodeIds.containsKey(node.id())) {
							Map<String, Object> map = setMap(node);
							nodesList.add(map);
							nodeIds.put(node.id(), "");
						}
					}
					// 获取关系数据并解析
					Iterable<Relationship> relationships = path.relationships();
					for (Relationship relationship : relationships) {
						if (!relationIds.containsKey(relationship.id())) {
							Map<String, Object> map = setMap(relationship);
							relationshipList.add(map);
							relationIds.put(relationship.id(), "");
						}
					}
				}
			}
		}
		resultMap.put("relationship", relationshipList);
		resultMap.put("nodes", nodesList);
		relationIds.clear();
		relationIds = null;
		nodeIds.clear();
		nodeIds = null;
		return resultMap;
	}

数据导入

  1. 节点CSV数据文件准备
    在这里插入图片描述
    建立节点关系的数据,在标题上一定要加上:ID,导入时默认是String类型,非string类型的数据注意在头上标明类型
  2. 关系CSV数据文件准备
    在这里插入图片描述
    关系标题上把要加上的属性标明,不要加:
  3. 最后执行导入命令,默认导入到data/databases/graph.db数据库,导入前一定要删除该目录,或者指定database。导入日志会生成在当前目录的import.report文件中
    ./bin/neo4j-admin import --nodes:BasicNode import/nodes.csv --relationships import/relation.csv --ignore-extra-columns=true --ignore-missing-nodes=true --ignore-duplicate-nodes=true

小结:对于单个节点、关系的创建使用spring-jpa-data还是非常轻松的,对于复杂查询或者操作来说的话它的扩展性确实很麻烦,不清楚有没有组件或者工具能解决这个问题,再一个就算需要研究一下批量导入数据的功能,因为单个插入的性能确实不强,后面有这方面的突破我会持续更新,未完待续。。。
最后,附上安装包及源码:https://download.csdn.net/download/weixin_37727274/12534808

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值