JPA @ManyToMany Unidirectional and Bidirectional

THERE ARE TWO NEW VERSIONS OF THIS POST.
MANY TO MANY SIMPLE: http://uaihebert.com/?p=1674&page=21
MANY TO MANY WITH AN EXTRA FIELD: http://uaihebert.com/?p=1674&page=22

Hello, good morning.

We will see today the @ManyToMany relationship Unidirectional and Bidirectional.

If want to set up an environment to run this code, you can check in the older posts about this subject: @OneToMany and @ManyToOne Unidirectional and Bidirectional, OneToOne Unidirectional and Bidirectional, Mapping two Tables in one Class, Mapping Date and Enum, Composite Primary-Key, SequenceGenerator, TableGenerator – Simple Primay Key, Auto Create Schema Script with: Ant, Hibernate 3 and JPA 2, Tutorial Hibernate 3 with JPA 2.

Let us work with a very easy user case, a notebook may have many owners (Person class), an owner may have many notebooks.

I will use the code from the older posts, I will only explain about the code related to the subject of this post.

Let us see the Person with a relationship Unidirectional:

package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name = "person")
@SecondaryTable(name = "health_care", pkJoinColumns = { @PrimaryKeyJoinColumn(name = "id") })
public class Person {

    @Id
    private int id;

    @Column
    private String name;

    @Column(table = "health_care", name = "company_name")
    private String companyName;

    @ManyToMany
    @JoinTable(name="person_has_notebooks", joinColumns={@JoinColumn(name="person_id")}, inverseJoinColumns={@JoinColumn(name="notebook_id")})
    private List notebooks;

    // Getters and setters
}

The @JoinTable annotation was detailed in other post; if you do not write it in a @ManyToMany, the JPA will create one table by default; the created table name would be “PERSON_NOTEBOOK”.

The Notebook class:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="notebook")
public class Notebook {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String serialNumber;
    private int ramMemoryTotal;
    private int hdSpaceTotal;

    // Getters and setters
}

And to create a relationship, run the code bellow:

package com;

//Using the * to make the import list smaller
import javax.persistence.*;
import java.util.*;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello");
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            Notebook noteA = new Notebook();
            noteA.setSerialNumber("A0123");
            Notebook noteB = new Notebook();
            noteB.setSerialNumber("B0123");
            Notebook noteC = new Notebook();
            noteC.setSerialNumber("C0123");

            List notebooks = new ArrayList();
            notebooks.add(noteA);
            notebooks.add(noteB);
            notebooks.add(noteC);

            Person person = new Person();
            person.setName("Zorro");
            person.setNotebooks(notebooks);

            em.persist(person);

            em.getTransaction().commit();
        }
        catch (Exception e) {
            em.getTransaction().rollback();
            e.printStackTrace();
        }
        finally{
            emf.close();
        }

        System.out.println("It is over");
    }
}

After running the code, check your console:

Console Image.

You can see in the Console Image that the relationship was created correctly.

Check in your database and you will see that the relationship tables were created correctly.

Let us transform our code into a Bidirectional relationship.

Edit your Notebook class:

package com;

import java.util.List;

//Using the * to make the import list smaller
import javax.persistence.*;

@Entity
@Table(name="notebook")
public class Notebook {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String serialNumber;
    private int ramMemoryTotal;
    private int hdSpaceTotal;

    @ManyToMany(mappedBy="notebooks")
    private List persons;

    // Getters and setters
}

If you still with doubts about the concepts of Unidirectional and Bidirectional relationships, you can find the explanation here: JPA @OneToOne Unidirectional and Bidirectional.

In a @ManyToMany relationship any side can be the owner. Remember that the weak side (with the mappedBy), if persisted alone, it will not trigger the Cascade option that you find the owner side. If you only execute the Notebook.setPersons() before you persist the object, the relationship with person will not be updated/created, the weak side does not have this “power”.

I hope this post help you.

See you soon.

10 thoughts on “JPA @ManyToMany Unidirectional and Bidirectional

  1. Hi There,

    Just wanted to say how useful I found this article. Finding a decent explanation on @ManyToMany annotations has been a headache. This was just what I was looking for and can confirm that it works well with Play Framework 2.0.1 eBeans.

    I will be bookmarking your site for future reference. Good job.

    Cheers
    Anthony

    • Hello,

      If you need to search data in a join table you could map the table as an Entity, or to use a native query.

      Thanks for passing by.

  2. Hi, great tutorial but you could complete it showing an example of how to retrieve the notebooks of a person. I’m trying this em.find(Person.class, 1) and I get a PersistentBag – com.sun.jdi.InvocationException occurred invoking method. do you know what the problem is??

  3. I have a similar unidirectional mapping(@ManyToMany). I am able to add, find but not able to delete.
    The error message is :

    SqlExceptionHelper:143- SQL Error: 1451, SQLState: 23000
    SqlExceptionHelper:144- Cannot delete or update a parent row: a foreign key constraint fails (`auto_scaling_group_regions`, CONSTRAINT `FKBB621CF710B01B06` FOREIGN KEY (`regions`) REFERENCES `region` (`id`))

    Tables:
    auto_scaling_group – The table which has a reference to region table
    regions – the table with some constant entries
    auto_scaling_group_regions – Generated by Hibernate to maintain @ManyToMany mapping
    —————————————————
    The AutoScalingGroup entity class:

    @Entity
    public class AutoScalingGroup {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = “id”)
    private Long id;

    @NotNull
    @Size(min = 1, max = 25)
    private String autoScalingGroupName;

    @NotNull
    @Max(500)
    @Min(0)
    private Integer minimumSize;

    @NotNull
    @Max(500)
    @Min(0)
    private Integer maximumSize;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set regions = new HashSet();

    ————————–

    The Region class is:

    @Entity
    public class Region {

    private String RegionName;

    @ManyToOne
    private CloudType cloudType;
    }

    I am able to add and update the AutoScalingGroup table but not able to delete.
    For me the Region table has values that need not be updated and deleted.

    Regards,
    Chandan

    • Hello Chandan,

      You must eliminate every reference before deleting an entity.

      You should do something like:
      a.setB(null);
      b.setA(null); // if the relationship is bidirectional
      entityManager.remove(a);

      By doing this the JPA will remove the reference before deleting it from database.

      I hope that helps you.

      Bye

      • Hi,

        I tried the above approach. The delete is working fine. But the problem now is , I am getting a different error when I am merging/updating the autoscaling group:
        ( @Entity
        public class AutoScalingGroup
        ……
        )
        An entity copy was already assigned to a different entity.; nested exception is java.lang.IllegalStateException: An entity copy was already assigned to a different entity.

        Appreciate your help.

        Chandan

        • Hello Chandan,

          This error usually happens when you have the same entity in two transactions.

          It could happen like below:
          1) open transA
          2) load entityA
          3) open transB
          2) load entityA

          Thanks for passing by.

Leave a Comment