1

I've using JPA 2.0 with Hibernate 4.3.5 and trying to use Hibernate to auto generate my tables.

I've created the following entry in my Contact entity:

/**
 * 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 is properly creating the contact table, but the join table is created without the correct 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;

The PK listed here is only the contact_id, which is incorrect. That will not allow me to have multiple addresses assigned. Rather, the PK should be a composite PK of contact_id, address_id.

Is Hibernate at fault, or is there something wrong in my JPA annotation? This is a one-way association.

As noted by a couple of people, technically speaking, I don't need a join table for a @OneToMany, but given that I need to use the Address entity in other entity objects, it is cleaner for me to use join tables for all the associations.

I have managed to hack around a solution using the @ManyToMany association and specifying a unique constraint on the join columns, but I am trying to understand if there is something wrong with my JPA or if it is a Hibernate bug.

Eric B.
  • 23,425
  • 50
  • 169
  • 316
  • Why do you need a JoinTable for a OneToMany relationship? ManyToMany requires a JoinTable as far as I know. – shazin Jun 13 '14 at 02:14
  • I didn't specify it in my question, but I need to use the Address object in several different entities. Consequently, it is cleaner for me to use a join table here, even if it is only a OneToMany. – Eric B. Jun 13 '14 at 13:09
  • I think the problem is nicely commented here https://stackoverflow.com/questions/3113885/difference-between-one-to-many-many-to-one-and-many-to-many \@OneToMany means, that you can assign multiple addresses to your contact, but the same address can not be assigned to more contacts. Therefore the unique constraint across the address_id and not address_id + contact_id. That is why \@ManyToMany works. – David Hladky Apr 24 '19 at 13:50

2 Answers2

0

If you need One To Many relationship between Contact and Address where one contact has many addresses. Following will suffice without an associative table.

@Valid
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="contact_id", referencedColumnName="id")
private List<Address> addresses;
shazin
  • 21,379
  • 3
  • 54
  • 71
  • Although that technically does work, it doesn't fix my join table. :) I need/want to use a JoinTable even though it is a OneToMany as I need to use the Address entity in other entities, and it is a cleaner solution architecturally to use a join table in this case. – Eric B. Jun 13 '14 at 13:11
0

You need to add more details to your question. For example, how have you annotated the Address entity?

Anyway, if you annotate and define your entities correctly, Hibernate will generate the tables correctly for you:

@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;
    }
}

Here's a sample test case:

@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();
}

And here are the DDL statements executed by Hibernate:

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
Behrang
  • 46,888
  • 25
  • 118
  • 160