Search code examples
javaspringhibernatejpamany-to-many

Get id's of a ManyToMany mapping table


I'm writing an API using Spring Boot and Hibernate where my persisted entity objects are also used as DTOs sent to and from the client. This is a simplified version of a typical entity I use:

@Entity
@Table(name = "STUDENT")
public class Student {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @ElementCollection
    @CollectionTable(name = "GROUP_STUDENT",
                     joinColumns = @JoinColumn(name = "GROUP_ID"))
    @Column(name="STUDENT_ID")
    private Set<Long> groupIds;

    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name="GROUP_STUDENT",
               joinColumns = @JoinColumn(name="GROUP_ID"),
               inverseJoinColumns = @JoinColumn(name="STUDENT_ID")
    )
    private Set<Group> groups = new HashSet<>();

    // getters and setters
}

and this is the associated class:

@Entity
@Table(name = "GROUP")
public class Group {
    @Id
    @GeneratedValue
    @Column(name = "ID")
    private Long id;

    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY, mappedBy = "groups")
    private Set<Student> students = new HashSet<>();

    // getters and setters
}

As you can see, there is a @ManyToMany association between Student and Group.

Since I send objects like these to the client, I choose to send only the id's of the associations and not the associations themselves. I've solved this using this answer and it works as expected.

The problem is this. When hibernate tries to persist a Student object, it inserts the groups as expected, but it also tries to insert the groupIds into the mapping table GROUP_STUDENT. This will of course fail because of the unique constraint of the mapping table composite id. And it isn't possible to mark the groupIds as insertable = false since it is an @ElementCollection. And I don't think I can use @Formula since I require a Set and not a reduced value.

This can of course be solved by always emptying either the groups of the groupIds before saving or persisting such an entity, but this is extremely risky and easy to forget.

So what I want is basically a read only groupIds in the Student class that loads the data from the GROUP_STUDENT mapping table. Is this possible? I'm grateful for any suggestions and glad to ellaborate on the question if it seems unclear.


Solution

  • I've managed to solve this by making the id-collection @Transient and populating it using @PostLoad:

    @Entity
    @Table(name = "STUDENT")
    public class Student {
        @PostLoad
        private void postLoad() {
            groupIds = groups.stream().map(Group::getId).collect(Collectors.toSet());
        }
    
        @Id
        @GeneratedValue
        @Column(name = "ID")
        private Long id;
    
        @Transient
        private Set<Long> groupIds;
    
        @JsonIgnore
        @ManyToMany(fetch = FetchType.LAZY)
        @JoinTable(name="GROUP_STUDENT",
                   joinColumns = @JoinColumn(name="GROUP_ID"),
                   inverseJoinColumns = @JoinColumn(name="STUDENT_ID")
        )
        private Set<Group> groups = new HashSet<>();
    
        // getters and setters
    }