You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

双复合主键Tree与Node多对多关联的JPA中间表注解配置

JPA复合主键下Tree与Node的多对多关联实现方案

核心思路

由于Tree和Node均使用复合主键,无法直接使用JPA原生@ManyToMany注解,必须显式创建中间关联实体,通过@OneToMany + @ManyToOne的组合模拟多对多关系,中间表的主键由双方复合主键共同组成。


步骤1:创建中间关联实体及其复合主键

TreeNodeId(中间表复合主键)

该类封装Tree和Node的复合主键作为中间表的联合主键:

@Embeddable
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TreeNodeId implements Serializable {
    private TreeIdentifier treeIdentifier;
    private NodeIdentifier nodeIdentifier;

    @Override
    public int hashCode() {
        return Objects.hash(treeIdentifier, nodeIdentifier);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        TreeNodeId that = (TreeNodeId) obj;
        return Objects.equals(treeIdentifier, that.treeIdentifier) &&
               Objects.equals(nodeIdentifier, that.nodeIdentifier);
    }

    private static final long serialVersionUID = 1L;
}

TreeNode实体(中间表对应实体)

通过@ManyToOne关联Tree和Node,并用@MapsId映射到复合主键的对应部分:

@Entity
@Table(name = "tree_node")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TreeNode {
    @EmbeddedId
    private TreeNodeId id;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("treeIdentifier")
    @JoinColumns({
        @JoinColumn(name = "tree_id", referencedColumnName = "tree_id"),
        @JoinColumn(name = "site_id", referencedColumnName = "site_id"),
        @JoinColumn(name = "prod_version", referencedColumnName = "prod_version")
    })
    private Tree tree;

    @ManyToOne(fetch = FetchType.LAZY)
    @MapsId("nodeIdentifier")
    @JoinColumns({
        @JoinColumn(name = "node_id", referencedColumnName = "node_id"),
        @JoinColumn(name = "node_version_id", referencedColumnName = "node_version_id")
    })
    private Node node;

    // 可添加中间表自定义字段,比如关联时间
    private LocalDateTime associationDate;
}

步骤2:修改Tree实体添加关联集合

在Tree类中添加与TreeNode的一对多关联,并提供便捷操作方法:

@Entity
@Table(name = "tree")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Tree {
    @EmbeddedId
    private TreeIdentifier treeIdentifier;
    private LocalDateTime creationDate;

    @OneToMany(mappedBy = "tree", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<TreeNode> treeNodes = new ArrayList<>();

    // 便捷方法:添加Node关联
    public void addNode(Node node) {
        TreeNode treeNode = new TreeNode();
        TreeNodeId id = new TreeNodeId(this.treeIdentifier, node.getNodeIdentifier());
        treeNode.setId(id);
        treeNode.setTree(this);
        treeNode.setNode(node);
        treeNode.setAssociationDate(LocalDateTime.now());
        this.treeNodes.add(treeNode);
        node.getTreeNodes().add(treeNode);
    }

    // 便捷方法:移除Node关联
    public void removeNode(Node node) {
        this.treeNodes.removeIf(tn -> tn.getNode().getNodeIdentifier().equals(node.getNodeIdentifier()));
        node.getTreeNodes().removeIf(tn -> tn.getTree().getTreeIdentifier().equals(this.treeIdentifier));
    }
}

步骤3:修改Node实体添加关联集合

在Node类中添加与TreeNode的一对多关联:

@Entity
@Table(name = "node")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Node {
    @EmbeddedId
    private NodeIdentifier nodeIdentifier;
    private Long nodePosition;

    @OneToMany(mappedBy = "node", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<TreeNode> treeNodes = new ArrayList<>();
}

关键注解说明

  • @MapsId:将关联实体的主键映射到当前实体嵌入式主键的指定属性,保证中间表主键与关联表主键一致性。
  • @JoinColumns:针对复合主键,需逐个指定外键列与关联表主键字段的对应关系。
  • fetch = FetchType.LAZY:默认懒加载避免性能损耗,可根据业务需求调整为EAGER。
  • cascade = CascadeType.ALL:对TreeNode的操作会级联到关联的Tree/Node,可根据场景调整为PERSIST等细分类型。

内容的提问来源于stack exchange,提问作者BugsOverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 19:50:17