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

如何在Spring JPA中实现一个实体关联多个不同实体?

问题描述

需要实现投诉功能:User可以投诉多种不同实体(Question、Answer、Comment或其他User),数据库schema设计为单表complains,其中user_id关联发起投诉的用户(一对多),entity_id关联被投诉的实体(多对一)。

尝试用泛型类Complain实现,但抛出异常:

org.hibernate.AnnotationException: 
Property com.*.*.Entities.Complain.entity_id has an unbound type and no explicit target entity.
Resolve this Generic usage issue or set an explicit target attribute (eg @OneToMany(target=)
or use an explicit @Type...

原有代码如下:

Complain.java(泛型版本)

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "complains")
public class Complain<T extends BaseComplain> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user_id;

    @ManyToOne
    @JoinColumn(name = "entity_id", nullable = false)
    private T entity_id;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date created_on;

}

User.java

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "users")
public class User extends BaseComplain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @OneToMany(mappedBy = "user_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain<BaseComplain>> author_complains;

    @OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain<User>> complains;

    // 其他内容...
}

Question.java(其他实体实现类似)

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "questions")
public class Question extends BaseComplain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "entity_id", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    @JsonManagedReference
    private Set<Complain<Question>> complains;
    
    // 其他内容...
}

问题:能否仅使用JPA基础特性实现该逻辑?已考虑过@MappedSuperclass,需要具体实现方案。


解决方案

核心问题分析

JPA规范不支持泛型实体类,Hibernate无法在运行时解析未绑定的泛型参数T,因此抛出类型未绑定的异常。需要用多态关联+映射超类的方式替代泛型实现。

步骤1:重构BaseComplain为@MappedSuperclass

将所有可被投诉的实体的公共主键字段抽离到映射超类中:

@MappedSuperclass
public abstract class BaseComplain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 公共getter/setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
}

步骤2:修改可被投诉的实体

让User、Question等实体继承BaseComplain,移除重复的id字段:

User.java

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "users")
public class User extends BaseComplain {
    // 移除原有id字段,继承自BaseComplain

    @OneToMany(mappedBy = "user", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain> authorComplains;

    @OneToMany(mappedBy = "targetEntity", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    private Set<Complain> complains;

    // 其他原有字段...
}

Question.java

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "questions")
public class Question extends BaseComplain {
    // 移除原有id字段,继承自BaseComplain

    @OneToMany(mappedBy = "targetEntity", orphanRemoval = true, fetch = FetchType.LAZY)
    @ToString.Exclude
    @JsonManagedReference
    private Set<Complain> complains;

    // 其他原有字段...
}

步骤3:重构Complain实体(去掉泛型)

添加实体类型字段解决不同实体id冲突问题,直接关联BaseComplain:

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "complains")
public class Complain {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    private User user;

    @ManyToOne
    @JoinColumn(name = "entity_id", nullable = false)
    private BaseComplain targetEntity;

    // 用枚举存储实体类型,比字符串更安全
    @Enumerated(EnumType.STRING)
    @Column(name = "entity_type", nullable = false)
    private EntityType entityType;

    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn;

    // 辅助方法:设置目标实体时自动填充类型
    public void setTargetEntity(BaseComplain targetEntity) {
        this.targetEntity = targetEntity;
        if (targetEntity instanceof User) {
            this.entityType = EntityType.USER;
        } else if (targetEntity instanceof Question) {
            this.entityType = EntityType.QUESTION;
        } else if (targetEntity instanceof Answer) {
            this.entityType = EntityType.ANSWER;
        } else if (targetEntity instanceof Comment) {
            this.entityType = EntityType.COMMENT;
        }
    }
}

步骤4:定义EntityType枚举

public enum EntityType {
    USER, QUESTION, ANSWER, COMMENT
}

查询示例

查询某个用户收到的所有投诉:

List<Complain> userComplains = entityManager.createQuery(
        "SELECT c FROM Complain c WHERE c.targetEntity.id = :userId AND c.entityType = :userType",
        Complain.class
)
.setParameter("userId", userId)
.setParameter("userType", EntityType.USER)
.getResultList();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 01:31:03