JPA 2 Entity with composite key generates extra redundant *_KEY field in databse

Viewed 34

I'm rewriting some XML configurations to entity classes and I'm experiencing problems with hibernate generated tables.

This is my original XML config for this relation:

<map name="rates" table="article_rate" cascade="all,delete-orphan" lazy="false">
    <key column="fk_article_uuid" foreign-key="article_rate_article_uuid_fk"/>
     <composite-map-key class="org.dropchop.jop.beans.ArticleRate$Id">
        <key-property column="bean_uuid" name="beanUuid" type="UuidType"/>
        <key-property column="rate_type" name="rateType"/>
    </composite-map-key>
    <composite-element class="org.dropchop.jop.beans.ArticleRate">
        <property column="outcome" name="outcome" type="integer"/>
        <property name="value" type="float" precision="17" scale="8">
            <column name="value" sql-type="numeric(17, 8)"/>
        </property>
        <property column="creator_uuid" name="creatorUuid" type="UuidType"/>
        <property column="created" name="created" type="timestamp"/>
    </composite-element>
</map>

After strugling with @ElementCollection and composite primary keys I ended up with 2 entities in relation like this:

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "article")
@ToString(callSuper = true, onlyExplicitlyIncluded = true)
public class EArticle implements Serializable {
    ... 
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "fk_article_uuid",referencedColumnName = "uuid", nullable = false)
    @MapKeyJoinColumns(value = {@MapKeyJoinColumn(name="bean_uuid"),@MapKeyJoinColumn(name="rate_type")})
    private Map<EArticleRate.Id, EArticleRate> rates = new HashMap<>();
    ...
}

and

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name="article_rate")
@IdClass(EArticleRate.Id.class)
public class EArticleRate implements Serializable {

    @Getter
    @Setter
    @EqualsAndHashCode(onlyExplicitlyIncluded = true)
    public static class Id implements Serializable {

        @EqualsAndHashCode.Include
        private UUID article;
        @EqualsAndHashCode.Include
        private UUID beanUuid;
        @EqualsAndHashCode.Include
        private String rateType;

        public Id(UUID article, UUID beanUuid, String rateType) {
            this.article = article;
            this.beanUuid = beanUuid;
            this.rateType = rateType;
        }
    }

@javax.persistence.Id
@Column(name = "bean_uuid", nullable = false)
private UUID beanUuid;

@javax.persistence.Id
@Column(name = "rate_type", nullable = false)
private String rateType;

@ManyToOne
@javax.persistence.Id
@JoinColumn(name = "fk_article_uuid", foreignKey = @ForeignKey(name = "article_rate_article_uuid_fk"), insertable = false, updatable = false, nullable = false)
private EArticle article;

@Column(name = "outcome")
private Integer outcome;

@Column(name = "value")
private Float value;

@Column(name = "creator_uuid")
private UUID creatorUuid;

@Column(name = "updated")
private ZonedDateTime modified;

}

hibernate generates table that looks like:

Hibernate: 

create table article_rate (
   fk_article_uuid uuid not null,
    bean_uuid uuid not null,
    rate_type varchar(255) not null,
    creator_uuid uuid,
    updated timestamp,
    outcome int4,
    value float4,
    rates_KEY bytea,
    primary key (fk_article_uuid, bean_uuid, rate_type)
)

While the table itself looks like it's supposed to, the problem is that hibernamte generates it with redundant field rates_KEY bytea and I don't know why. Is there anything wrong with my relation, is it too complicated, can something be simplified?

Any help appreciated.

Regards
Armando
PS: as seen I also use lombok to simplify code.

3 Answers

I've fixed it by remoivng ArticleRate.Id from ArticleRate and changed

Map<ArticleRate.Id, ArticleRate> 

in Article entity to

Set<ArticleRate> 

since I don't need map anymore.

But would still like to know why is hibernate creating redundant field.

Your @OneToMany setup is a bit weird - generally for bidirectional relationship one side (@ManyToOne) does joinColumn to specify it's the owner of the relationship and has the column, while the other side uses mappedBy parameter on @OneToMany annotation. It's likely that not finding it hibernate assumed that the actual foreign key column was not specified and so it generated it from the field name (hence rates_KEY). It also possibly created a fk_article_uuid column in your article table, which you don't need.

After strugling some more and since with Set I even had more problems on storing rates, I've decided to return to original solution and managed to make it work with a Map.

ArticleRate now looks like

@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name="article_rate")
public class EArticleRate implements Serializable {

    @Embeddable
    public static class RateId implements Serializable {
        private UUID beanUuid;
        private String rateType;
        private UUID articleUuid;


        public RateId() {
        }

        public RateId(UUID beanUuid, String rateType, UUID articleUuid) {
            this.beanUuid = beanUuid;
            this.rateType = rateType;
            this.articleUuid = articleUuid;
        }


        public UUID getBeanUuid() {
        return beanUuid;
        }

        public void setBeanUuid(UUID beanUuid) {
        this.beanUuid = beanUuid;
        }

        public String getRateType() {
        return rateType;
        }

        public void setRateType(String rateType) {
        this.rateType = rateType;
        }

        public UUID getArticleUuid() {
        return articleUuid;
        }

        public void setArticleUuid(UUID articleUuid) {
        this.articleUuid = articleUuid;
        }


        @Override
        public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        RateId rateId = (RateId) o;
        return beanUuid.equals(rateId.beanUuid) && rateType.equals(rateId.rateType) && articleUuid.equals(rateId.articleUuid);
        }

        @Override
        public int hashCode() {
        return Objects.hash(beanUuid, rateType, articleUuid);
        }
    }

    @EmbeddedId
    private RateId id;

    @ManyToOne
    @MapsId("articleUuid")
    @JoinColumn(name = "fk_article_uuid", foreignKey = @ForeignKey(name = "article_rate_article_uuid_fk"), insertable = false, updatable = false, nullable = false)
    private EArticle article;

    /** other fields **/
}

And relation in my Article entity now looks like:

@OneToMany(mappedBy = "article", fetch = FetchType.EAGER, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
@MapKey(name = "id")
private Map<EArticleRate.RateId, EArticleRate> rates = new HashMap<>();

My db table for article rates is now correct and what I wanted

create table article_rate (
   fk_article_uuid uuid not null,
    beanUuid uuid not null,
    rateType varchar(255) not null,
    creator_uuid uuid not null,
    updated timestamp,
    outcome int4,
    value float4,
    primary key (fk_article_uuid, beanUuid, rateType)
)

I wanted to use map since I usualy know what rates I want to get out with a key, so I dont have to use .stream() or iterations to find the correct ones.

Related