【问题标题】:Hibernate JPA Parent-Child mappingHibernate JPA 父子映射
【发布时间】:2016-11-21 23:50:22
【问题描述】:

我有一个父表,即 audit_log(parent),它包含一个列 id。对于 audit_log 中的给定 ID,我有一个供应商 ID 列表。我将它们存储在单独的表 audit_log_vendorid(子表)中。我希望子表从父表中获取 id 作为列之一(parent_id)。这是表架构。

审计日志

+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| id    | bigint(19) | NO   | PRI | NULL    |       |
+-------+------------+------+-----+---------+-------+

审计日志供应商

+-----------+------------+------+-----+---------+----------------+
| Field     | Type       | Null | Key | Default | Extra          |
+-----------+------------+------+-----+---------+----------------+
| id        | bigint(19) | NO   | PRI | NULL    | auto_increment |
| vendor_id | bigint(19) | NO   |     | NULL    |                |
| parent_id | bigint(19) | NO   |     | NULL    |                |
+-----------+------------+------+-----+---------+----------------+

我已经定义了我的休眠类如下

@Entity
@Table(name="audit_log")
public class AuditLog  {

private List<AuditVendorPair> vendorIDs;


public AuditLog(List<AuditVendorPair> vendorIds) throws Exception {
    this.vendorIDs = vendorIDs;
}

@OneToMany(cascade=CascadeType.ALL)
@JoinTable(name = "audit_log_vendorid",  
   joinColumns = { @JoinColumn(name = "parent_id", referencedColumnName="id") })
public List<AuditVendorPair> getVendors() {
    return vendorIDs;
}

@Id @Column(name="ID")
public Long getId() {
    return super.getId();
}

public void setHostServices(List<AuditVendorPair> vendorIDs){
    this.vendorIDs = vendorIDs;
}

}

我的 audit_log_vendorid 的休眠映射类如下。我传入一个供应商 ID,并期望其他两个字段由休眠填充。我想要的 parent_id 字段来自 audit_log 中的“id”字段。现在初始化为null导致mysql约束异常。

@Entity
@Table(name="audit_log_vendorid")
public class AuditVendorPair {

private Long id;
private Long parent_id;
private Long vendor_id;
public AuditVendorPair(Long vendor_id){
    this.vendor_id = vendor_id;
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
public Long getId(){
    return id;
}

public void setId(Long id){
    this.id = id;
}

@Column(name="vendor_id")
public Long getVendorID() {
    return vendor_id;
}

public void setVendorID(Long vendor_id){
    this.vendor_id = vendor_id;
}


@Column(name="parent_id")
public Long getParentId() {
    return parent_id;
}

public void setParentId(Long parentID){
    this.parent_id = parentID;
}
}

我很想知道我的注释是否正确。我基本上希望通过 hibernate 将 audit_log 表中的 id 填充到 audit_log_vendorid 表的 parent_id 字段中。

【问题讨论】:

    标签: java mysql spring hibernate jpa


    【解决方案1】:

    不,它们不正确。 audit_log_vendorid 不是连接表。连接表是未映射到实体的表,其中包含映射到其他表的两个关联实体的 ID。

    您也不应该在 AuditVendorPair 中有 parent_id 字段。不仅因为这不尊重 Java 命名约定,还因为它应该被替换为对 AuditLog 的引用,映射为 ManyToOne。

    因此,简而言之,您应该有一个双向 OneToMany 关联,映射如 the documentation 中所述。

    【讨论】:

    • 是的 audit_log_vendorid 不是连接表。引用 AuditLog,映射到 ManyToOne 的原因是什么?我认为单向 oneToMany 会起作用。使用外键,我们是否指示休眠它需要从引用的列中获取 parent_id 的值?如果我在 audit_log 表中的主键大于 ID,情况会改变吗?
    • 您可以使用单向 OneToMany,但是 audit_log_vendorid 中的 parent_id 列将由 OneToMany 关联处理(您需要@JoinColumn("parent_id")),并且不应在 AuditVendorPair 实体中再次映射.如果您需要从 AuditVendorPair 实体访问父级,则它应该是双向的。
    【解决方案2】:

    我认为你忽略了 JPA 中的一个关键概念,即实体是对象,所以你永远不会有一个实体直接使用 ID 引用它的父级,你会引用对象(JPA 在查询时将使用 id数据库)

    @Entity
    @Table(name="audit_log")
    public class AuditLog  {
    
        @OneToMany(cascade= CascadeType.ALL, mappedBy = "auditLog")
        private Collection<AuditVendorPair> vendorIDs;
    
        @Id @Column(name="id")
        private Long id;
    
        public AuditLog() {
        }
    
        public Collection<AuditVendorPair> getVendors() {
            if (vendorIDs == null) {
                vendorIDs = new ArrayList<>();
            }
            return vendorIDs;
        }
    
        public long getId() {
            return id;
        }
    }
    

    @Entity
    @Table(name = "audit_log_vendorid")
    public class AuditVendorPair {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id")
        private Long id;
    
        @JoinColumn(nullable = false, name = "parent_id")
        @ManyToOne(optional = false)
        private AuditLog auditLog;
    
        @Column(name = "vendor_id")
        private Long vendorId;
    
        public AuditVendorPair() {
        }
    
        public long getVendorId() {
            return vendorId;
        }
    
        public void setVendorId(long vendorId) {
            this.vendorId = vendorId;
        }
    
        public AuditLog getAuditLog() {
            return auditLog;
        }
    
        public void setAuditLog(AuditLog auditLog) {
            this.auditLog = auditLog;
        }
    }
    

    AuditVendorPair 使用 Entity 引用 AuditLog,由于这是一个实体关系,您必须使用 @JoinColumn 指定名称。

    JPA/Hibernate 的一些最佳实践

    • 规范不需要 args 构造函数。
    • 没有主键的设置器,除非您在代码中生成它。
    • 您不能对字段使用原语。但是,如果数据库强制执行 NOT NULL,您应该在 getter 和 setter 中使用原始 long,以便您的 IDE 可以警告您有关 NPE,而不是等待您的测试失败。
    • 使用集合而不是列表,因为列表意味着顺序。
    • 不要在字段名中使用下划线,但在指定列名时使用下划线。
    • 注意 getVendors() 中的空值检查和集合创建,这是为了避免首次创建对象时出现 NPE。这意味着您可以使用相同的逻辑来添加 AuditVendorPair,无论 AuditLog 是刚刚创建还是从数据库加载。这也意味着我不会创建 setVendors(),除非我需要替换整个列表,这种情况很少发生,如果是,您通常需要显式删除列表中的每个实体。

    【讨论】:

    • 感谢您的信息。当我尝试保留 AuditLog 时,我得到以下信息:com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:列 'audit_id' 不能为空 我是否需要将外键引用从子 parent_id 添加到父 id 列?
    【解决方案3】:

    听起来您应该多阅读一些关于 JPA 背后的原则的内容。 这是代码的“原始”JPA 示例,如果您使用 Spring-data 和 autowire @PersistanceUnit,则无需自己管理 entityManager。

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("PU-Name");
    EntityManager em = emf.createEntityManager();
    
    try {
        long primaryKey = 1L; // comes from somewhere else
        long vendorId = 1L;   // comes from somewhere
        AuditLog log = em.find(AuditLog.class, primaryKey); // loaded from DB
    
        AuditVendorPair pair = new AuditVendorPair();
        pair.setAuditLog(log);
        pair.setVendorId(vendorId);
        em.getTransaction().begin();
        em.persist(pair);
        em.getTransaction().commit();
    } finally {
        em.close();
    }
    

    您必须在代码中连接实体,并且必须从数据库中加载任何现有实体。如果要存储新的 AuditVendorPair,则首先必须加载 AuditLog 对象,然后将其设置在新创建的 AuditVendorPair 上。通常 Vendor 也是一个实体,因此也必须查找它。

    注意:上面的例子没有维护 AuditLog 和 AuditVendorPair 之间的双向关系,因为 AuditVendorPair 没有添加到 AuditLog 上的供应商集合中。由于定义关系的列位于 AuditVendorPair(已存储)上,这不是问题,因为下次您从数据库加载 AuditLog 实例时,AuditVendorPair 将成为供应商集合的一部分。但是,如果您在持久性上下文关闭后使用 AuditLog,您可能希望保持双向关系。

    注意:上面的示例假设您收到带有主键的请求。一般来说,您永远不应该在任何前端公开主键,在我们的系统中,我们会为 UI 中公开的每个实体生成一个唯一字段 (UUID)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-26
      • 2011-08-17
      • 1970-01-01
      • 2013-12-15
      • 2019-02-24
      • 2014-09-18
      • 2014-01-31
      • 2012-05-09
      相关资源
      最近更新 更多