使用Neo4j操作图数据
文档型数据库会将数据存储到粗粒度的文档中,而图数据库会将数据存储到多个细粒度的节点中,这些节点之间通过关系建立关联。图数据库中的一个节点通常会对应数据库中的一个概念(concept),它会具备描述节点状态的属性。连接两个节点的关联关系可能也会带有属性。
Spring Data Neo4j
Spring Data Neo4j提供了很多与Spring Data JPA和Spring Data MongoDB相同的功能,当然所针对的是Neo4j图数据库。它提供了将Java对象映射到节点和关联关系的注解、面向模板的Neo4j访问方式以及Repository实现的自动化生成功能。
添加Neo4j依赖
创建项目,pom文件中引入依赖,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
配置文件
在配置文件中配置Neo4j相关配置,如下:
# neo4j配置
spring.data.neo4j.uri= bolt://localhost:7687
spring.data.neo4j.username=neo4j
spring.data.neo4j.password=neo4j
使用注解标注图实体
Neo4j定义了两种类型的实体:节点(node)和关联关系(relationship)。一般来讲,节点反映了应用中的事物,而关联关系定义了这些事物是如何联系在一起的。
图数据库使用
创建实体类节点
创建entity包,添加实体类:PersonEntity和MovieEntity
import lombok.Data;
import org.springframework.data.neo4j.core.schema.GeneratedValue;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
/**
* 定义演员信息
*/
@Node("Person")
@Data
public class PersonEntity {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer born;
public PersonEntity(Integer born, String name) {
this.name = name;
this.born = born;
}
}
import lombok.Data;
import org.springframework.data.neo4j.core.schema.*;
import java.util.ArrayList;
import java.util.List;
/**
* 定义电影信息,标签名,labels可以缺省
*/
@Node(labels = "Movie")
@Data
public class MovieEntity {
/**
* Id自增
*/
@Id
@GeneratedValue
private Long id;
private final String title;
/**
* 映射到neo4j的属性名
*/
@Property("tagline")
private final String description;
/**
* 生成node时自动生成
*
* @param title
* @param description
*/
public MovieEntity(String title, String description) {
this.id = null;
this.title = title;
this.description = description;
}
/**
* 定义一个关系(参演)[direction]
*/
@Relationship(type = "ACTED_IN", direction = Relationship.Direction.INCOMING)
private List<Roles> actorsAndRoles = new ArrayList<>();
/**
* 定义一个关系(导演)
* 注意这些关系最终的箭头指向是当前实体,即TargetNode(PersonEntity)->当前定义Relationship的实体(MovieEntity)
*/
@Relationship(type = "DIRECTED", direction = Relationship.Direction.INCOMING)
private List<PersonEntity> directors = new ArrayList<>();
/**
* 用户指定特定的Id
*
* @param id
* @return
*/
public MovieEntity withId(Long id) {
if (this.id != null && this.id.equals(id)) {
return this;
} else {
MovieEntity newObject = new MovieEntity(this.title, this.description);
newObject.id = id;
return newObject;
}
}
}
节点间的关系创建Roles
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
import org.springframework.data.neo4j.core.schema.TargetNode;
import java.util.List;
/**
* 定义一个关系属性
*/
@RelationshipProperties
public class Roles {
@Id
private Long id;
private final List<String> roles;
@TargetNode // 相当于@StartNode
private final PersonEntity person;
// 参数1是目标关系实体节点 参数2是关系属性
// Roles 参数1:Person实体,演员的出生年和姓名;参数2:演员名字列表(考虑到一个演员可能参演多个角色)
public Roles(PersonEntity person, List<String> roles) {
this.person = person;
this.roles = roles;
}
public List<String> getRoles() {
return roles;
}
}
使用Neo4jTemplate对图数据进行CRUD
import com.neo4j.entity.MovieEntity;
import com.neo4j.entity.PersonEntity;
import com.neo4j.entity.Roles;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.neo4j.core.Neo4jTemplate;
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
* 使用Neo4jTemplate操作Neo4j
*/
@Component
@Slf4j
public class Neo4jTemplateOp {
@Resource
private Neo4jTemplate neo4jTemplate;
/**
* 删除所有节点和关系(删除节点会响应删除关联关系),避免后续创建节点重复影响
*/
public void deleteAll() {
neo4jTemplate.deleteAll(MovieEntity.class);
neo4jTemplate.deleteAll(PersonEntity.class);
}
/**
* 存储图数据库
*/
public void save() {
MovieEntity movie = new MovieEntity("流浪地球", "一个叫地球的小孩流浪的故事。");
Roles roles1 = new Roles(new PersonEntity(1998, "张三"), Collections.singletonList("三哥"));
Roles roles2 = new Roles(new PersonEntity(1993, "李四"), Collections.singletonList("四哥"));
PersonEntity director = new PersonEntity(1973, "导演1");
// 添加movie的演员实体,加入(参演)关系
movie.getActorsAndRoles().add(roles1);
movie.getActorsAndRoles().add(roles2);
movie.getDirectors().add(director);
// 存入图数据库持久化
neo4jTemplate.save(movie);
}
/**
* 通过ID查询信息
*/
public void findById() {
Optional<PersonEntity> person = neo4jTemplate.findById(5, PersonEntity.class);
log.info("person:{}", person);
}
/**
* 通过属性查询节点,如name 需要手写cypherQuery语句
* 两种写法如下
* MATCH (n:Person {name: $name}) RETURN n
* MATCH (n:Person) WHERE n.name = $name RETURN n
*/
public void findOne() {
Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
Optional<PersonEntity> person1 = neo4jTemplate.findOne("MATCH (n:Person {name: $name}) RETURN n", map, PersonEntity.class);
Optional<PersonEntity> person2 = neo4jTemplate.findOne("MATCH (n:Person) WHERE n.name = $name RETURN n", map, PersonEntity.class);
log.info("查询名字为张三的Person节点:{}", person1);
log.info("查询名字为张三的Person节点:{}", person2);
}
/**
* 通过属性关系查询节点,使用toExecutableQuery查询
*/
public void findExecutableQuery() {
Map<String, Object> map = new HashMap<>(8);
map.put("roles", Collections.singletonList("三哥"));
QueryFragmentsAndParameters parameters = new QueryFragmentsAndParameters(
"MATCH (person:Person) -[ relation:ACTED_IN]-> (movie:Movie) \n" +
"WHERE relation.roles = $roles\n" +
"RETURN person");
parameters.setParameters(map);
List<PersonEntity> roles = neo4jTemplate.toExecutableQuery(PersonEntity.class, parameters).getResults();
log.info("查询角色为“三哥”的演员:{}", roles);
}
/**
* 更新节点信息
*/
public void update() {
Map<String, Object> map = new HashMap<>();
Long userId = 0L;
map.put("name", "张三");
map.put("usedName", "小张三");
QueryFragmentsAndParameters queryFragmentsAndParameters =
new QueryFragmentsAndParameters(
"MATCH (n:Person{name: $name}) SET n.name = $usedName");
queryFragmentsAndParameters.setParameters(map);
neo4jTemplate.toExecutableQuery(
PersonEntity.class,
queryFragmentsAndParameters).getResults();
Optional<PersonEntity> person1 = neo4jTemplate.findById(userId, PersonEntity.class);
log.info("查询张三信息:{}", person1);
person1.get().setName("新海诚");
neo4jTemplate.save(person1.get());
Optional<PersonEntity> person2 = neo4jTemplate.findById(userId, PersonEntity.class);
System.out.println("\n更新“新津诚”的name为“新海诚”:\n" + person2);
}
}
使用repository对图数据进行CRUD
新建repository包,创建PersonRepository和MovieRepository
import com.example.neo4jdemo.entity.PersonEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PersonRepository extends Neo4jRepository<PersonEntity, Long> {
}
import com.example.neo4jdemo.entity.MovieEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MovieRepository extends Neo4jRepository<MovieEntity, Long> {
}
问题
低版本和高版本Neo4j兼容性问题
这两天学习SpringBoot时碰到了很多问题
springboot集合neo4j引用了org.neo4j的包,报错Required identifier property not found for class 用SpringBoot集成neo4j,查询报错Could not find mappable nodes or relationships inside Record org.springframework.data.neo4j.core.schema中没有@NodeEntity,@StartNode,@EndNode RelationShip无法注解在实体关系类中 nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.neo4j.ogm.session.SessionFactory] sessionFactory找不到