Hibernate inserts optimization when there is relationship @ManyToMany

0

Imagine relationships:

  

User has many Permissions

     

Permission has many Users

We can create a N para N relationship as follows:

User.class

public class User {
  /*Many attributes here*/
  private List permissions;

  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy =         "users")
  public List getPermissions() {
    return permissions;
  }
  public void setPermissions(List permissions){
    this.permissions = permissions;
  }
}

Permission.class

public class Permission{
  /*Many attributes here..*/
  private List users;

  @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    public List<User> getUsers() {
    return users;
  }

  public void setUsers(List<User> users) {
    this.users = users;
  }
}

So you can affirm a N pra N relationship, where owner is the Permission class.

When I want to say that user 1 has the permissions 3 and 5 , for example, I do something like this:

User u = (User)session.get(User.class,  1);

List permissions = new ArrayList<>();
permissions.add(new Permission(3));
permissions.add(new Permission(5));

for(Object permission : permissions){
  Permission p = 
        (Permission) session.get(Permission.class, ((Permission)permission).getId());
  p.getUsers().add(u);
}

session.getTransaction().commit();

Everything working correctly.

However, when I view the logs , I see that hibernate is firstly deleting all records in the associative table permission_user related to that permission, then adding it all up again. (Since we have access to all previously associated users when we call p.getUsers(); //pega todos os usuários já associados )

Let's assume that 3 permission already has the 100 user and the 101 user associated with it, and so I want to now associate the user 1 . In the logs , hibernate is running more or less this ...

delete from permission_user where id_permission = ? //deve ser id 3
insert into permission_user (id_permission, id_user) values (?, ?) //deve ser 3 e 100 respectivamente
insert into permission_user (id_permission, id_user) values (?, ?) //deve ser 3 e 101 respectivamente
insert into permission_user (id_permission, id_user) values (?, ?) //deve ser 3 e 1 respectivamente

The issue is, since I am associating a new user, my expectation is that hibernate can only execute insert (Since user 100 and 101 are already present in the associative table). And this occurs for each permission. So, if I say that user 1 will have the permissions, 3 , 5 , 10 11 , 12 , 13 , 15 , 16 and 19 and each of these permissions already has users associated with it, about 10 users for example, hibernate will execute more than 100 instructions to perform what I want. This is really very problematic.

Could someone give me a light?

Thank you in advance.

    
asked by anonymous 18.01.2017 / 04:28

1 answer

1
  

This is because Hibernate supports an additional feature of a collection called Bag . A Bag is a collection that may have duplicate members, but is not ordered. The best feature of a Bag is that you can get the number of occurrences of an object through the API using the public int occurrences(Object o) method.

     

In List , there is no way to do the same without iterating through all its elements.

     

So when a List is mapped without a column for indexing, Hibernate treats it as a Bag . In essence, a List that has a column for indexing is Bag indexed.

     

With Hibernate treating List as a Bag , it believes that there may be duplicate elements in the join table. Also, it knows that the elements in the join table are not ordered. So, there is no way to know which lines should be deleted from the object in java.

     

Then Hibernate needs to continue and re-insert all the lines it knows from java that should not be deleted.

     

If you do not need duplicate objects, you can use Set . A Set does not need a column for indexing, and Hibernate can distinguish a row from its table because it knows that a Set has no duplicate elements.

Source

That is, either you use a column to sort your join table, or use Set instead of List . In case you keep List it would look like this:

Permission:

@Entity
public class Permission {

    @Id
    private int id;

    @OrderColumn(name="order_id")
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List<User> users;

    public Permission() {}

    public Permission(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }
}

User:

@Entity
public class User {

    @Id
    private int id;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "users")
    private List<Permission> permissions;

    public User() {}

    public User(int id) {
        this.id = id;
    }

    public List<Permission> getPermissions() {
        return permissions;
    }

    public void setPermissions(List<Permission> permissions) {
        this.permissions = permissions;
    }
}

Test:

public class Teste {

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

        em.getTransaction().begin();

        User u = em.find(User.class, 1);

        int permissions[] = {1, 2};

        for(int permission : permissions) {
            Permission p = em.find(Permission.class, permission);
            p.getUsers().add(u);
        }

        em.getTransaction().commit();

        em.close();
        emf.close();
    }
}

With the annotation:

  

select user0_.id as id1_2_0_ from User user0_ where user0_.id =?

     

select permission0_.id as id1_0_0_ from Permission permission0_ where permission0_.id =?

     

select users0_.permissions_id as permissi1_1_0_, users0_.users_id as users_id2_1_0_, users0_.permissions_id_index_column as permissi3_0_, user1_.id as id1_2_1_ from Permission_User users0_ inner join User user1_ on users0_.users_id = user1_.id where users0_.permissions_id =?

     

select permission0_.id as id1_0_0_ from Permission permission0_ where permission0_.id =?

     

select users0_.permissions_id as permissi1_1_0_, users0_.users_id as users_id2_1_0_, users0_.permissions_id_index_column as permissi3_0_, user1_.id as id1_2_1_ from Permission_User users0_ inner join User user1_ on users0_.users_id = user1_.id where users0_.permissions_id =?

     

insert into Permission_User (permissions_id, permissions_id_index_column, users_id) values (?,?,?)

     

insert into Permission_User (permissions_id, permissions_id_index_column, users_id) values (?,?,?)

No DELETE !

    
18.01.2017 / 20:15