一、项目目录结构图
二、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-Neo4j</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Spring-Boot 集成 Neo4j图形数据库实现关系的构建与查询</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.12.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<!-- Starter POMs是可以包含到应用中的一个方便的依赖关系描述符集合 -->
<!-- 该Starters包含很多你搭建项目, 快速运行所需的依赖, 并提供一致的, 管理的传递依赖集。 -->
<!-- 大多数的web应用都使用spring-boot-starter-web模块进行快速搭建和运行。 -->
<!-- spring-boot-starter-web -->
<!-- 对全栈web开发的支持, 包括Tomcat和 spring-webmvc -->
<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>
<!-- 图形数据库Neo4j 官方支持的neo4j依赖包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
</dependencies>
</project>
三、配置文件
server.port=8080
server.session.timeout=10
server.tomcat.uri-encoding=utf8
#在application.properties文件中引入日志配置文件
#===================================== log =============================
logging.config=classpath:logback-boot.xml
#Neo4j配置
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=n123
#数据库uri地址
spring.data.neo4j.uri=http://localhost:7474
Application.java
package com.appleyk;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;
@SpringBootApplication// same as @Configuration @EnableAutoConfiguration @ComponentScan
public class Application extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
四、节点实体类
package com.appleyk.node;
import org.neo4j.ogm.annotation.GraphId;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public abstract class BaseNode {
@GraphId
private Long id;
protected String name;
public BaseNode() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.appleyk.node;
import java.util.ArrayList;
import java.util.List;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;
import com.appleyk.relation.LikeRelation;
@NodeEntity
public class User extends BaseNode{
/**
* 用户ID
*/
private Long uid;
/**
* 用户性别
*/
private String sex;
/**
* 用户年龄
*/
private Integer age;
/**
* 兴趣爱好
*/
private List<String> hobbies;
/**
* 添加关系喜欢,方向为 ->,表明当前节点是startNode
*/
@Relationship(type="Like",direction = Relationship.OUTGOING)
private List<LikeRelation> likes;
public User(){
hobbies = new ArrayList<>();
likes = new ArrayList<>();
}
public Long getUid() {
return uid;
}
public void setUid(Long uid) {
this.uid = uid;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public void addHobby(String hobby){
hobbies.add(hobby);
}
public void addLikes(User user,String reason,Integer since,Integer relationID){
LikeRelation like = new LikeRelation(this, user, reason, since, relationID);
this.likes.add(like);
}
}
五、关系实体类
LikeRelation.java
package com.appleyk.relation;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.Property;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;
import com.appleyk.node.BaseNode;
@RelationshipEntity(type = "Like")
public class LikeRelation {
@GraphId
private Long id;
/**
* 定义关系的起始节点 == StartNode
*/
@StartNode
private BaseNode startNode;
/**
* 定义关系的终止节点 == EndNode
*/
@EndNode
private BaseNode endNode;
/**
* 定义关系的属性
*/
@Property(name = "reason")
private String reason;
@Property(name = "since")
private Integer since;
@Property(name = "relationID")
private Integer relationID;
public LikeRelation() {
}
public LikeRelation(BaseNode startNode,BaseNode endNode,String reason,
Integer since,Integer relationID){
this.startNode = startNode;
this.endNode = endNode;
this.reason = reason;
this.since = since;
this.relationID = relationID;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BaseNode getStartNode() {
return startNode;
}
public void setStartNode(BaseNode startNode) {
this.startNode = startNode;
}
public BaseNode getEndNode() {
return endNode;
}
public void setEndNode(BaseNode endNode) {
this.endNode = endNode;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public Integer getSince() {
return since;
}
public void setSince(Integer since) {
this.since = since;
}
public Integer getRelationID() {
return relationID;
}
public void setRelationID(Integer relationID) {
this.relationID = relationID;
}
}
六、节点User接口
UserRepository.java
package com.appleyk.repository;
import java.util.List;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.appleyk.node.User;
@Repository
public interface UserRepository extends GraphRepository<User>{
List<User> getUsersByName(@Param("name") String name);
/**
* 根据年龄查询用户节点
* @param age
* @return
*/
@Query("match(n:User) where n.age >{age} return n")
List<User> getUsers(@Param("age") Integer age);
}
七、关系Like接口
LikeRelationRepository.java
package com.appleyk.repository;
import java.util.List;
import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.GraphRepository;
import com.appleyk.relation.LikeRelation;
public interface LikeRelationRepository extends GraphRepository<LikeRelation>{
/**
* 查询关系
* @param relationName
* @return
*/
@Query("match p = (n)-[r:Like]->(b) return p")
List<LikeRelation> getLikes();
/**
* 为两个已经存在的节点添加关系
* @param startNodeID -- 起始节点
* @param endNodeID -- 终止节点
* @param rID -- 关系的ID
* @param year -- 关系的开始年限
* @param reason -- 关系产生的原因
* @return
*/
@Query("match(a),(b) where a.uid={0} and b.uid = {1}"
+ " create p = (a)-[r:Like{relationID:{2},since:{3},reason:{4}}]->(b) return p ")
List<LikeRelation> createLikes(Long startNodeID,Long endNodeID,
Integer rID,Integer year,String reason);
}
八、neo4j查询关系语句
例如:match p =(n)-[r:Like]-(b) return p
九、单元测试User节点的创建和查询
UserNodeTest.java
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
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.User;
import com.appleyk.relation.LikeRelation;
import com.appleyk.repository.UserRepository;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes=Application.class)
public class UserNodeTest {
@Autowired
UserRepository userRepository;
/**
* 创建用户节点 【批量创建】
* @throws Exception
*/
@Test
public void createUser() throws Exception{
List<User> userNodes = new ArrayList<>();
User userNode1 = new User();
userNode1.setUid(1001L);
userNode1.setName("刘大壮");
userNode1.setAge(22);
userNode1.setSex("男");
userNode1.addHobby("游戏");
userNode1.addHobby("睡觉");
userNode1.addHobby("撸串");
User userNode2 = new User();
userNode2.setUid(1002L);
userNode2.setName("马晓丽");
userNode2.setAge(17);
userNode2.setSex("女");
userNode2.addHobby("逛街");
userNode2.addHobby("美食");
userNode2.addHobby("化妆");
userNodes.add(userNode1);
userNodes.add(userNode2);
Iterable<User> iterable = userRepository.save(userNodes);
for (User user : iterable) {
System.out.println("创建节点:【"+user.getName()+"】成功!");
}
}
/**
* 创建节点 == 内置关系
*/
@Test
public void createNodeandRelation(){
User startNode = new User();
startNode.setUid(1011L);
startNode.setName("刘泽");
startNode.setAge(22);
startNode.setSex("男");
startNode.addHobby("游戏");
startNode.addHobby("睡觉");
User endNode1 = new User();
endNode1.setUid(1012L);
endNode1.setName("张婷");
endNode1.setAge(17);
endNode1.setSex("女");
endNode1.addHobby("逛街");
endNode1.addHobby("美食");
endNode1.addHobby("化妆");
User endNode2 = new User();
endNode2.setUid(1013L);
endNode2.setName("林志玲");
endNode2.setAge(45);
endNode2.setSex("女");
endNode2.addHobby("逛街");
endNode2.addHobby("美食");
endNode2.addHobby("化妆");
startNode.addLikes(endNode1, "心地善良,人美", 2015,521 );
startNode.addLikes(endNode2, "女神姐姐", 2011, 520);
User userNode = userRepository.save(startNode);
System.out.println("节点"+userNode.getName()+"创建成功!");
}
/**
* 根据用户的name查询user节点列表
*/
@Test
public void findUserByName(){
List<User> users = userRepository.getUsersByName("刘大壮");
System.out.println("共查出来节点有:"+users.size()+"个");
}
/**
* 根据用户的年龄查询user节点【年龄大于18】
*/
@Test
public void findUserByAge(){
List<User> users = userRepository.getUsers(18);
for (User user : users) {
System.out.println("共查出来节点有:"+users.size()+"个, == "+user.getName());
}
}
}
十、单元测试Like关系的创建与查询
关系的添加语句:
match(a),(b) where a.uid = 1001 and b.uid = 1002
create p=(a)-[r:Like{relationID:520,since:2018,reason:'晓丽是女神'}]->(b) return p
LikeRelationTest.java
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
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.User;
import com.appleyk.relation.LikeRelation;
import com.appleyk.repository.LikeRelationRepository;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class LikeRelationTest {
@Autowired
private LikeRelationRepository likeRepository;
/**
* 在已有的两个节点的基础上创建关系
*/
@Test
public void createLikeRelation() throws Exception {
LikeRelation like = new LikeRelation();
/**
* 节点 == 刘大壮
*/
User startNode = new User();
startNode.setUid(1001L);
/**
* 节点 == 马晓丽
*/
User endNode = new User();
endNode.setUid(1002L);
like.setStartNode(startNode);
like.setEndNode(endNode);
like.setRelationID(520);
like.setSince(2018);
like.setReason("晓丽是女神");
List<LikeRelation> likes = likeRepository.createLikes(startNode.getUid(),
endNode.getUid(), like.getRelationID(),like.getSince(),like.getReason());
/**
* 遍历创建的关系
*/
for (LikeRelation likeRelation : likes) {
User sNode = (User) likeRelation.getStartNode();
User eNode = (User) likeRelation.getEndNode();
System.out.println(sNode.getName() + "--喜欢-->" + eNode.getName());
}
}
/**
* 创建节点并创建关系
*/
@Test
public void createLikes(){
LikeRelation like = new LikeRelation();
/**
* 节点 == 韩梅梅
*/
User startNode = new User();
startNode.setUid(1003L);
startNode.setName("韩梅梅");
startNode.setAge(18);
startNode.setSex("女");
startNode.addHobby("看书");
startNode.addHobby("逛街");
/**
* 节点 == 李晓明
*/
User endNode = new User();
endNode.setUid(1004L);
endNode.setName("李晓明");
endNode.setAge(19);
endNode.setSex("男");
endNode.addHobby("游戏");
endNode.addHobby("音乐");
endNode.addHobby("篮球");
like.setStartNode(startNode);
like.setEndNode(endNode);
like.setRelationID(520);
like.setSince(2018);
like.setReason("晓明好帅啊");
LikeRelation relation = likeRepository.save(like);
System.out.println(relation.getStartNode().getName()+"-->喜欢"+
relation.getEndNode().getName()+",理由:"+relation.getReason());
}
/**
* 查询关系
*/
@Test
public void findLikes(){
List<LikeRelation> likes = likeRepository.getLikes();
for (LikeRelation likeRelation : likes) {
User sNode = (User) likeRelation.getStartNode();
User eNode = (User) likeRelation.getEndNode();
System.out.println(sNode.getName() + "--喜欢-->" + eNode.getName()+" == reason: "+likeRelation.getReason());
}
}
}
十一、效果演示
创建前清空测试数据的cypher语句:match(n)-[r:Like]-(b) delete n,r,b
(1)创建节点 == 不内置关系
(2)查询年龄大于18岁的user节点
(3)创建节点 == 内置关系的创建
(4)刘大壮喜欢马晓丽,我们为这两个节点创建Like关系,并连带关系的属性一起创建
(5)再创建一个关系,创建关系的同时,创建关系中的startNode和endNode
(6)查询关系Like
十二、项目地址
博主注:neo4j关系其实就是由两个node加一个边edge组成的,关系是有向的,因此,node分起始startNode和终止endNode,通俗点就是源节点到目标节点之间存在一条边,这个边就是关系relation,其查询cypher语句的模板为:
match path =(a)-[r] - (b) return path
在neo4j的关系查询中,关系默认是不区分方向的,因为a和b存在关系r,反过来b和a之间理论上也是存在关系的,除非你指定关系的方向,否则查询的时候会出现正反两条结果,但是显示关系【可视化】的时候,却是有方向的
比如查询语句:match path =(a)-[:Like] -> (b) return path 的结果为:
如果不指定关系的方向查询的话,其语句:match path =(a)-[:Like] - (b) return path 的结果为:
由于demo中我们在定义关系实体的时候,指定了startNode和endNode,这就表明,即使查询语句不指定关系,但是在结果返回的时候,匹配节点的时候,还是会隐式的暴露关系的方向