Question

I have a Group entity that has a list of User entities in a many to many relationship. It is mapped by a typical join table containing the two IDs. This list may be very large, a million or more users in a group.

I need to add a new user to the group, typically that will be something like

group.getUsers().add(user);
user.getGroups().add(group);

em.merge(group);
em.merge(user);

If I understand typical JPA operation, will this require pulling down the entire list of 1 million+ users into the collection in order to add the new user and then save? That doesn't sound very scalable to me.

Should I simply not be defining this relationship in JPA? Should I be manipulating the join table entries directly in a case like this?

Please forgive the loose syntax, I'm actually using Spring Data JPA so I don't directly use the entity manager directly very often, but the question seems to be general to JPA so I wanted to pose it that way.

Was it helpful?

Solution

Design your models like this and play with UserGroup for associations.

@Entity
public class User {

   @OneToMany(cascade = CascadeType.ALL, mappedBy = "user",fetch = FetchType.LAZY)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Set<UserGroup> userGroups = new HashSet<UserGroup>();

}


@Entity
@Table(name="user_group", 
    uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "group_id"})})
public class UserGroup {


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id", nullable = false)
    @ForeignKey(name = "usergroup_user_fkey")
    private User user;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "group_id", nullable = false)
    @ForeignKey(name = "usergroup_group_fkey")  
    private Group group;

}


@Entity
public class Group {

    @OneToMany(cascade = CascadeType.ALL, mappedBy="group", fetch = FetchType.LAZY )
    @OnDelete(action = OnDeleteAction.CASCADE)      
    private Set<UserGroup>  userGroups  = new HashSet<UserGroup>();

}

Do like this.

     User user =     findUserId(id); //All groups wont be loaded they are marked lazy
     Group group =  findGroupId(id);  //All users wont be loaded they are marked lazy

     UserGroup  userGroup = new UserGroup();
     userGroup.setUser(user);
     userGroup.setGroup(group);

     em.save(userGroup);

OTHER TIPS

Using the ManyToMany mapping effectively is caching the collection in the entity, so you might not want to do this for large collections, as displaying it or passing the entity around with it triggered will kill performance.

Instead you might remove the mapping on both sides, and create an entity for the relation table that you can use in queries when you do need to access the relationship. Using an intermediate entity will allow you to use paging and cursors, so that you can limit the data that might be brought back into usable chunks, and you can insert a new entity to represent new relationships with ease.

EclipseLink's attribute change tracking though does allow adding to collections without the need to trigger the relationship, as well as other performance enhancements. This is enabled with weaving and available on collection types that do not maintain order.

The collection classes returned by getUsers() and getGroups() don't have to have their contents resident in memory, and if you have lazy fetching turned on, as I assume you do for such a large relationship, the persistence provider should be smart enough to recognize that you're not trying to read the contents but just adding a value. (Similarly, calling size() on the collection will typically cause a SQL COUNT query rather than actually loading and counting the elements.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top