Java Hibernate JPA父子映射

Java Hibernate JPA父子映射,java,mysql,spring,hibernate,jpa,Java,Mysql,Spring,Hibernate,Jpa,我有一个父表,即audit_log(parent),其中包含一个列id。对于audit_log中的给定id,我有一个供应商id列表。我将它们存储在单独的表audit\u log\u vendorid(子表)中。我希望子表从父表获取id作为列之一(parent_id)。这是表模式 审计日志 +-------+------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra

我有一个父表,即audit_log(parent),其中包含一个列id。对于audit_log中的给定id,我有一个供应商id列表。我将它们存储在单独的表audit\u log\u vendorid(子表)中。我希望子表从父表获取id作为列之一(parent_id)。这是表模式

审计日志

+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| id    | bigint(19) | NO   | PRI | NULL    |       |
+-------+------------+------+-----+---------+-------+
审核日志供应商ID

+-----------+------------+------+-----+---------+----------------+
| 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    |                |
+-----------+------------+------+-----+---------+----------------+
我已经定义了我的hibernate类如下

@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\u log表中的id由hibernate填充到audit\u log\u vendorid表的parent\u id字段中。

不,它们不正确
audit\u log\u vendorid
不是联接表。联接表是未映射到实体的表,它包含映射到其他表的两个关联实体的ID

在AuditVendorPair中也不应该有parent_id字段。这不仅是因为它不遵守Java命名约定,还因为它应该被一个对AuditLog的引用所取代,并映射为manytone


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

我认为您忽略了JPA中的一个关键概念,即实体是对象,因此您永远不会让实体直接使用ID引用其父对象,您将引用该对象(JPA在查询DB时将使用ID)

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

JPA/Hibernate的一些最佳实践

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

听起来你应该多读一点JPA背后的原则。 下面是一个“原始”JPA示例,说明了代码的外观,如果您使用Spring数据和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上进行设置。通常情况下,供应商也是一个实体,因此也必须对其进行查找

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


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

是的,audit\u log\u vendorid不是联接表。引用带有ManyToOne映射的AuditLog的原因是什么?我以为单向的“一个女人”就行了。使用外键,我们是否指示休眠,它需要从引用的列获取parent_id的值?如果我在audit\u log表中的主键大于ID,情况会发生变化吗?您可以使用单向OneToMany,但audit\u log\u vendorid中的父ID列将由OneToMany关联处理(您需要
@JoinColumn(“父ID”)
),并且不应在AuditVendorPair实体中进行第二次映射。如果您需要从AuditVendorPair实体访问父级,那么它应该是双向的。谢谢您提供的信息。当我尝试持久化AuditLog时,我得到了以下结果:com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:Column'audit\u id'不能为null是否需要将外键引用从子父级\u id添加到父级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;
    }
}
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();
}