Springboot连接Neo4j

本文档介绍利用Springboot连接Neo4j数据库实现节点、关系的增删改查。所有代码均可查看官方指导:https://docs.spring.io/spring-data/neo4j/docs/6.1.2/reference/html/#reference以及官方手册https://docs.spring.io/spring-data/neo4j/docs/6.1.2/api/

0 事前配置

pom.xml中:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>

版本为6.1.2,6+版本应该通用。

application.properties中:

# neo4j配置
spring.neo4j.uri = bolt://localhost:7687
spring.neo4j.authentication.username = usrname
spring.neo4j.authentication.password = pwd

项目结构:

  • controller
  • domain
  • node
  • relationship
  • repository
  • service

1 domain包

domain中为Neo4j数据库中存放的实体类,包括节点类和关系类,分别置于node包和relationship包中

1.1 node包

对应Neo4j数据库中的节点。以Country.java为例:

@Node(primaryLabel = "Country")
@Data
@Builder
public class Country {
    @Id
    @GeneratedValue
    Long id;

    @Property(name = "name")
    private String name;

    @Relationship(type = "buildIn", direction = Relationship.Direction.INCOMING)
    private List<BuildIn> buildIn;
}
  • @Node注解,声明这是一个节点类,primaryLabel参数为该节点的主标签;
  • @Id与@GeneratedValue注解,声明该成员是一个自动生成的id,Neo4j中的id为长整型类Long;
  • @Property注解,声明该成员是本节点的一个属性,name参数为该属性的名称;
  • @Relationship注解,声明该成员是与本节点有关的关系的列表,
  • type参数为该关系的名称;
  • direction参数为该关系的方向,离开本节点(OUTGOING,默认)或进入本节点(INCOMING)。
  • 注意:当关系一端的节点声明了此关系,另一端的节点一定不能声明同一关系,否侧会由于循环嵌套出现stackOverflow错误!

1.2 relationship包

对应Neo4j数据库中的关系。以BuildIn.java为例:

@RelationshipProperties
@Data
@Builder
public class BuildIn {
    @Id
    @GeneratedValue
    Long id;

    @TargetNode
    private Ship ship;

    private int year;
}
  • @RelationshipProperties注解,声明这是一个关系类,其中必须包含一个@Id和@GeneratedValue注解声明的id;
  • @TargetNode注解,声明该关系的另一端节点。

2 repository包

包含各个节点类与数据库的交互,每个接口均继承Neo4jRepository接口。以CountryRepository为例:

@Repository
public interface CountryRepository extends Neo4jRepository<Country, Long> {
    /*
    按照name查询国家
     */
    Country findFirstByName(String name);
}

findAll、save、delete等基础方法已默认包含,无需额外声明。

简单查询可以自动生成,复杂查询等语句需要用@Query注解写查询语句,如:

@Query(value = "match p=(s:Ship)-[]-() where s.name={0} return p")
List<PathValue> getRelationshipByName(String name);

查询某一艘船及其对应的所有关系,得到的结果为org.neo4j.driver.internal.value.PathValue的列表

Spring Data Neo4j 6+版本中不在支持将关系作为查询结果,自动映射的对象必须为有主标签的节点或节点的映射,或Neo4j的原始数据类(如Path或Node)。

3 Service包

service包中为系统提供的服务,涉及部分逻辑与repository接口中方法的封装,以CountryService.java为例:

@Service
public class CountryService {
    @Autowired
    private CountryRepository countryRepository;

    @Autowired
    private ShipRepository shipRepository;

    public List<Country> getAllCountry(){
        return countryRepository.findAll();
    }

    public Country getCountryByName(String name){
        return countryRepository.findFirstByName(name);
    }

    public Country addCountry(String name){
        return countryRepository.save(Country.builder().name(name).build());
    }

    public void deleteCountry(String name){
        countryRepository.delete(getCountryByName(name));
    }

    public Country addBuildInRelationship(String shipName, String countryName, int year){
        Country country = countryRepository.findFirstByName(countryName);
        Ship ship = shipRepository.findFirstByName(shipName);

        for (BuildIn b : country.getBuildIn()){
            if (b.getShip().getName().equals(shipName)){
                return country;
            }
        }
        country.getBuildIn().add(BuildIn.builder().ship(ship).year(year).build());
        return countryRepository.save(country);
    }
}

自动注入repository后,封装数据库的增删改查。

其中addBuildInRelationship为添加关系的方法,此处先判断该关系是否已存在,若不存在则在该类的关系列表中添加该关系,即可实现数据库中的关系添加,删除关系同理,之后覆盖对应节点。

4 controller包

controller包中为springboot的控制器,以CountryController.java为例:

@RestController
@RequestMapping("/country")
public class CountryController {
    @Autowired
    private CountryService countryService;

    @GetMapping("/get_all_country")
    public List<Country> getAllCountry(){
        return countryService.getAllCountry();
    }

    @GetMapping("/get_country")
    public Country getCountry(@RequestParam("name")String name){
        return countryService.getCountryByName(name);
    }

    @PostMapping("/add_country")
    public Country addCountry(@RequestParam("name")String name){
        return countryService.addCountry(name);
    }

    @DeleteMapping("/delete_country")
    public void deleteCountry(@RequestParam("name")String name){
        countryService.deleteCountry(name);
    }

    @PostMapping("/add_build_in_relationship")
    public Country addBuildInRelationship(
            @RequestParam("shipName")String shipName,
            @RequestParam("countryName")String countryName,
            @RequestParam("year")int year){
        return countryService.addBuildInRelationship(shipName, countryName, year);
    }
}

自动注入ControllerService服务类即可调用对应方法。

5 运行结果与高级方法

查询所有country运行结果:

[
	{
        "name": "泛美",
        "buildIn": []
    },{
        "name": "美国",
        "buildIn": [
            {
                "id": 88,
                "ship": {
                    "name": "得梅因",
                    "tier": 10
                },
                "year": 1941
            },
            ...
		]
    },
    ...
]

看到查询输出的是之前设定的Country.java类中的成员,包括所有的关系,此时可以通过projection映射对输出结果进行筛选与重组。

以CountryProjection.java接口为例:

public interface CountryProjection {
    String getName();
}

设置只得到Country类中的name成员;修改CountryRepository.java中的方法,添加:

List<CountryProjection> findAllBy();

得到全部Country的映射,即只得到name,增加对应Service方法与Controller方法,运行结果:

[
    {
        "name": "泛美"
    },
    {
        "name": "美国"
    },
    ...
]

就不再有关系输出了。