OneToMany - extend CollectionKey with PK of the "One" side.

Description

Hi there,

Sometimes there is a need to make a non-standard association between two entities. Imagine this example:

create table person ( id bigint generated by default as identity, first_name varchar(255), last_name varchar(255), primary key (id) ); create table alternative_name ( id bigint generated by default as identity, alternative varchar(255), name varchar(255), primary key (id) ); insert into person (first_name,last_name) values ('blazej', 'adamczyk'); insert into person (first_name,last_name) values ('blazej', 'testing'); insert into alternative_name (name, alternative) values ('blazej', 'blaze'); insert into alternative_name (name, alternative) values ('blazej', 'vlach');

A person has first and last name. Multiple Person can have the same name, and a single name may have multiple alternative_names. Thus we have a ManyToMany but without a jointable.

It is possible to do this using a single sided OneToMany collection on Person class like this:

@Entity @Data @ToString @NoArgsConstructor public class Person implements Serializable { @Id @GeneratedValue private Long id; private String firstName; private String lastName; @OneToMany @JoinColumn(name="name", referencedColumnName = "firstName") List<AlternativeName> alternativeNameList; }

This works well until we do not fetch several Persons with the same first_name during a single transaction. If this happens during flush and commit we get the following:

org.hibernate.HibernateException: Found shared references to a collection

After digging a bit through the hibernate classes I found that the CollectionKey which identifies a collection between the two classes is based only on the "JoinColumn".

I would like to start a discussion: Could we identify a PersistenceCollection by the "JoinColumn" but additionally by the PK of the source object? This way in the above example both Person will get a new collection representing alternative names but not the same.

There's the problem of updating these collections of course. Maybe we can assume that if the "One" side is not actually really one, than the user should be aware of what he is doing and will not modify this collection in multiple objects?

Alternatively maybe this check for shared collections should be disabled if the collections are marked non insertable and non updateable? This should still leave the users the ability to properly handle such association at least in read only mode.

In the attachment I have uploaded a zip containg a spring-boot project showing the error and a "proposed" (probably wrong) solution by modifying the CollectionReferenceInitializerImpl and CollectionKey classes.

Let me know what you think about this. Thanks.

Attachments

3
  • 21 Aug 2020, 03:44 PM
  • 21 Aug 2020, 03:24 PM
  • 29 Mar 2017, 11:18 PM

Activity

Christian BeikovAugust 21, 2020 at 4:22 PM

I suppose you have a Source entity on which the Set<SourceGroup> is mapped as well? Why not map the sourceId as @ManyToOne(fetch = LAZY) Source source instead?

That way, everything should work out fine as there would be no more shared Collection references.

Gonzalo De la CruzAugust 21, 2020 at 3:46 PM
Edited

Hello Christian.

I totally undestand your point. Let me explain you my concrete problem because maybe you have another idea about how to solve it.

In my domain classes, I have a series of Sources that generate Events. Users can also create Groups of Sources. The database schema would be something like this:

The many-to-many relationship between Source and Source Group is mapped with a join table.

My users want to be able to filter Events by Source Group name. In order to do that, I declare the following relationship:

@Entity @Data @ToString @NoArgsConstructor public class Event implements Serializable { @Id @GeneratedValue private Long id; private String message; private Long sourceId; @ManyToMany @JoinTable(name = "source_source_group", joinColumns = @JoinColumn(name = "source_id", referencedColumnName = "sourceId", updatable = false, insertable = false), inverseJoinColumns = @JoinColumn(name = "source_group_id", referencedColumnName = "id", updatable = false, insertable = false)) List<SourceGroup> sourceGroups; } //////////////////////////////////////////////////////////////////////////////////////////////////////// @Entity @Data @ToString @NoArgsConstructor public class SourceGroup { @Id private Long id; private String name; }

However, when I run the code I get the same error:

Caused by: org.hibernate.HibernateException: Found shared references to a collection: com.example.Event.sourceGroups

In my case, the source_id of the Events is immutable and will never change, but it is not the primary key of the Entity.

I attach a working example with this implementation.

Christian BeikovAugust 21, 2020 at 2:20 PM

I’m personally a bit undecided about whether this is something Hibernate should support at all. Essentially, what you are modelling here is no well defined *ToMany assocation, but an ad-hoc one, given that the join condition is based on non-unique columns. This begs for bad things to happen.

The more I think about it, the less I think this mapping should even be allowed. You could have one AlternativeName being associated to multiple Persons in one session. If the association is updatable, what should happen if you “just” change the name of a person? Should the AlternativeName be updated? Or maybe deleted? What if you remove an AlternativeName from the collection? Should the name be updated to null? Should the AlternativeName be removed? This mapping ist just broken by design.

No matter what you are trying to do with your generic filtering library, I don’t think this the way you should try to achieve this, simply because such mappings are inherently problematic.

I don’t want to advertise here, but maybe you will like what I did with Blaze-Persistence Entity-Views which supports unidirectional mappings or correlated/ad-hoc bidirectional mappings as well. It has a filter concept built into the model, so maybe this is just about what you are looking for: https://github.com/Blazebit/blaze-persistence#entity-view-usage

Gonzalo De la CruzAugust 21, 2020 at 2:01 PM

@Christian In our use case we are implementing a generic filtering library that generates queries dynamically based on the filters selected by the user. We use querydsl-jpa to generate the predicates, which uses Hibernate below for performing the queries. Because of this, we cannot use ad-hoc queries as we need our implementation to be generic for all the entities.

All the other relationships we have between entities work perfectly. But now we need to make a join using a column that does not belong to the primary key and that is not unique, and we are facing this issue.

Adam TaylorAugust 21, 2020 at 1:45 PM

Hmm, okay, I haven’t tried the ad-hoc joins yet so I may have to go down that route. It’s a bummer though because it’s certainly adding complexity that I’d rather be abstracted into the @JoinColumn annotation.

Details

Assignee

Reporter

Components

Priority

Created March 29, 2017 at 11:21 PM
Updated August 21, 2020 at 4:22 PM

Flag notifications