19

In a Many to Many relation between two table with a Mapping table in between, how can I only load ids for the second entity.

Following is the example to explain what I want to achieve here. Below is a sample schema

create table user(
 id int PrimaryKey,
 name text
)
create table pages (
 id int PrimaryKey,
 page_name text
)
create table user_page (
 id_user int,
 id_page int,
 PrimaryKey (id_user, id_page)
)

Note: there are additional columns in user and page tables which i have not included here for brevity.

User entity:

@Entity
@Table(name = "user")
public class User {
 @id
 @column(name="id")
 private Integer id;
 @column(name="name")
 private String name;
 ... 
 ...
}

@Entity
@Table(name = "page")
public class Page {
 @id
 @column(name="id")
 private Integer id;
 @column(name="page_name")
 private String name;
 ... 
 ...
}

What I want to do is add another attribute Set<Integer> pageIds in User class and have all page ids mapped for a user in this collection.

How can this be done using Hibernate?

Salman A. Kagzi
  • 3,833
  • 13
  • 45
  • 64

2 Answers2

35

In User class:

@ManyToMany
@JoinTable(
    name="user_page",
    joinColumns = @JoinColumn(name="id_user"),
    inverseJoinColumns = @JoinColumn(name="id_page")
)
public Set<Page> pages;

You can get the id's by iterating over the returned set. By default collections are lazily (i.e. only ids) loaded.

EDIT: If you don't want to map Page for some reason, you can use @ElementCollection like this:

@ElementCollection
@CollectionTable(name="user_page", joinColumns=@JoinColumn(name="id_user"))
@Column(name="id_page")
public Set<Long> pageIds;
lunr
  • 5,159
  • 4
  • 31
  • 47
  • 5
    Is there any way I can load only Ids in a Set without using Page class? i.e. add another attribute Set to User class which has all the ids? – Salman A. Kagzi Jun 09 '13 at 14:34
2

I encountered a problem with the accepted answer, when I wanted to map both pages and pageIds. Hibernate will use both of these fields to make changes to the mapping table when a User is saved, possibly causing a lot of weird behaviour.

I instead solved this by making pageIds @Transient and populating the collection using @PostLoad:

@ManyToMany
@JoinTable(
    name="user_page",
    joinColumns = @JoinColumn(name="id_user"),
    inverseJoinColumns = @JoinColumn(name="id_page")
)
public Set<Page> pages;

@Transient
public Set<Long> pageIds;

@PostLoad
private void postLoad() {
    pageIds = pages.stream().map(Page::getId).collect(Collectors.toSet());
}
darksmurf
  • 3,747
  • 6
  • 22
  • 38
  • 7
    I would think that Hibernate would then need to eagerly load all of the Page objects into memory to achieve this, which wouldn't be great if there were many Page objects or the Page object had many associations and properties. – Jeremy Ninnes Oct 02 '18 at 07:16
  • @JeremyNinnes 5 years later and you're absolutely right; I'm currently working on removing this pattern from my codebase since I recently found that it sometimes increases the number of db lookups by a factor of x1000: from 5 to 5000+. – darksmurf Nov 08 '22 at 23:55