简体   繁体   English

多级投影上的 Spring Data Neo4J 层次映射

[英]Spring Data Neo4J hierarchy mapping on multi-level projection

I have a simple hierarchy in neo4j directly derived from the business model.我在 neo4j 中有一个简单的层次结构,直接源自业务模型。

@Node
public class Team {
    @Id private String teamId;
    private String name;
}
@Node
public class Driver {
    @Id private String driverId;
    private String name;
    @Relationship(direction = Relationship.Direction.OUTGOING)
    private Team team;
}
@Node
public class Car {
    @Id private String carId;
    private String name;
    @Relationship(direction = Relationship.Direction.OUTGOING)
    private Driver driver;
}

which results in the corresponding graph (Team)<--(Driver)<--(Car) usually all requests start at Car .这导致相应的图表(Team)<--(Driver)<--(Car)通常所有请求都从Car开始。
A new use case needs to create a tree structure starting at Team nodes.一个新的用例需要从Team节点开始创建一个树结构。 The Cypher query aggregates the data on neo and returns it to SDN. Cypher 查询聚合 neo 上的数据并将其返回给 SDN。

public List<Projection> loadHierarchy() {
    return neo4jClient.query("""
                MATCH(t:Team)<--(d:Driver)<--(c:Car)
                WITH t, d, collect(distinct c{.carId, .name}) AS carsEachDriver
                WITH t, collect({driver: d{.driverId, .name}, cars: carsEachDriver }) AS driverEachTeam
                WITH collect({team: t{.teamId, .name}, drivers: driverEachTeam }) as teams
                RETURN teams
             """)
            .fetchAs(Projection.class)
            .mappedBy((typeSystem, record) -> new Projection() {
              @Override
              public Team getTeam() {
                  return record.get... // how to access single object?
              }

              @Override
              public List<Retailers> getRetailers() {
                  return record.get... // how to access nested list objects?
              }
            })
            .all();
}

The result is a list of following objects:结果是以下对象的列表:

{
  "drivers": [
    {
      "driver": {
        "name": "Mike",
        "driverId": "15273c10"
      },
      "cars": [
        {
          "carId": "f4ca4581",
          "name": "green car"
        },
        {
          "carId": "11f3bcae",
          "name": "red car"
        }
      ]
    }
  ],
  "team": {
    "teamId": "4586b33f",
    "name": "Blue Racing Team"
  }
}

The problem is now, how to map the response into an according Java model.现在的问题是,如何将响应映射到相应的 Java 模型中。 I don't use the entity classes.我不使用实体类。
I tried multi-level projection with nested interfaces.我尝试使用嵌套接口进行多级投影。

public interface Projection {

    Team getTeam();
    List<Drivers> getDrivers();

    interface Drivers {
        Driver getDriver();
        List<Cars> getCars();
    }

    interface Driver {
        String getDriverId();
        String getName();
    }

    interface Car {
        String getCarId();
        String getName();
    }

    interface Team {
        String getTeamId();
        String getName();
    }
}

I struggle to access the nested lists and objects, to put them into the model.我很难访问嵌套列表和对象,将它们放入模型中。
SDN is the Spring Boot Starter in version 2.6.3. SDN 是 2.6.3 版本的 Spring Boot Starter。
An example how to map a nested object in a list would be a good starting point.如何在列表中映射嵌套对象的示例将是一个很好的起点。
Or may be my approach is totally wrong?或者可能是我的方法完全错误? Any help is appreciated.任何帮助表示赞赏。

Projection are not meant to be something like a view or wrapper of arbitrary data.投影并不意味着类似于任意数据的视图或包装器。

Within the context you can get a Neo4jMappingContext instance.在上下文中,您可以获得Neo4jMappingContext实例。 You can use this to obtain the mapping function for an already existing entity.您可以使用它来获取现有实体的映射函数。 With this, you do not have to take care about mapping the Car and (partially because of the team relationship) the Drivers .有了这个,您不必关心映射Car和(部分由于团队关系) Drivers

BiFunction<TypeSystem, MapAccessor, Car> mappingFunction = neo4jMappingContext.getRequiredMappingFunctionFor(Car.class);

The mapping function accepts an object of type MapAccessor .映射函数接受MapAccessor类型的对象。 This is a Neo4j Java driver type that is implemented besides others also by Node and MapValue .这是一种 Neo4j Java 驱动程序类型,除其他外还由NodeMapValue

You can use those values from your result eg drivers in a loop (should be possible to call asList on the record) and within this loop you would also assign the cars .您可以在循环中使用结果中的这些值,例如drivers (应该可以在记录上调用asList ),并且在此循环中您还可以分配cars

Of course using the mapping function would only make sense if you have a lot more properties to map because nothing in the return structure (as you already said between the lines) applies to the entity structure regarding the relationships.当然,只有当您有更多要映射的属性时,使用映射函数才有意义,因为返回结构中的任何内容(正如您在两行之间已经说过的那样)适用于有关关系的实体结构。

Here is an example of using the mapping function and direct mapping.这是使用映射功能和直接映射的示例。 You have to decide what matches best for your use case.您必须决定最适合您的用例的内容。

public Collection<Projection> loadHierarchy() {
    var teamMappingFunction = mappingContext.getRequiredMappingFunctionFor(Team.class);
    var driverMappingFunction = mappingContext.getRequiredMappingFunctionFor(Driver.class);
    return neo4jClient.query("""
            MATCH(t:Team)<--(d:Driver)<--(c:Car)
            WITH t, d, collect(distinct c{.carId, .name}) AS carsEachDriver
            WITH t, collect({driver: d{.driverId, .name}, cars: carsEachDriver }) AS driverEachTeam
            WITH {team: t{.teamId, .name}, drivers: driverEachTeam } as team
            RETURN team
         """)
            .fetchAs(Projection.class)
            .mappedBy((typeSystem, record) -> {
                Team team = teamMappingFunction.apply(typeSystem, record.get("team"));
                List<DriverWithCars> drivers = record.get("team").get("drivers").asList(value -> {
                    var driver = driverMappingFunction.apply(typeSystem, value);
                    var cars = value.get("carsEachDriver").asList(carValue -> {
                        return new Car(value.get("name").asString());
                    });
                    
                    return new DriverWithCars(driver, cars); // create wrapper object incl. cars
                });
                return new Projection(team, drivers);
            })
            .all();
}

(Disclaimer: I did not execute this on a data set, so there might be typos or wrong access to the record) Please note that I changed the cypher statement a little bit to have get one Team per record instead of the whole list. (免责声明:我没有在数据集上执行此操作,因此可能存在拼写错误或对记录的错误访问)请注意,我稍微更改了密码语句,以使每条记录获得一个Team ,而不是整个列表。 Maybe this is already what you have asked for.也许这已经是你所要求的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM