> 技术文档 > Neo4j入门第二期(Spring Data Neo4j的使用)

Neo4j入门第二期(Spring Data Neo4j的使用)

强烈建议先看第一期:

Neo4j入门第一期(Cypher入门)-CSDN博客

SDN快速入门

Spring Data Neo4j简称SDN,是Spring对Neo4j数据库操作的封装,其底层基于neo4j-java-driver实现。

创建工程 

 4.0.0  com.sl-express sl-express-parent 1.4  com.sl-express.sdn sl-express-sdn 1.0-SNAPSHOT  11 11 1.2-SNAPSHOT    com.sl-express.common sl-express-common ${sl-express-common.version}    org.springframework.boot spring-boot-starter-data-neo4j  

基础代码

SDNApplication

编写启动类:

package com.sl.sdn;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplicationpublic class SDNApplication { public static void main(String[] args) { SpringApplication.run(SDNApplication.class, args); }}

Entity

编写实体,在物流中,会存在网点、二级转运中心、一级转运中心,我们分别用Agency、TLT、OLT表示。

由于以上三个机构的属性是相同的,但在Neo4j中的标签是不一样的,所以既要保证不同的类,也有相同的属性,这种场景比较适合将属性写到父类中,自己继承父类来实现,这里我们采用抽象类的来实现。

package com.sl.sdn.entity.node;import com.sl.sdn.enums.OrganTypeEnum;import io.swagger.annotations.ApiModelProperty;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import lombok.experimental.SuperBuilder;import org.springframework.data.geo.Point;import org.springframework.data.neo4j.core.schema.GeneratedValue;import org.springframework.data.neo4j.core.schema.Id;@Data@SuperBuilder(toBuilder = true)@NoArgsConstructor@AllArgsConstructorpublic 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; @ApiModelProperty(value = \"位置坐标, x: 纬度,y: 经度\", required = true) private Point location; //机构类型 public abstract OrganTypeEnum getAgencyType();}

 机构枚举:

package com.sl.sdn.enums;import cn.hutool.core.util.EnumUtil;import com.sl.transport.common.enums.BaseEnum;/** * 机构类型枚举 */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); }}
package com.sl.sdn.entity.node;import com.sl.sdn.enums.OrganTypeEnum;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;import lombok.experimental.SuperBuilder;import org.springframework.data.neo4j.core.schema.Node;/** * 网点实体 */@Node(\"AGENCY\")@Data@ToString(callSuper = true)@SuperBuilder(toBuilder = true)@NoArgsConstructorpublic class AgencyEntity extends BaseEntity { @Override public OrganTypeEnum getAgencyType() { return OrganTypeEnum.AGENCY; }}
package com.sl.sdn.entity.node;import com.sl.sdn.enums.OrganTypeEnum;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;import lombok.experimental.SuperBuilder;import org.springframework.data.neo4j.core.schema.Node;/** * 一级转运中心实体 (OneLevelTransportEntity) */@Node(\"OLT\")@Data@ToString(callSuper = true)@SuperBuilder(toBuilder = true)@NoArgsConstructorpublic class OLTEntity extends BaseEntity { @Override public OrganTypeEnum getAgencyType() { return OrganTypeEnum.OLT; }}
package com.sl.sdn.entity.node;import com.sl.sdn.enums.OrganTypeEnum;import lombok.Data;import lombok.NoArgsConstructor;import lombok.ToString;import lombok.experimental.SuperBuilder;import org.springframework.data.neo4j.core.schema.Node;/** * 二级转运中心实体(TwoLevelTransportEntity) */@Node(\"TLT\")@Data@ToString(callSuper = true)@SuperBuilder(toBuilder = true)@NoArgsConstructorpublic class TLTEntity extends BaseEntity { @Override public OrganTypeEnum getAgencyType() { return OrganTypeEnum.TLT; }}
package com.sl.sdn.entity.line;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;/** * 运输路线实体 */@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class TransportLine { private Long id; private Double cost; //成本}

DTO

DTO用于服务间的数据传输,会用到OrganDTOTransportLineNodeDTO

package com.sl.sdn.dto;import cn.hutool.core.annotation.Alias;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import javax.validation.constraints.NotNull;/** * 机构数据对象,网点、一级转运、二级转运都是看作是机构 * BaseEntity中的location无法序列化,需要将经纬度拆开封装对象 */@Datapublic class OrganDTO { @Alias(\"bid\") //业务id作为id进行封装 @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;}
package com.sl.sdn.dto;import io.swagger.annotations.ApiModelProperty;import lombok.Data;import java.util.ArrayList;import java.util.List;/** * 运输路线对象 */@Datapublic class TransportLineNodeDTO { @ApiModelProperty(value = \"节点列表\", required = true) private List nodeList = new ArrayList(); @ApiModelProperty(value = \"路线成本\", required = true) private Double cost = 0d;}

Repository

SDN也是遵循了Spring Data规范,同时也提供了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 { /** * 根据bid查询 * * @param bid 业务id * @return 网点数据 */ AgencyEntity findByBid(Long bid); /** * 根据bid删除 * * @param bid 业务id * @return 删除的数据条数 */ Long deleteByBid(Long bid);}

OLTRepository

package com.sl.sdn.repository;import com.sl.sdn.entity.node.OLTEntity;import org.springframework.data.neo4j.repository.Neo4jRepository;/** * 一级转运中心数据操作 */public interface OLTRepository extends Neo4jRepository { /** * 根据bid查询 * * @param bid 业务id * @return 一级转运中心数据 */ OLTEntity findByBid(Long bid); /** * 根据bid删除 * * @param bid 业务id * @return 删除的数据条数 */ Long deleteByBid(Long bid);}

复杂查询

通过继承Neo4jRepository实现简单的查询是非常方便的,如果要实现复杂的查询就需要定义Cypher查询实现了,需要通过Neo4jClient进行查询操作,下面我们以查询两个网点间最短运输路线为例进行查询。

定义Repository 
package com.sl.sdn.repository;import com.sl.sdn.dto.TransportLineNodeDTO;import com.sl.sdn.entity.node.AgencyEntity;/** * 运输路线相关操作 */public interface TransportLineRepository { /** * 查询两个网点之间最短的路线,查询深度为:10 * * @param start 开始网点 * @param end 结束网点 * @return 路线 */ TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end);}
编写实现
package com.sl.sdn.repository.impl;import cn.hutool.core.bean.BeanUtil;import cn.hutool.core.collection.CollUtil;import cn.hutool.core.convert.Convert;import cn.hutool.core.stream.StreamUtil;import cn.hutool.core.util.NumberUtil;import cn.hutool.core.util.StrUtil;import com.sl.sdn.dto.OrganDTO;import com.sl.sdn.dto.TransportLineNodeDTO;import com.sl.sdn.entity.node.AgencyEntity;import com.sl.sdn.enums.OrganTypeEnum;import com.sl.sdn.repository.TransportLineRepository;import org.neo4j.driver.internal.value.PathValue;import org.neo4j.driver.types.Path;import org.springframework.data.neo4j.core.Neo4jClient;import org.springframework.data.neo4j.core.schema.Node;import org.springframework.stereotype.Component;import javax.annotation.Resource;import java.util.List;import java.util.Map;import java.util.Optional;@Component // 将该类注册为 Spring Beanpublic class TransportLineRepositoryImpl implements TransportLineRepository { @Resource // 注入 Neo4j 客户端用于执行 Cypher 查询 private Neo4jClient neo4jClient; /** * 查找从一个网点到另一个网点的最短路径。 * * @param start 起始网点实体 * @param end 目标网点实体 * @return 包含最短路径信息的 DTO 对象 */ @Override public TransportLineNodeDTO findShortestPath(AgencyEntity start, AgencyEntity end) { // 获取网点数据在 Neo4j 中的标签名称(类型) String type = AgencyEntity.class.getAnnotation(Node.class).value()[0]; // 构造 Cypher 查询语句,寻找从起点到终点的最短路径 String cypherQuery = StrUtil.format(\"MATCH path = shortestPath((start:{}) -[*..10]-> (end:{}))\\n\" + \"WHERE start.bid = $startId AND end.bid = $endId \\n\" + \"RETURN path\", type, type); // 执行查询,并处理结果 Optional optional = this.neo4jClient.query(cypherQuery) .bind(start.getBid()).to(\"startId\") // 绑定起始节点 ID 到查询参数 \'startId\' .bind(end.getBid()).to(\"endId\") // 绑定目标节点 ID 到查询参数 \'endId\' .fetchAs(TransportLineNodeDTO.class) // 设置返回数据类型为 TransportLineNodeDTO .mappedBy((typeSystem, record) -> { // 自定义映射逻辑,将记录转换为 DTO 对象  PathValue pathValue = (PathValue) record.get(0); // 从记录中获取路径对象  Path path = pathValue.asPath(); // 将 PathValue 转换为 Path 对象  // 创建 TransportLineNodeDTO 实例  TransportLineNodeDTO dto = new TransportLineNodeDTO();  // 处理路径中的节点,将其转换为 OrganDTO 列表  List nodeList = StreamUtil.of(path.nodes()) // 遍历路径中的所有节点 .map(node -> { Map map = node.asMap(); // 将节点转换为 Map OrganDTO organDTO = BeanUtil.toBeanIgnoreError(map, OrganDTO.class); // 将 Map 转换为 OrganDTO // 取第一个标签作为类型 OrganTypeEnum organTypeEnum = OrganTypeEnum.valueOf(CollUtil.getFirst(node.labels())); organDTO.setType(organTypeEnum.getCode()); // 设置类型代码 // 查询出来的数据,x:经度,y:纬度 organDTO.setLatitude(BeanUtil.getProperty(map.get(\"location\"), \"y\")); // 设置纬度 organDTO.setLongitude(BeanUtil.getProperty(map.get(\"location\"), \"x\")); // 设置经度 return organDTO; // 返回 OrganDTO 实例 }) .collect(Collectors.toList()); // 收集所有 OrganDTO 实例到列表中  dto.setNodeList(nodeList); // 设置节点列表到 DTO 对象  // 提取关系中的 cost 数据,进行求和计算,算出该路线的总成本  double cost = StreamUtil.of(path.relationships()) // 遍历路径中的所有关系 .mapToDouble(relationship -> { Map objectMap = relationship.asMap(); // 将关系转换为 Map return Convert.toDouble(objectMap.get(\"cost\"), 0d); // 获取 cost 值并转换为 double,如果不存在则默认为 0 }).sum(); // 计算所有关系的 cost 总和  dto.setCost(cost); // 设置总成本到 DTO 对象  // 取2位小数  dto.setCost(NumberUtil.round(dto.getCost(), 2).doubleValue());  return dto; // 返回最终的 DTO 对象 }).one(); // 获取单个结果 return optional.orElse(null); // 如果没有找到结果,则返回 null }}

知识百科全解