How to map a Many-To-One relationship with multiple target entities with JPA and Hibernate


#1

Hello everyone,
I’m trying to map column to multiple target entities and trying to define a relationship many-to-one.

I have a column in my table that can be foreign key on multiple tables. Let call that column channel. If channel type is 1 then I need to fetch entity A (channel), and if channel_type is 2 then I have to fetch entity B (channel).

Is this possible to do in hibernate?

Thank you in advance.


#2

Check out the @Any annotation for this task.


#3

@vlad thank you. Can you please check this code, I have error:

ChannelType.java

package com.tips.hibernate.entities;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "channel_type")
@DynamicInsert
@DynamicUpdate
public class ChannelType implements Serializable {

    private static final long serialVersionUID = 20181121211300L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;

    @Column(name = "code", nullable = false, unique = true)
    private String code;

    @Column(name = "description")
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Call.java

package com.tips.hibernate.entities;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "call")
@DynamicInsert
@DynamicUpdate
public class Call implements Serializable, Channel {

    private static final long serialVersionUID = 20181121212000L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;

    @Column(name = "duration", nullable = false)
    private Long duration = 0L;

    @Column(name = "target_phone", nullable = false)
    private String targetPhone;

    @Column(name = "comment")
    private String comment;

    @Column(name = "status", nullable = false)
    private String status = "handled";

    @Column(name = "description")
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Long getDuration() {
        return duration;
    }

    public void setDuration(Long duration) {
        this.duration = duration;
    }

    public String getTargetPhone() {
        return targetPhone;
    }

    public void setTargetPhone(String targetPhone) {
        this.targetPhone = targetPhone;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Sms.java

package com.tips.hibernate.entities;

import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "sms")
@DynamicInsert
@DynamicUpdate
public class Sms implements Serializable, Channel {

    private static final long serialVersionUID = 20181121211500L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;

    @Column(name = "target_phone", nullable = false)
    private String targetPhone;

    @Column(name = "comment")
    private String comment;

    @Column(name = "status", nullable = false)
    private String status = "unknown";

    @Column(name = "description")
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTargetPhone() {
        return targetPhone;
    }

    public void setTargetPhone(String targetPhone) {
        this.targetPhone = targetPhone;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

Interaction.java

package com.tips.hibernate.entities;

import org.hibernate.annotations.*;

import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.Date;

@Entity
@Table(name = "interaction")
@DynamicInsert
@DynamicUpdate
@SuppressWarnings("squid:S1948")
@AnyMetaDef(
    name = "ChannelMetaDef", idType = "long", metaType = "string",
    metaValues = {
            @MetaValue(targetEntity = Call.class, value = "call"),
            @MetaValue(targetEntity = Sms.class, value = "sms")
    }
)
public class Interaction implements Serializable {

    private static final long serialVersionUID = 20181121214100L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false, updatable = false)
    private Long id;

//    @ManyToOne
//    @JoinColumn(name = "channel_type_id", nullable = false)
//    private ChannelType channelType;

    @Any(
        metaColumn = @Column(name = "channel_type_id"),
        metaDef = "ChannelMetaDef",
        fetch = FetchType.EAGER
    )
    @JoinColumn(name = "channel_id", nullable = false)
    private Channel channel;

    @Column(name = "description")
    private String description;

    @Column(name = "created_at", nullable = false)
    private Date createdAt;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

//    public ChannelType getChannelType() {
//        return channelType;
//    }
//
//    public void setChannelType(ChannelType channelType) {
//        this.channelType = channelType;
//    }

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    @Override
    public String toString()
    {
        return "Interaction{" +
                "id=" + id +
                ", channel=" + channel +
                ", description='" + description + '\'' +
                ", createdAt=" + createdAt +
                '}';
    }
}

interface Channel {
    String getTargetPhone();
}

Interaction schema:

<changeSet id="interaction" author="hedza06" context="dev, prod">
        <createTable tableName="interaction">
            <column name="id" type="int" autoIncrement="${autoIncrement}">
                <constraints nullable="false" primaryKey="true" />
            </column>
            <column name="channel_type_id" type="bigint">
               <constraints nullable="false"/>
            </column>
            <column name="channel_id" type="bigint">
                <constraints nullable="false"/>
            </column>
            <column name="description" type="varchar(255)">
                <constraints nullable="true"/>
            </column>
            <column name="created_at" type="timestamp" defaultValueDate="${now}"/>
        </createTable>
        <!-- Add foreign key on channel_type -->
        <addForeignKeyConstraint
                baseTableName="interaction"
                baseColumnNames="channel_type_id"
                constraintName="fk_interaction_channel_type_id_key"
                referencedTableName="channel_type"
                referencedColumnNames="id" />
    </changeSet>

When I tried to fetch all interaction it returns null for channel. Channel can be Sms or Call depends on ChannelType.java (column code which is string - unique).


#4

Try to debug it and see why it does not work for you. You can find a working example in the Hibernate ORM test cases.


#5

getting exact kissanime same errors. Somethings wrong with groupme the code.

Regards,
Shane.


#6

Check out these examples from the Hibernate ORM GitHub repository. They work just fine and you can compare yours with these and spot the error.