【问题标题】:Loading child nodes within the same transaction在同一事务中加载子节点
【发布时间】:2018-01-09 01:44:12
【问题描述】:

我有一个 TreeNode 实体,它通过OneToMany 关系指向自身。我们还需要兄弟姐妹之间的上一个和下一个关系来导航。

@Entity
@Table(name = "node")
@SecondaryTables({
    @SecondaryTable(name = "hierarchy", 
        pkJoinColumns = @PrimaryKeyJoinColumn(name = "node",
        referencedColumnName = "id")) })
public class TreeNode {

  @Id
  @Column(unique = true)
  private String id;

  @Column
  private String label;

  @ManyToOne
  @JoinColumn(name = "parent", table = "hierarchy")
  private TreeNode parent;

  @Column(table = "hierarchy", name = "previous")
  private String previous;

  @Column(name = "next", table = "hierarchy")
  private String next;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  @JoinTable(name = "hierarchy", joinColumns = {@JoinColumn(name = "parent",
      referencedColumnName = "id")}, inverseJoinColumns = {@JoinColumn(name = "node",
      referencedColumnName = "id")})
  private Set<TreeNode> nodes = new HashSet<>();

  @Column(name = "updated_dt")
  private Timestamp updatedDate;

  public TreeNode getLastChild() {
    for (TreeNode node : nodes) {
      if (node.getNext() == null) {
        return node;
      }
    }
    return null;
  }
   // getter, setters, equals, hashcode
}

我正在尝试从一个制表符分隔的文件中加载完整的树结构,其中每一行代表一个新节点。 例如,

T1
T1   T2
T1   T2   T3
T1   T2   T4

以下是加载记录的代码。

@Transactional
public void loadTree(Scanner lines) {

while (lines.hasNextLine()) {
  final String line = lines.nextLine();
  try (Scanner scanner = new Scanner(line)) {
    Iterator<String> nodes = scanner.useDelimiter("\t");
    addChildNodes(nodes);
  }
}
}

private TreeNode addChildNodes(final Iterator<String> nodes) {
TreeNode parentNode = null;
while (nodes.hasNext()) {
  String nodeTerm = nodes.next();

  if (!StringUtils.hasText(nodeTerm)) {
    continue;
  }

  String id = generateId(parentNode, nodeTerm);
  TreeNode node = repo.findOne(id);
  if (node == null) {
    node = new TreeNode(id, nodeTerm);
    node.setParent(parentNode);
    TreeNode previous = null;
    if (parentNode != null)
      previous = parentNode.getLastChild();
    if (previous != null) {
      node.setPrevious(previous.getId());
      previous.setNext(node.getId());
    }
    node = repo.saveAndFlush(node);
    if (previous != null) {
      previous = repo.save(previous);
    }
  }
  parentNode = node;
}
return parentNode;

}

但问题是nodes 列表永远不会加载,因此getLastChild 总是返回空列表,而previousnext 值永远不会设置。为什么nodes 的延迟加载不起作用?我试过它急切的加载,但没有工作。或者,我正在为每个查找和保存创建新事务,但它非常慢,并且选择查询随着树大小的增加而不断增加。

【问题讨论】:

    标签: java spring hibernate jpa spring-data-jpa


    【解决方案1】:

    TreeNode.nodes 中的实体未加载,因为没有可加载的内容。您还没有将关联的任何一方声明为反方,因此 JPA 将其视为两个独立的关联(并且您从未设置 TreeNode.nodes)。如果这是您的意图,那么您需要明确设置TreeNode.nodes。否则,您需要mappedBy@OneToMany,并且您不能同时使用@JoinColumn@JoinTable

    您似乎在反对(而不是与)JPA 合作。我不确定TreeNode.previousTreeNode.next 应该如何工作,但是将表用于辅助表和连接表是一个不错的主意。我什至不认为 JPA 的行为对于这种情况是明确定义的。无论如何,为什么不让它们成为两个一对一的、懒惰的关联呢?

    【讨论】:

      【解决方案2】:

      您只在一个地方致电setParent

      node.setParent(parentNode);

      parentNode 又只设置在一个地方:

      TreeNode parentNode = null;

      因此,节点的父节点永远不会设置为 null 之外的任何内容。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-06
        相关资源
        最近更新 更多