初学Neo4j
初学Neo4j
基础
下载
桌面工具介绍
Browser

概念
- 节点:实体,可以是人、公司、组织、地点、事物等。可以理解为sql中的表
- 边:关系,可以是朋友、投资、投资组合、投资组合成员、投资组合成员投资等。
- 属性:节点和边的属性,可以是节点的姓名、年龄、性别、地址、电话等,也可以是边的类型、时间等。
- 标签:标签是对节点的分类,这样使得构建Neo4j数据模型更加简单
- 图:由节点和边组成的数据结构,可以表示复杂的关系和结构。
- 图数据库:一种存储结构为图数据的数据库,可以存储大量复杂的关系和结构,并支持复杂的查询和操作。
- Cypher :图数据库的查询语言,可以查询和操作图数据。
- Gremlin :图数据库的查询语言,可以查询和操作图数据。
- Neo4j:一个开源的图数据库,支持Cypher和Gremlin两种查询语言。
安装
- 下载Neo4j安装包,并解压到任意目录。
- 启动Neo4j服务,在命令行窗口中执行以下命令:
neo4j console
- 访问Neo4j管理界面,默认地址为http://localhost:7474,用户名密码为neo4j/neo4j。
Cypher 语法
Cypher是一种用于查询和操作图数据的查询语言,它支持多种数据类型和操作符,可以满足各种复杂的查询需求。
创建节点:使用CREATE命令创建节点,节点可以包含属性。
CREATE (a:Person {name:"Alice",age:30,isAlive:true})- Persion 是节点的类型(类似sql的表),name、age、isAlive 是节点的属性。
- a 是节点的标识符(类似别名)。
创建边:使用CREATE命令创建边,边可以包含属性。
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"}) CREATE (a)-[:KNOWS]->(b)创建节点:在Cypher查询窗口中输入以下命令,创建一个名为"Alice"的节点:
CREATE (a:Person {name:"Alice"})查询节点:在Cypher查询窗口中输入以下命令,查询所有节点:
MATCH (n) RETURN n查询边:在Cypher查询窗口中输入以下命令,查询所有边:
MATCH ()-[r]-() RETURN r查询关系:在Cypher查询窗口中输入以下命令,查询"Alice"节点和"Bob"节点之间的关系:
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"}) MATCH (a)-[r]->(b) RETURN r删除节点:在Cypher查询窗口中输入以下命令,删除"Alice"节点:
MATCH (a:Person {name:"Alice"}) DELETE a删除边:在Cypher查询窗口中输入以下命令,删除"Alice"节点和"Bob"节点之间的"KNOWS"类型的边:
MATCH (a:Person {name:"Alice"}), (b:Person {name:"Bob"}) MATCH (a)-[r:KNOWS]->(b) DELETE rMATCH (a)-[r]->(b) WHERE id(a)=1 AND id(b)=2 DELETE r;这些操作将删除图中的节点和边,并返回删除的记录数。 注意:删除操作可能会影响其他节点和边的关系,因此需要谨慎操作。
创建索引:在Cypher查询窗口中输入以下命令,为"name"属性创建索引:
//创建索引语法: //OPTIONS子句指定索引提供程序和配置。 CREATE [TEXT] INDEX [index_name] [IF NOT EXISTS] FOR (n:LabelName) ON (n.propertyName) [OPTIONS "{" option: value[, ...] "}"] //示例: CREATE TEXT INDEX agency_index_bid IF NOT EXISTS FOR (n:AGENCY) ON (n.bid) //删除索引语法: DROP INDEX index_name //示例: DROP INDEX agency_index_bid
SDN
Spring Data Neo4j简称SDN
依赖
<!--SDN依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
配置文件
server:
port: 9902
logging:
level:
org.springframework.data.neo4j: debug
spring:
application:
name: sl-express-sdn
mvc:
pathmatch:
#解决异常:swagger Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
#因为Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher
matching-strategy: ant_path_matcher
data:
neo4j:
database: neo4j
neo4j:
authentication:
username: neo4j
password: neo4j123
uri: neo4j://192.168.150.101:7687
实体
以物流转运为例
@Data
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseEntity {
@Id
@GeneratedValue
@ApiModelProperty(value = "Neo4j ID", hidden = true)
private Long id;
@ApiModelProperty(value = "业务id", required = true)
private Long bid;
@ApiModelProperty(value = "名称", required = true)
private String name;
@ApiModelProperty(value = "电话", required = true)
private String phone;
@ApiModelProperty(value = "地址", required = true)
private String address;
// 在 org.springframework.data.geo.Point 包中
@ApiModelProperty(value = "位置坐标, x: 纬度,y: 经度", required = true)
private Point location;
//机构类型
public abstract OrganTypeEnum getAgencyType();
}
机构枚举
public enum OrganTypeEnum implements BaseEnum {
OLT(1, "一级转运中心"),
TLT(2, "二级转运中心"),
AGENCY(3, "网点");
/**
* 类型编码
*/
private final Integer code;
/**
* 类型值
*/
private final String value;
OrganTypeEnum(Integer code, String value) {
this.code = code;
this.value = value;
}
public Integer getCode() {
return code;
}
public String getValue() {
return value;
}
public static OrganTypeEnum codeOf(Integer code) {
return EnumUtil.getBy(OrganTypeEnum::getCode, code);
}
}
网点实体
@Node("AGENCY") // sdn中的注解,对于节点名称
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class AgencyEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.AGENCY;
}
}
一级转运实体
@Node("OLT")
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class OLTEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.OLT;
}
}
二级转运实体
@Node("TLT")
@Data
@ToString(callSuper = true)
@SuperBuilder(toBuilder = true)
@NoArgsConstructor
public class TLTEntity extends BaseEntity {
@Override
public OrganTypeEnum getAgencyType() {
return OrganTypeEnum.TLT;
}
}
转运路线实体
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TransportLine {
private Long id;
private Double cost; //成本
}
DTO
/**
* 机构数据对象,网点、一级转运、二级转运都是看作是机构
* BaseEntity中的location无法序列化,需要将经纬度拆开封装对象
*/
@Data
public class OrganDTO {
@Alias("bid") //业务id作为id进行封装 该注解是hutool包中的
@ApiModelProperty(value = "机构id", required = true)
private Long id;
@ApiModelProperty(value = "名称", required = true)
private String name;
@ApiModelProperty(value = "类型,1:一级转运,2:二级转运,3:网点", required = true)
private Integer type;
@ApiModelProperty(value = "电话", required = true)
private String phone;
@ApiModelProperty(value = "地址", required = true)
private String address;
@ApiModelProperty(value = "纬度", required = true)
private Double latitude;
@ApiModelProperty(value = "经度", required = true)
private Double longitude;
}
/**
* 运输路线对象
*/
@Data
public class TransportLineNodeDTO {
@ApiModelProperty(value = "节点列表", required = true)
private List<OrganDTO> nodeList = new ArrayList<>();
@ApiModelProperty(value = "路线成本", required = true)
private Double cost = 0d;
}
Repository
SDN也是遵循了Spring Data JPA规范,同时也提供了Neo4jRepository,该接口中提供了基本的CRUD操作,我们定义Repository需要继承该接口
package com.sl.sdn.repository;
import com.sl.sdn.entity.node.AgencyEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
/**
* 网点操作
*/
public interface AgencyRepository extends Neo4jRepository<AgencyEntity, Long> {
/**
* 根据bid查询
*
* @param bid 业务id
* @return 网点数据
*/
AgencyEntity findByBid(Long bid);
/**
* 根据bid删除
*
* @param bid 业务id
* @return 删除的数据条数
*/
Long deleteByBid(Long bid);
}
package com.sl.sdn.repository;
import com.sl.sdn.entity.node.OLTEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
/**
* 一级转运中心数据操作
*/
public interface OLTRepository extends Neo4jRepository<OLTEntity, Long> {
/**
* 根据bid查询
*
* @param bid 业务id
* @return 一级转运中心数据
*/
OLTEntity findByBid(Long bid);
/**
* 根据bid删除
*
* @param bid 业务id
* @return 删除的数据条数
*/
Long deleteByBid(Long bid);
}
package com.sl.sdn.repository;
import com.sl.sdn.dto.OrganDTO;
import java.util.List;
/**
* 通用机构查询
*/
public interface OrganRepository {
/**
* 无需指定type,根据id查询
*
* @param bid 业务id
* @return 机构数据
*/
OrganDTO findByBid(Long bid);
/**
* 查询所有的机构,如果name不为空的按照name模糊查询
*
* @param name 机构名称
* @return 机构列表
*/
List<OrganDTO> findAll(String name);
}
package com.sl.sdn.repository;
import com.sl.sdn.entity.node.TLTEntity;
import org.springframework.data.neo4j.repository.Neo4jRepository;
/**
* 二级转运中心数据操作
*/
public interface TLTRepository extends Neo4jRepository<TLTEntity, Long> {
/**
* 根据bid查询
*
* @param bid 业务id
* @return 二级转运中心数据
*/
TLTEntity findByBid(Long bid);
/**
* 根据bid删除
*
* @param bid 业务id
* @return 删除的数据条数
*/
Long deleteByBid(Long bid);
}
测试
@SpringBootTest
class AgencyRepositoryTest {
@Resource
private AgencyRepository agencyRepository;
@Test
public void testFindByBid() {
AgencyEntity agencyEntity = this.agencyRepository.findByBid(9001L);
System.out.println(agencyEntity);
}
@Test
public void testSave() {
AgencyEntity agencyEntity = new AgencyEntity();
agencyEntity.setAddress("测试数据地址");
agencyEntity.setBid(9001L);
agencyEntity.setName("测试节点");
agencyEntity.setPhone("1388888888888");
this.agencyRepository.save(agencyEntity);
System.out.println(agencyEntity);
}
@Test
public void testUpdate() {
AgencyEntity agencyEntity = this.agencyRepository.findByBid(9001L);
agencyEntity.setName("测试节点1");
this.agencyRepository.save(agencyEntity);
System.out.println(agencyEntity);
}
@Test
public void testDeleteByBid() {
Long count = this.agencyRepository.deleteByBid(9001L);
System.out.println(count);
}
/**
* 查询全部
*/
@Test
public void testFindAll() {
List<AgencyEntity> list = this.agencyRepository.findAll();
for (AgencyEntity agencyEntity : list) {
System.out.println(agencyEntity);
}
}
/**
* 分页查询
*/
@Test
public void testPage() {
//设置分页、排序条件,page从0开始
PageRequest pageRequest = PageRequest.of(1, 2, Sort.by(Sort.Order.desc("bid")));
Page<AgencyEntity> page = this.agencyRepository.findAll(pageRequest);
page.getContent().forEach(agencyEntity -> {
System.out.println(agencyEntity);
});
}
}
常见问题
物流项目中,运输路线错综复杂,你们如何进行计算路线的?
- 距离优先:有些物流运输场景,如快递、紧急送货等,可能更关注路程的短暂,因此距离优先可以减少整体的运输时间和成本。
- 成本优先:其他场景可能更关注成本效益,因此会优先选择经济效益最高的路线,即使路程稍长也可以接受,以降低整体的运输成本。
选择距离优先还是成本优先,需要根据具体的运输需求、客户要求、市场竞争等因素综合考虑,并可能根据不同的情况进行调整。
是距离优先还是成本优先?
为什么选择使用Neo4j图数据库存储路线?
路线运输模型在Neo4j中是如何设计的?
在Neo4j中如何设置关系的查询深度?
在SDN中如何自定义Cypher查询?
可以直接定义JPA方法,为什么还要自定义查询?
