0

I've noticed some strange behavior in one of my applications. I have a ManyToOne relationship between two classes, Frame and Brand. As far as the database is concerned, the relationship is unidirectional. Each Frame in the table has a brand_id column but the Brand doesn't have any columns pointing back the other way. All good so far, but in the actual code I sometimes need to get information about all the frames associated with a particular brand. To facilitate this, I added a field to the Brand class that holds a collection of frame ids and annotated it with @ElementCollection and @CollectionTable.

Here is where things get a bit funky. The Frame class is part of a hierarchy using single table inheritance. Not all of the classes that map to this table have brands, so the brand_id column has to be nullable. From what I have been able to determine so far, Hibernate forces 'nullable' to be false for relationships annotated with @ElementCollection. There doesn't seem to be any documentation explaining why. The relevant code can be found in 'org.hibernate.cfg.Ejb3JoinColumn.buildJoinTableJoinColumns()' in hibernate-core-5.3.13.Final.jar but all it says is

currentJoinColumn.setNullable( false ); //I break the spec, but it's for good

Is there any way to override this limitation in Hibernate? If not, is there some other way to do what I want without using @ElementCollection?

My Code:

@Entity(name = "Frame")
@DiscriminatorValue("FRAME")
public class Frame extends Product
{
    @ManyToOne(optional=true)
    @JoinColumn(name = "brand_id")
    @IndexedEmbedded
    protected Brand brand;

    public Brand getBrand()
    {
        return brand;
    }
}

@Entity(name = "Brand")
@Table(name = "brands")
public class Brand extends Entity
{
    @ElementCollection
    @CollectionTable(name = "products", joinColumns = @JoinColumn(name = "brand_id"))
    @Column(name = "id")
    protected Set<BigInteger> frameIds;

    public Set<BigInteger> getFrameIds()
    {
        return frameIds;
    }

    public void setFrameIds(Set<BigInteger> frameIds)
    {
        this.frameIds = frameIds;
    }
}

public class Foo extends Entity
{
    @ManyToOne
    @JoinColumn(name = "brand_id")
    protected Brand brand;

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "frames_to_features", inverseJoinColumns = {
        @JoinColumn(name = "frame_id", referencedColumnName = "id") }, joinColumns = {
                @JoinColumn(name = "feature_id", referencedColumnName = "id") })
    protected Set<Frame> frames;

    public List<BigInteger> getExcludedFrameIds()
    {
        List<BigInteger> brandFrames = new ArrayList<BigInteger>();
        if (getBrand() != null)
        {
            brandFrames.addAll(getBrand().getFrameIds());
            Set<BigInteger> localFrames = getFrameIds();
            if (localFrames != null)
            {
                brandFrames.removeAll(localFrames);
            }
        }
        return brandFrames;
    }
}
pbuchheit
  • 1,371
  • 1
  • 20
  • 47
  • Why can't you map the frames directly? Use `@OneToMany(mappedBy = "brand") Set frames;` – Christian Beikov Mar 23 '21 at 08:12
  • The relationship is unidirectional. Brands do not need the full entity graph for Frame, they just need a list of Ids. – pbuchheit Mar 23 '21 at 13:42
  • I understand what design you "intend", but as you figured, it looks like it's not possible to model it this way. Instead of mapping the full entity, you could try to map a `@Immutable` entity consisting just of the id to reduce the amount of data loaded. – Christian Beikov Mar 23 '21 at 17:16

0 Answers0