Spring-Boot +Neo4j+实现节点的创建和关系的添加【玩转neo4j】

我们先来看下neo4j的三种连接方式




打开neo4j的配置文件




总过有三种连接方式


常用的有两种,一种是http的连接方式【端口:7474】,一种是Bolt的连接方式【端口:7687】


http的连接方式本篇不再讲了,本篇主要讲第二种连接方式,并结合neo4j提供的原生JavaAPI驱动进行节点的创建和关系的添加


最终实现的效果是






一、项目目录结构







二、项目Pom依赖


<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.appleyk</groupId>
	<artifactId>Spring-Boot-Neo4jAPI</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<description>Spring-Boot 集成Neo4j,实现原生JavaAPI的节点、关系操作</description>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.12.RELEASE</version>
	</parent>
	<!-- 使用Java8,嘗試使用新特新【stream和lambda】 -->
	<properties>
		<java.version>1.8</java.version>
		<janino.version>3.0.8</janino.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 添加热部署 devtools:监听文件变动 -->
		<!-- 当Java文件改动时,Spring-boo会快速重新启动 -->
		<!-- 最简单的测试,就是随便找一个文件Ctrl+S一下,就可以看到效果 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<!-- optional=true,依赖不会传递 -->
			<!-- 本项目依赖devtools;若依赖本项目的其他项目想要使用devtools,需要重新引入 -->
			<optional>true</optional>
		</dependency>
		<!-- Spring 单元测试 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- JUnit单元测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.neo4j.driver/neo4j-java-driver -->
		<dependency>
			<groupId>org.neo4j.driver</groupId>
			<artifactId>neo4j-java-driver</artifactId>
			<version>1.6.1</version>
		</dependency>
	</dependencies>
</project>



三、项目属性配置文件properties

application.properties


server.port=8080
server.session.timeout=10
server.tomcat.uri-encoding=utf8

#在application.properties文件中引入日志配置文件
#=====================================  log  =============================
logging.config=classpath:logback-boot.xml

#Neo4j配置
#连接器:dbms.connector.bolt  协议:Bolt二进制协议
spring.neo4j.url=bolt://localhost:7687
#账户名称
spring.neo4j.username=neo4j
#账户密码
spring.neo4j.password=n123




四、neo4j图形数据库数据结构组成说明


(1)基本数据组成:节点 




match(n) return n == 查询所有节点,不管节点的性质【分类】是什么







上图中红色框圈出来的就是节点的标签,也就是节点的性质是什么,再说直白点就是节点的分类,比如,节点是动物啊还是人类啊,人类中再继续往下分...etc



match(n:Cat) return n == 查询节点性质【分类,标签】等于Cat【猫】的所有节点





(2)关系




何为关系, 这里我就不做文章了,最简单的关系,就是两个节点之间的关系,抽象的表达式就是


(n)-[r]-(b)


抽象图就是




其中start node 代表表达式(n)中的n,relationship代表表达式[r]中的关系r,end node代表表达式(b)中的b


翻译一下就是:节点n,关系r,节点b,具体哪种性质的节点n,哪种关系r,哪种性质的节点b,这里我们需要再指定,三者只要确定一个,就能match出n,r,b的结果,并返回n,r,b具体对应的节点和节点n和b之间的关系r



比如,博主和猫之间的关系查询如下


match(n)-[r:Have]-(b) return n,r,b





我们来分析一下这个查询语句




首先,这个语句只确定了关系r是Have性质的,节点n和b我们并没有指定Label,但是我们却可以通过cypher语句查询出来结果,我们看下这个结果对应的table结构是什么样的




总过三列,没毛病吧,很好理解,因为我们return的结果就是n,r,b,这里关系r是没有属性值的




上面说过,n,r , b 三者确定一个就可以匹配结果,不信的话,我们下面再查询一把,这次我们确定节点n


match(n:Coder)-[r]-(b) return n,r,b


翻译成文字就是:查询所有和Coder性质的节点n有关系r的节点b,并返回n,r, b




上述语句的查询效果就是如下




如果我们只想要节点b,不想要节点b和关系r出现在返回的结果里,我们可以只返回b就行了,效果如下





由于节点b包含的有自己的关系,因此,查出来的结果带有关系也就不足为奇了





好了,扯了那么多,我们回归正题,如何实现文章中提到的爸爸节点和儿子节点二者之间的关系呢?



五、注入neo4jsession


(1)





package com.appleyk.config;
import org.neo4j.driver.v1.AuthTokens;
import org.neo4j.driver.v1.Driver;
import org.neo4j.driver.v1.GraphDatabase;
import org.neo4j.driver.v1.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.neo4j.url}")
    private String url;

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

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

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


六、节点+关系实体构建


(1)基类RObject



package com.appleyk.node.base;

import java.util.HashMap;
import java.util.Map;

/**
 * 节点和关系对象的基类,含公共部分的id、标签label以及属性properties
 * @author yukun24@126.com
 * @blob   http://blog.csdn.net/appleyk
 * @date   2018年5月12日-上午9:55:19
 */
public class RObject {


	/**
	 * 节点标签名称 == Node Labels
	 */
	private String label;
	
	/**
	 * 节点属性键值对 == Property Keys
	 */
    private Map<String, Object> properties;
    
    
    public RObject(){
    	properties = new HashMap<>();
    }
    

	public String getLabel() {
		return label;
	}
	public void setLabel(String label) {
		this.label = label;
	}
	public Map<String, Object> getProperties() {
		return properties;
	}
	
	/**
	 * 添加属性
	 * @param key
	 * @param value
	 */
	public void addProperty(String key,Object value){
		properties.put(key, value);
	}
	
	/**
	 * 拿到属性
	 * @param key
	 * @return
	 */
	public Object getProperty(String key){
		return properties.get(key);
	}
	/**
	 * 移除属性
	 * @param key
	 */
	public void removeProperty(String key){
		properties.remove(key);
	}
	
	public void setProperties(Map<String, Object> properties) {
		this.properties = properties;
	}
}

(2)节点子类RNode





package com.appleyk.node;

import com.appleyk.node.base.RObject;
import com.appleyk.node.relation.REdge;

public class RNode extends RObject{
    
	/**
	 * 节点的uuid == 对应其他数据库中的主键
	 */
	private Long uuid;
	
	/**
	 * 节点里面是否包含有边 == 关系
	 */
	private REdge edge;

	public Long getUuid() {
		return uuid;
	}

	public void setUuid(Long uuid) {
		this.uuid = uuid;
	}

	public REdge getEdge() {
		return edge;
	}

	public void setEdge(REdge edge) {
		this.edge = edge;
	}
}



(3)节点关系REdge





package com.appleyk.node.relation;

import com.appleyk.node.RNode;
import com.appleyk.node.base.RObject;

/**
 * 边 == 关系
 * @author yukun24@126.com
 * @blob   http://blog.csdn.net/appleyk
 * @date   2018年5月12日-上午9:54:55
 */
public class REdge extends RObject{

	/**
	 * 关系的ID  ==  聚合、连接、属于、包括等,这些关系可能是枚举字典,因此记录关系ID是有必要的
	 */
	private Long relationID;
	
	/**
	 * 关系名称
	 */
	private String name;
	
	/**
	 * 关系指向哪一个节点 == 可能这个节点还有关系【节点关系递增下去】
	 */
	private RNode  rNode;
	
	public Long getRelationID() {
		return relationID;
	}

	public void setRelationID(Long relationID) {
		this.relationID = relationID;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public RNode getrNode() {
		return rNode;
	}

	public void setrNode(RNode rNode) {
		this.rNode = rNode;
	}
	
}


七、删除所有关系等于父亲的节点【先清空数据】

match(n)-[r:父亲]-(b) delete n,r,b





八、单元测试

(1)




import org.junit.Test;
import org.junit.runner.RunWith;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.Session;
import org.neo4j.driver.v1.StatementResult;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.types.Node;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.appleyk.Application;
import com.appleyk.node.RNode;
import com.appleyk.node.relation.REdge;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class Neo4jSessionTest {

	final static ObjectMapper mapper = new ObjectMapper();

	 static {
		 /**
		  * 使用neo4j的session执行条件语句statement,一定要使用这个反序列化对象为json字符串
		  * 下面的设置的作用是,比如对象属性字段name="李二明",正常反序列化json为 == "name":"李二明"
		  * 如果使用下面的设置后,反序列name就是 == name:"appleyk"
		  * 而session执行语句create (:儿子{"name":"李二明","uuid":3330,"age":12,"height":"165cm"})会报错
		  * 因此,......etc
		  */		 
		 mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
	 }

	@Autowired
	private Session session;

	@Test
	public void connect() {
		System.out.println("连接是否成功:" + session.isOpen());
	}

	@Test
	public void createNodeAndRelation() throws Exception {

		/**
		 * 建立一个爸爸与儿子的关系
		 */

		// 1.首先创建爸爸节点
		RNode rDad = new RNode();
		rDad.setUuid(1110L);
		rDad.setLabel("爸爸");
		// 2.其次为爸爸节点添加属性 == Property Keys
		rDad.addProperty("uuid", 1110L);
		rDad.addProperty("name", "李大明");
		rDad.addProperty("age", 45);
		rDad.addProperty("height", "182.5cm");
		// 3.添加关系
		REdge edge = new REdge();
		edge.addProperty("relationID", 521L);
		edge.addProperty("time", "1997-05-12");
		edge.setRelationID(521L);
		edge.setName("父亲");
		// 4.为关系节点添加指向节点 == 创建儿子节点
		RNode rSon = new RNode();
		rSon.setUuid(2220L);
		rSon.setLabel("儿子");
		// 5.为儿子节点添加属性 == Property Keys
		rSon.addProperty("uuid", 2220L);
		rSon.addProperty("name", "李小明");
		rSon.addProperty("age", 20);
		rSon.addProperty("height", "185cm");
		// 6.给爸爸节点添加关系
		rDad.setEdge(edge);

	    createNode(rDad);
	    createNode(rSon);
	    createRelation(rDad,rSon);
	    System.err.println("创建成功");

	}

	/**
	 * 创建节点
	 * @param rNode
	 * @throws Exception
	 */
	public void createNode(RNode rNode) throws Exception{
		RNode srcNode = queryNode(rNode);
		//查node是否已經存在了,不存在則創建
		if(srcNode == null){
			String propertiesString = mapper.writeValueAsString(rNode.getProperties());
			String cypherSql = String.format("create (:%s%s)", rNode.getLabel(), propertiesString);
			System.out.println(cypherSql);
			session.run(cypherSql);
			System.err.println("创建节点:"+rNode.getLabel()+"成功!");
		}else{
			System.err.println("节点已存在,跳过创建");
		}
	}
	
	/**
	 * 创建关系
	 * @param srcNode
	 * @param tarNode
	 * @throws Exception
	 */
	public void createRelation(RNode srcNode,RNode tarNode) throws Exception{
		REdge edge = queryRelation(srcNode,tarNode);
		if(edge == null){
			edge = srcNode.getEdge();
			String propertiesString = mapper.writeValueAsString(edge.getProperties());
			String cypherSql = String.format("match(a),(b) where a.uuid=%d and b.uuid=%d create (a)-[r:%s %s]->(b)", 
					srcNode.getUuid(),tarNode.getUuid(),
					edge.getName(), propertiesString);
			System.out.println(cypherSql);
			session.run(cypherSql);
			System.err.println("创建关系:"+edge.getName()+"成功!");
		}else{
			System.err.println("关系已存在,跳过创建");
		}
	}

	
		
	/**
	 * 查询节点
	 * 
	 * @param rNode
	 * @return
	 */
	public RNode queryNode(RNode rNode) {

		RNode node = null;
		String cypherSql = String.format("match(n:%s) where n.uuid = %d return n", rNode.getLabel(), rNode.getUuid());
		StatementResult result = session.run(cypherSql);
		if (result.hasNext()) {
			Record record = result.next();
			for (Value value : record.values()) {
				/**
				 * 结果里面只要类型为节点的值
				 */
				if (value.type().name().equals("NODE")) {
					 Node noe4jNode = value.asNode();
					 node = new RNode();
					 node.setLabel(rNode.getLabel());
					 node.setProperties(noe4jNode.asMap());
					
				}
			}
		}
		return node;
	}

	/**
	 * 查询关系
	 * @param rNode
	 * @return
	 */
	public REdge queryRelation(RNode srcNode,RNode tarNode){
		REdge edge = srcNode.getEdge();
		String cypherSql =String.format("match(n)-[r:%s]-(b) where n.uuid = %d and b.uuid = %d return r", 
				edge.getName(),srcNode.getUuid(),tarNode.getUuid());
		StatementResult result = session.run(cypherSql);
		if(result.hasNext()){
			return edge;
		}
		return null;
	}
}



(2)执行单元测试方法createNodeAndRelation,查看效果如下




查看图库如下





为了防止节点和关系的重复创建,测试demo中加了条件判断,创建节点的时候先查询节点是否存在,如果不存在才新建节点




创建关系的时候也一样,如果两个节点之间已经存在关系了,则不再创建






为了验证查询语句是否起作用,我们让上面的demo再执行一遍,查看下控制台输出如下




因为爸爸节点和儿子节点已经存在neo4j图库中了,且二者之间已经有了关系等于父亲的边,因此,这里我们不会再neo4j里重复再创建数据了


为了再次验证条件语句的作用,我决定给爸爸节点【李大明】再加一个关系节点,假设李大明还有个17岁的儿子李二明,于是,我们稍作修改如下




理想状态下,是节点爸爸李大明不会创建了,因为已经存在了,而节点李二明属于新增节点,因为其uuid我们重新指定了,而李大明和李二明之间的关系是不存在的,因此,再次执行方法,会看到如下的效果




无误后,我们去图库中查询已下验证一把





清除语句很简单,牢记n,r,b表达式就行了,执行效果如下






关于关系的探究,上述只是一个简单的案列,当然利用上述的实体类,完全可以构造出复杂关系网,比如家庭关系等,有兴趣的可以下载下面提供的项目github地址:


https://github.com/kobeyk/Spring-Boot-Neo4jAPI.git






  • 18
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
首先,需要创建一个Spring Boot项目,并添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-neo4j</artifactId> </dependency> ``` 接下来,需要定义实体类和Repository接口。例如,我们要创建一个搜索系统,需要定义一个节点实体类`SearchNode`和一个关系实体类`SearchRelation`,并分别创建对应的Repository接口`SearchNodeRepository`和`SearchRelationRepository`。 ```java @NodeEntity public class SearchNode { @Id @GeneratedValue private Long id; @Indexed private String title; @Indexed private String content; @Relationship(type = "RELATED_TO", direction = Relationship.OUTGOING) private Set<SearchRelation> relations = new HashSet<>(); // getters and setters } @RelationshipEntity(type = "RELATED_TO") public class SearchRelation { @Id @GeneratedValue private Long id; @StartNode private SearchNode fromNode; @EndNode private SearchNode toNode; // getters and setters } public interface SearchNodeRepository extends Neo4jRepository<SearchNode, Long> { List<SearchNode> findByTitleContainingIgnoreCase(String title); } public interface SearchRelationRepository extends Neo4jRepository<SearchRelation, Long> { List<SearchRelation> findByFromNode(SearchNode fromNode); } ``` 在上述代码中,`SearchNode`和`SearchRelation`分别用`@NodeEntity`和`@RelationshipEntity`注解标记为节点关系实体类。`SearchNode`中包含了`@Relationship`注解,用于描述和`SearchRelation`的关系,其中`type`关系类型,`direction`关系方向。`SearchNodeRepository`和`SearchRelationRepository`继承自`Neo4jRepository`,可以使用Spring Data Neo4j提供的各种查询方法。 接下来,在`application.properties`文件中配置Neo4j数据库连接信息: ```properties spring.data.neo4j.uri=bolt://localhost:7687 spring.data.neo4j.username=neo4j spring.data.neo4j.password=123456 ``` 最后,可以在Controller中使用上述Repository接口来实现搜索功能,例如: ```java @RestController public class SearchController { @Autowired private SearchNodeRepository searchNodeRepository; @GetMapping("/search") public List<SearchNode> search(@RequestParam String keyword) { return searchNodeRepository.findByTitleContainingIgnoreCase(keyword); } } ``` 在上述代码中,通过`searchNodeRepository.findByTitleContainingIgnoreCase(keyword)`方法来查询包含关键词的节点信息。可以根据具体需求,使用更复杂的查询方法来实现更丰富的搜索功能。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值