Java Hibernate ManyToMany Tutorial – With Add and Delete examples

The many-to-many mappings and usage thereof can be a little bit weird on first glance.

This tutorial demonstrates:

  1. The mappings (Annotations not XML).
  2. Adding an entity to a group.
  3. Removing an entity from a group.
  4. Listing parents with children.
  5. Listing children with parents.

image

(The Hibernate Logic is completely separate from the UI)

The project is available to download at the bottom of this post.

Creating the many-to-many relationship

This is achieved by having an additional link or map table which holds the relationships between the two data tables.

image

The MySQL for this example is:

 

Creating the link table

The MySQL for the link table is:

(included in the project available for download at the bottom of the post)

Creating the entities using Netbeans or Eclipse Hibernate Tools

I’m not going to explain this here, suffice it to say, it’s totally unnecessary to create and annotate all the entities manually. They can be created with Netbeans, Elcipse or the standalone Hibernate-Tools facility (ok, maybe not in their entirety, some modifications may be necessary. In fact the Hibernate Plugin distributed with Netbeans doesn’t provide for link tables (many-to-many annotations) as far as I’m aware. I would welcome correction on this one however).

Note: In order to preserve ‘Order’ I’ve used ‘List’ objects rather than ‘Set’ objects for the member variable collections in the entity classes.

Here’s the end result to be aiming for:

TClass.java

Note the use of ‘joinColumns’ here; the ‘name’ attribute is specified as “class_id”. If the column name in each table were not “class_id” it would be necessary to use the ‘referencedColumnName’ attribute also. This is detailed here: http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html

Similarly, the ‘inverseJoinColumns’ attribute operates the same way.

TStudent.java

 

The many-to-many annotations

This is the bit that’s weird. It’s necessary to consider one side of the relationship to be in control or to be “owning”. The other side of the relationship is submissive in a since. The correct terminology is the ‘owning side’ and the ‘non-owning side’ (also known as the ‘inverse’ side).

I.e. We will be adding students to classes but never the other way round. We will be removing students from classes, we will not consider it to be a case of removing classes from students. This is of course a notional perspective, but keeping this perspective makes it much easier to understand the mappings and the process of adding and removing relations.

The ‘owning side’ of the relationship gets the complex annotation and the ‘non-owning side’ (inverse side) gets the less complex annotation.

 

Processing the entities within the many-to-many relationship.

For all of the examples below, it’s necessary that all entities are from within the same hibernate session.

Fetching the TClass with all it’s TStudents

 

Fetching the TStudent with all it’s TClasses

 

Adding a TStudent to a TClass

 

Removing a TStudent from a TClass

Recall that adding students to classes (or removing) is a notional perspective. The mappings used do not permit ‘thinking’ of adding classes to students, instead, they promote ‘thinking about’ adding students to classes. In any case, all we’re doing is adding or removing entries in the link table.

Note:

  • The use of: c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); This ensures that only distinct instances of the parent are returned.
  • The use of ‘List’ objects rather than ‘Set’ objects in the entity classes. This preserves the ordering specified in the Criteria Query. (‘Set’ objects loose the ordering).

Adding the same student to a class more than once.

The only reason this is given a mention here is because as mentioned above, ‘List’ objects have been used instead of ‘Set’ objects as member variables within the TStudent and TClass entities. The use of a ‘Set’ will not permit duplicate objects in the Collection. A ‘List’ will permit duplicate objects in the Collection. Therefore, using A ‘Set’ will prevent the addition of the same TStudent to a TClass more than once. A ‘List’ will not prevent this.

This does present something to think about; if removing one, which one is removed? Does it matter? No, because they’re both the same. It will only matter if the link table had an additional column such as ‘date attended’ for example. This changes everything. The notion of students ‘belonging’ to classes is lost and the notion of a student attendance list is realised.

Having an additional column in the link table changes everything because it’s necessary to have access to this additional column.The link table must now get it’s own entity mapping.

However, the whole process of @ManyToMany is not lost , Hibernate provides the ability to embed the link entity (with it’s additional columns) within the other two as a member variable ‘List’ or ‘Set’ object.

Download

  • This is a Netbeans (7.1.1) project.
  • It has a Swing UI but don’t be turned off, the UI is completely separate from the Hibernate logic.
  • It uses a MySQL DB. The SQL is provided. In this example the DB name is ‘test’. (Remember to change the ‘catalog’ attributes in the entity classes if using a different DB name)
  • All the jar files and sql is included in the project.

HibernateManyToManyWorkedExample

Conclusion

This tutorial has shown:

  1. Mapping a Hibernate many-to-many relationship with annotations.
  2. Adding a child to a parent (or vice versa). I.e. inserting to the link table.
  3. Removing a child from the parent (or vice versa). I.e. deleting from the link table.
  4. Listing the parents with children.
  5. Listing the children with parents.
  6. The points of note with regard persisting the same relationship more than once. I.e. adding the same  student to the same class more than once.
  7. An introduction to the concept of having additional columns in the link table.

Comments always welcome……

9 Comments

  • If I am not mistaken one rule of many to many relationship is that the combination of “class_id” and “student_id” should produce unique rows.
    But in your example you are using a separate primary key “class_student_map_id” to create unique combinations. I believe this is wrong. You should remove this key and create a primary key by combining “class_id” and “student_id” and then set foreign key constrains on them.

    • Thanks for the comment Jay but, I don’t think it’s necessarily “wrong”. It depends on the application. In this case, what you say makes since because the same student can’t really be in a class more than once without the notion of an additional time/date column. It is a matter of preference and I prefer having the single primary key with a unique constraint on the (class_id, student_id) combination. The reason is, that it’s ok to have table t_class_student_map mapped as an individual entity also. Accessing the entity (TClassStudentMap.class) individually can improve performance in certain situations, it is often more convenient to access it using the single primary key.

      Additionally, for example, if you exchange ‘pocket’ (pool or snooker table pocket (or whatever)) with ‘class’ and exchange ‘student’ with ‘ball’, then it makes since that the same ball will appear in the same pocket multiple times (throughout a match/game or even tournament). This table structure can keep track of scores or statistics (for example).

      BTW, in which case, look at the chapter “Adding the same student to a class more than once” above for info on this – List vs. Set example.

      Thanks for the comment… feel free anytime.

  • I have the same kind of structure on my project with courses and students, It is working fine when I am enrolling a student to a course, much like adding TStudent to TClass in your example. The problem is unenrolling the student is not removing the row in the relation table of the database. can you please take a look at it

    @Entity
    public class Student implements Serializable{
    private static final long serialVersionUID = 1L;

    private long studentId;
    private String userName;
    private String firstName;
    private String lastName;
    private Set courses = new HashSet(0);

    @ManyToMany(fetch= FetchType.LAZY, cascade={CascadeType.PERSIST, CascadeType.MERGE},
    mappedBy=”students”, targetEntity=Course.class)
    public Set getCourses() {
    return courses;
    }

    @Entity
    public class Course implements Serializable {

    /**
    *
    */
    private static final long serialVersionUID = 1L;

    private long courseId;
    private String courseName;
    private int creditHours;
    private String courseLanguage;

    private Teacher teacher;

    private Set students = new HashSet(0);

    @ManyToMany(fetch= FetchType.LAZY, targetEntity=Student.class,
    cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.ALL})
    @JoinTable(name=”course_student”,
    joinColumns={@JoinColumn(nullable=true)},
    inverseJoinColumns=@JoinColumn(nullable=true))
    public Set getStudents() {
    return students;
    }

    I have tried changing the cascade type to type.All also but nothing changed

    • Hi Sime

      Hmmm, the annotation on getStudents() seems a little off.

      @JoinColumn(nullable=true)

      I think you need to specify the name of the columns.

      e.g.
      joinColumns = {@JoinColumn(name = “class_id”)},
      inverseJoinColumns = {@JoinColumn(name = “student_id”)}

      Where the joinColum (class_id) is the name of the “course” column in the link table and the inverseJoinColumn name id the name of the “student” column.

      Let me know if that doesn’t work. Feel free to post more code if you’re still having difficulty.

      John

      • Hi,
        I have removed the joinColumns annotation, this will allow hibernate to use default naming of columns in the link table, I dropped all the tables and recreated them and this part works fine. But still not removing (dropping out) of course. I run some tests and I think it has something to do with the student being passed (s1) not being equal to the student found in the set retrieved from the database (s2), meaning s1.equals(s2) fails, It has something to do with the hashcode of this two objects. But I couldn’t think of a way to resolve that. I have a feeling it has something to do with overriding the hashcode() method.
        any suggestions

        • Yep, that could very well be the problem. Of course, student in the following code needs to be the same instance as the one in the list: course.getStudents().remove(student).

          I’ve emailed you. If you want to email me on a test case that demonstrates the problem I’m happy to take a look.

          Keep in mind however that operating on ManyToMany configurations is possibly not the best approach for the following reasons:
          1. For any add/remove operation Hibernate removes all corresponding items from the link table then re-ads them. This is not good for performance (DB reindexing etc).
          2. It’s difficult to filter and page the students when operating on the course.

          In my opinion, operating directly on the link table entity is far more efficient. It also eliminates allot of complexity, complexity that hibernate is supposed to abstract the developer from, let it help you. Also, resist the urge to define a composite key in the link table, again its much easier if you just have an integer ID on the link table; put a unique constraint on the (course_id, student_id) to keep the pairings unique. Using a Set in the Hibernate Entities rather than a List will enforce the unique constraint at the Java level.

          Anyway, email me back a test case and I’m happy to take a look. The findings can be commented here to help others…..

          John

          • That in fact was the problem. and I solved it by overriding the equals and hashCode methods of the Object class in my student class. like this

            @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST,
            CascadeType.MERGE, CascadeType.ALL }, mappedBy = “students”, targetEntity = Course.class)
            public Set getCourses() {
            return courses;
            }

            public void setCourses(Set courses) {
            this.courses = courses;
            }

            @Override
            public boolean equals(Object o) {
            if (o == this) {
            return true;
            }
            if (o == null || o.getClass() != this.getClass()) {
            return false;
            }
            Student stu = (Student) o;
            return (studentId == stu.getStudentId()
            && (firstName == stu.getFirstName() || (firstName != null && firstName
            .equals(stu.getFirstName())))
            && (lastName == stu.getLastName() || (lastName != null && lastName
            .equals(stu.getLastName()))) && (userName == stu
            .getUserName() || (userName != null && userName.equals(stu
            .getUserName()))));
            }
            @Override
            public int hashCode(){
            int result = 1;
            result = ((Long)studentId).hashCode();
            return result;
            }

            thank you for your help, it is much appriciated

  • Then i use this to remove a course

    boolean rtrn = course.getStudents().remove(student);
    this is returning false and no changes has been made in the relation table (Courses_students) in the database.

    Please look at the comment bellow first and Sorry to have put this in two places

  • My business partners were wanting a form earlier this week and came across a document management site with a ton of fillable forms . If others want it too , here’s https://goo.gl/AUUBV5