JPA 2.0 (Hibernate) 使用 @JoinTable 为 @OneToMany 生成不正确的连接表 PK

Posted

技术标签:

【中文标题】JPA 2.0 (Hibernate) 使用 @JoinTable 为 @OneToMany 生成不正确的连接表 PK【英文标题】:JPA 2.0 (Hibernate) generates incorrect join table PK for @OneToMany with @JoinTable 【发布时间】:2014-06-13 01:04:57 【问题描述】:

我使用 JPA 2.0 和 Hibernate 4.3.5 并尝试使用 Hibernate 自动生成我的表。

我在我的联系人实体中创建了以下条目:

/**
 * Address
 */
@Valid
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "contact_address", joinColumns = @JoinColumn(name = "contact_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "id"))
@OrderColumn
private List<Address> addresses;

Hibernate 正在正确创建联系人表,但创建的连接表没有正确的 PK:

CREATE TABLE `contact_address` (
  `contact_id` bigint(20) NOT NULL,
  `address_id` bigint(20) NOT NULL,
  `addresses_order` int(11) NOT NULL,
  PRIMARY KEY (`contact_id`),
  UNIQUE KEY `UK_mvvtppjfu6d0lcjm83u5youn8` (`address_id`),
  CONSTRAINT `FK_4fntyt0q2l6vkfg7t38pg4i94` FOREIGN KEY (`contact_id`) REFERENCES `contact` (`id`),
  CONSTRAINT `FK_mvvtppjfu6d0lcjm83u5youn8` FOREIGN KEY (`address_id`) REFERENCES `address` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

这里列出的PK只有contact_id,不正确。那将不允许我分配多个地址。相反,PK 应该是 contact_idaddress_id 的复合 PK。

是 Hibernate 出了问题,还是我的 JPA 注释有问题?这是一种单向关联。

正如一些人所说,从技术上讲,我不需要 @OneToMany 的连接表,但考虑到我需要在其他实体对象中使用 Address 实体,使用连接对我来说更简洁所有关联的表格。

我已经设法使用 @ManyToMany 关联并在连接列上指定唯一约束来破解解决方案,但我试图了解我的 JPA 是否有问题或者它是否是 Hibernate 错误。

【问题讨论】:

为什么需要 JoinTable 来实现 OneToMany 关系?据我所知,ManyToMany 需要一个 JoinTable。 我没有在我的问题中指定它,但我需要在几个不同的实体中使用 Address 对象。因此,我在这里使用连接表更简洁,即使它只是一个 OneToMany。 我认为这个问题在这里得到了很好的评论***.com/questions/3113885/… \@OneToMany 的意思是,您可以为您的联系人分配多个地址,但不能将同一个地址分配给更多联系人。因此,address_id 而非 address_id + contact_id 的唯一约束。这就是 \@ManyToMany 起作用的原因。 【参考方案1】:

如果您需要联系人和地址之间的一对多关系,其中一个联系人有多个地址。如果没有关联表,以下就足够了。

@Valid
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="contact_id", referencedColumnName="id")
private List<Address> addresses;

【讨论】:

虽然这在技术上确实有效,但它并不能修复我的连接表。 :) 我需要/想要使用 JoinTable,即使它是 OneToMany,因为我需要在其他实体中使用 Address 实体,并且在这种情况下使用连接表在架构上是一种更简洁的解决方案。【参考方案2】:

您需要为您的问题添加更多详细信息。例如,您如何注释 Address 实体?

无论如何,如果您正确注释和定义实体,Hibernate 会为您正确生成表格:

@Entity
public class Contact 

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(
            name = "UUID",
            strategy = "org.hibernate.id.UUIDGenerator"
    )
    private String id;

    private String fullName;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(
            name = "contact_addresses",
            joinColumns = @JoinColumn(
                    name = "contact_id",
                    referencedColumnName = "id"
            ),
            inverseJoinColumns = @JoinColumn(
                    name = "address_id",
                    referencedColumnName = "id"
            )
    )
    private Set<Address> addresses = new HashSet<>();

    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    

    public String getFullName() 
        return fullName;
    

    public void setFullName(String brand) 
        this.fullName = brand;
    

    public Set<Address> getAddresses() 
        return addresses;
    

    public void setAddresses(Set<Address> contacts) 
        this.addresses = addresses;
    

    public void addAddress(Address address) 
        getAddresses().add(address);
        address.setContact(this);
    


@Entity
public class Address 

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(
            name = "UUID",
            strategy = "org.hibernate.id.UUIDGenerator"
    )
    private String id;

    private String streetAddress;

    @ManyToOne(cascade = CascadeType.ALL)
    private Contact contact;

    public String getId() 
        return id;
    

    public void setId(String id) 
        this.id = id;
    

    public String getStreetAddress() 
        return streetAddress;
    

    public void setStreetAddress(String name) 
        this.streetAddress = name;
    

    public Contact getContact() 
        return contact;
    

    public void setContact(Contact contact) 
        this.contact = contact;
    

这是一个示例测试用例:

@Test
public void testContactAddresses() 
    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("ContactAddresses");
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();

    Contact contact = new Contact();
    contact.setFullName("Steve Ballmer");

    Address address1 = new Address();
    address1.setStreetAddress("Developers Street");

    Address address2 = new Address();
    address2.setStreetAddress("Developers Developers Street");

    Address address3 = new Address();
    address3.setStreetAddress("Developers Developers Developers Street");

    contact.addAddress(address1);
    contact.addAddress(address2);
    contact.addAddress(address3);

    entityManager.persist(contact);

    transaction.commit();

下面是 Hibernate 执行的 DDL 语句:

Hibernate: drop table Address if exists
Hibernate: drop table Contact if exists
Hibernate: drop table contact_addresses if exists
Hibernate: create table Address (id varchar(255) not null, streetAddress varchar(255), contact_id varchar(255), primary key (id))
Hibernate: create table Contact (id varchar(255) not null, fullName varchar(255), primary key (id))

Hibernate: create table contact_addresses (
    contact_id varchar(255) not null, 
    address_id varchar(255) not null, 
    primary key (contact_id, address_id)
)

Hibernate: alter table contact_addresses add constraint UK_mxmb2y0iu8624h4rrdamayobp unique (address_id)
Hibernate: alter table Address add constraint FK98iji1i9ycae5a36rman0vd17 foreign key (contact_id) references Contact
Hibernate: alter table contact_addresses add constraint FK6n27pwv86i3cx03jv3i9taidw foreign key (address_id) references Address
Hibernate: alter table contact_addresses add constraint FKmw2sdpyxrxj3obg1x1ltdlwbo foreign key (contact_id) references Contact

【讨论】:

以上是关于JPA 2.0 (Hibernate) 使用 @JoinTable 为 @OneToMany 生成不正确的连接表 PK的主要内容,如果未能解决你的问题,请参考以下文章

JPA 2.0 使用 Hibernate 作为提供者 - 例外:EntityManager 没有持久性提供者

JPA 2.0:在JPA 2.0中使用javax.validation。*包的例外

无法使用 JPA 2.0、Spring 2.5.6、Hibernate 3.6.1 和 Maven 在 HSQLDB 中持久化

spring.jpa.hibernate.ddl-auto=create 在带有 SpringBoot 2.0 的 Hibernate 5 中不起作用

使用 JPA 2.0 以编程方式加载实体类?

Hibernate入门------HelloWord