java - 用于公开 Neo4j 节点的 REST 接口(interface)实现

标签 java spring rest spring-mvc neo4j

我将我的节点表示为域实体。我想通过 REST 接口(interface)公开我的节点,使用 GET 按 id 查询,POST 保存节点及其相关节点,PUT 更新节点及其相关节点。 GET 工作正常,但我有一个关于 POST 和 PUT 案例的实现问题。让我来说明这个问题,因为代码往往比文字说明的更多。

在这个例子中,有两个相关的节点类型表示为域实体。一个协作可以有多个标签,一个标签可以属于多个协作。所以我们有一个多对多的关系。它们共享相同的基类 NodeBacked 它基本上充当底层节点的包装器。

节点支持

abstract class NodeBacked {

    private Node node;

    public NodeBacked(final Node node) {
        this.node = node;
    }

    public Long getId() {
        return this.node.getId();
    }

    @Override
    public int hashCode() {
        return this.node.hashCode();
    }   

    @JsonIgnore
    public Node getNode() {
        return this.node;
    }

}

协作

public class Collaboration extends NodeBacked {

    public Collaboration(final Node node) {
        super(node);
    }

    // Leaving out some properties for clearness

    @JsonProperty(NAME_JSON)
    public String getName() {
        return (String) getNode().getProperty(NAME);
    }   

    @JsonProperty(TAGS_JSON)
    public Iterable<Tag> getTags() {
        return new IterableWrapper<Tag, Path>(Traversal.description().breadthFirst()
                .relationships(Relationships.HAS, Direction.OUTGOING).uniqueness(Uniqueness.NODE_GLOBAL)
                .evaluator(Evaluators.atDepth(1)).evaluator(Evaluators.excludeStartPosition()).traverse(getNode())) 

{
            @Override
            protected Tag underlyingObjectToObject(final Path path) {
                return new Tag(path.endNode());
            }
        };
    }

    public void setName(final String name) {
        final Index<Node> index = getNode().getGraphDatabase().index().forNodes(Indexes.NAMES);
        getNode().setProperty(NAME, name);
        if (StringUtils.isNotEmpty(getName())) {
            index.remove(getNode(), NAME, name);
        }
        index.add(getNode(), NAME, name);
    }

    public void addTag(final Tag tag) {
        if (!Traversal.description().breadthFirst().relationships(Relationships.HAS, Direction.OUTGOING)
                .uniqueness(Uniqueness.NODE_GLOBAL).evaluator(Evaluators.atDepth(1))
                .evaluator(Evaluators.excludeStartPosition())
                .evaluator(Evaluators.includeWhereEndNodeIs(tag.getNode())).traverse(getNode()).iterator().hasNext

()) {
            getNode().createRelationshipTo(tag.getNode(), Relationships.HAS);
        }
    }

    @Override
    public boolean equals(final Object o) {
        return o instanceof Collaboration && getNode().equals(((Collaboration) o).getNode());
    }

}

标签

public class Tag extends NodeBacked {

    public Tag(final Node node) {
        super(node);
    }

    @JsonProperty(NAME_JSON)
    public String getName() {
        return (String) getNode().getProperty(NAME);
    }

    public void setName(final String name) {
        final Index<Node> index = getNode().getGraphDatabase().index().forNodes(Indexes.NAMES);
        getNode().setProperty(NAME, name);
        if (StringUtils.isNotEmpty(getName())) {
            index.remove(getNode(), NAME, name);
        }
        index.add(getNode(), NAME, name);
    }

    @JsonProperty(COLLABORATIONS_JSON)
    @JsonSerialize(using = SimpleCollaborationSerializer.class)
    private Iterable<Collaboration> getCollaborations(int depth) {
        return new IterableWrapper<Collaboration, Path>(Traversal.description().breadthFirst()
                .relationships(Relationships.HAS, Direction.INCOMING).uniqueness(Uniqueness.NODE_GLOBAL)
                .evaluator(Evaluators.atDepth(1)).evaluator(Evaluators.excludeStartPosition()).traverse(getNode())) 

{
            @Override
            protected Collaboration underlyingObjectToObject(final Path path) {
                return new Collaboration(path.endNode());
            }
        };
    }

    @Override
    public boolean equals(final Object o) {
        return o instanceof Tag && getNode().equals(((Tag) o).getNode());
    }

}

我将通过 REST (Spring 3.2) 公开协作,如下所示。 MappingJackson2HttpMessageConverter用于将 POJO 转换为 JSON,反之亦然。

@Controller
@RequestMapping(value = CollaborationController.CONTEXT_PATH)
public class CollaborationController {

    public static final String CONTEXT_PATH = "/collaborations";

    @Autowired
    private GraphDatabaseService db;

    @Transactional
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public @ResponseBody Collaboration getCollaboration(final @PathVariable Long id) {
        // should use a service layer but doing this for clearness
        return new Collaboration(db.getNodeById(id));
    }   

}

这很好用。 getter 查询节点的属性并返回合适的 JSON 字符串。 GET/collaborations/1 的示例 JSON 字符串:

{
  "name" : "Dummy Collaboration",
  "tags" : [ {
    "name" : "test",
    "id" : 3
  }, {
    "name" : "dummy",
    "id" : 2
  } ],
  "id" : 1
}

那么,问题是什么?想象一下带有 JSON 正文的 POST 请求,如下所示:

{
  "name" : "Second collaboration",
  "tags" : [ {
    "name" : "tagged"
  } ]
}

CollaborationController 有以下方法来处理 POST 请求:

@Transactional
    @RequestMapping(method = RequestMethod.POST, headers = JSON_CONTENT_TYPE)
    public @ResponseBody ResponseEntity<Collaboration> persist(final @RequestBody Collaboration collaboration, 
            final UriComponentsBuilder builder) {
        final Collaboration collab = new Collaboration(db.createNode(Labels.COLLAB));
        // Problem!!
        collab.setName(collaboration.getName());        

        final HttpHeaders headers = new HttpHeaders();
        headers.setLocation(builder.path(CONTEXT_PATH + "/{id}").buildAndExpand(collab.getId()).toUri());
        return new ResponseEntity<Collaboration>(collab, headers, HttpStatus.CREATED);
    }

collab.setName(collaboration.getName()); 将不起作用,因为协作类不包含其自身的属性并使用直接查询底层节点的 getter。在这种情况下,没有任何节点可用,因为 Jackson2 应使用 Spring 的 MappingJackson2HttpMessageConverter 将协作从 JSON 转换为 POJO。没有任何属性,因此无需设置任何内容...

我正在寻找解决这个问题的最干净的解决方案,但还没有找到。我可以使用 POJO(或 VO 或...)作为持久方法的传入参数,但这并不是真正可维护的。更改属性将需要更新 Collaboration 类以及 CollaborationVO(POJO)类。

非常欢迎您提出建议! Spring Data Neo4j 主要解决了所有这些问题,但我对它的性能并不满意。这就是我尝试另一种方法的确切原因。

我希望这个解释足够清楚。谢谢你的包容!

使用的框架:

  • Spring 3.2.3.RELEASE
  • Neo4j 2.0.0-M3(嵌入式)
  • jackson 2.2.2

最佳答案

你说:

I could use a POJO (or VO or ...) as incoming parameter for the persist method, 
but that's not really maintainable. 

但我看不出解决这个问题的方法——尽管我同意它看起来很丑并且应该有更好的方法;但是,我还没有看到一个。

您的方法没有讨论的一个方面是它向客户端公开底层数据库实现。当然,它被序列化为 JSON,但这是否会阻止客户端将“协作(或标记)”对象放在一起? [您的客户端可能是一个 javascript 客户端,所以这不是问题,但这不会阻止其他一些假设的客户端构建您的对象之一。]如果该客户端调用 getCollaborations()(或 getTags())会发生什么方法?对我来说,这是两害相权取其轻。

我会将您现有的域实体更改为类似于 ECollaboration/ETag,然后为 Collaboration/Tag 创建纯 POJO。 E 类知道 POJO 是可以的,但反之则不行。因此,E 类也可以全部实现一个带有方法的接口(interface),例如:“convert()”和“valueOf()”以在 API 类和域类之间进行转换。实际上,这些方法也对可维护性有一点帮助 - 当某些内容发生变化时,您可以很容易地看到对另一个对象的影响。

关于java - 用于公开 Neo4j 节点的 REST 接口(interface)实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17216729/

相关文章:

java 在纸上打印 - 格式化打印输出

java - LibGDX 按钮在触摸时没有响应

java - 如何使用 Spring Boot Rest Java EE 项目正确管理文件写入?

android - 如何为仅返回 bool 值的 API 编写 API 调用作为 android 中 retrofit 2 的响应?

c# - StreamReader 问题

java - 抽象类中的静态构造函数?

java - Spring Boot 在无效的 If-Modified-Since 值上抛出异常

spring - 如何处理spring中未映射的url

java - 如何使用 Jersey 2 测试框架为此类编写单元测试

java - 条件依赖