Saturday, June 27, 2009

Hibernate Bi-Directional Many to Many mapping

There are times we have many to many relation between 2 objects (tables). The many to many relationship between 2 tables is defined by a third table. For example, suppose we 2 tables: “customer” and “service” and we say that a customer can be related to more than one service, we add a third table: “user_customer” that will hold the relations between customers and services.

Let’s summarize the structure of these 3 tables:

  • customer: This table holds customers. The fields in this table are:
    • customer_id (primary key)
    • first_name
    • last_name
  • service: This table holds list of services that a customer can receive. For example, cables TV customer can get the following services: Sports, Drama, News etc’. A customer can get more than one service. The fields in this table are:
    • service_id (primary key)
    • name
  • customer_service: This table defines the relation between customers and services. It connected users to services. It contains only the key from each table:
    • user_id
    • service_id

Note that “customer” and “service” tables used in this example doesn’t contain all the business data usually would have been used in the real world. This is to make things simpler.

Hibernate has a special annotation that helps us to define this relation: @ManyToMany. This is how this annotation is being used:

This is the Customer class representing the “customer” table:

package com.bashan.blog.hibernate;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table(name = "customer")
public class Customer {
  @Id
  @Column(name = "customer_id")
  private Integer customerId;
  @Column(name = "first_name")
  private String firstName;
  @Column(name = "last_name")
  private String lastName;
  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  @JoinTable(name = "customer_service",
      joinColumns = {@JoinColumn(name = "customer_id")},
      inverseJoinColumns = @JoinColumn(name = "service_id"))
  private Set<Service> services;
  public Integer getCustomerId() {
    return customerId;
  }
  public void setCustomerId(Integer customerId) {
    this.customerId = customerId;
  }
  public String getFirstName() {
    return firstName;
  }
  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }
  public String getLastName() {
    return lastName;
  }
  public void setLastName(String lastName) {
    this.lastName = lastName;
  }
}

This is the Service class representing the “service” table:

package com.bashan.blog.hibernate;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table(name = "service")
public class Service {
  @Id
  @Column(name = "service_id")
  private Integer serviceId;
  @Column(name = "name")
  private String name;
  @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
  @JoinTable(name = "customer_service",
      joinColumns = {@JoinColumn(name = "service_id")},
      inverseJoinColumns = @JoinColumn(name = "customer_id"))
  private Set<Customer> customers;
  public Integer getServiceId() {
    return serviceId;
  }
  public void setServiceId(Integer serviceId) {
    this.serviceId = serviceId;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

Not that there is no need to define any CustomerService object since the @ManyToMany annotation already contains this information in the “name” property of the @JoinTable.

The code for these 2 classes can be found in this link.

7 comments:

  1. I know this is an old example but I'm having a problem with this that doesn't seem addressed anywhere. If I have add/remove methods to put object relations into the respective collections i.e. an addService(Service s) on the Customer object and an addCustomer(Customer c) on the Service object you cannot update the collections of both objects because when you do, you dirty both collections and thus Hibernate tries to the relation both ways. If you follow the documentation and have one side "own" the relationship with the @JoinTable and the other use the "mappedBy" notion you run into the problem of if you delete an object on the "non-owning" side the many-to-many" table entries are not deleted. It's kind of a chicken and egg problem - no matter which way you go you run into an issue. I'm almost resigned to the fact that many-to-many should not be used and that everything ought to be a two-sided one-to-many and many-to-one style relation -- I've never had an issue controlling the semantics in that situation. Do you have any thoughts?

    ReplyDelete
  2. I don't have a strong memory on what happens when you delete one side of the relation, but if I remember correct it should delete the row on the connecting table no matter from what side you delete - meaning the row on the many-to-many connecting table should be simply deleted.

    ReplyDelete
  3. Dude... you have no idea of how long I've been struggling with Hibernate to do this. Thanks for share it with us. You deserve all respect from the Java Community.

    Paulo Almeida

    ReplyDelete
  4. Thanks for sharing it with us, you just saved my day ;)

    ReplyDelete
  5. A typical metadata ORM model is create by xml mapping style,alternatively we can use hibernate annotation to design our persistance class.
    See the below to example one with annotaion and one without it

    http://fundapass.blogspot.in/2012/09/hibernate-with-annotation-example.html

    http://fundapass.blogspot.in/2012/09/hibernate-one-to-many-mapping.html

    ReplyDelete
  6. Thanks u for sharing.....

    ReplyDelete