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

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.

Check out the @Any annotation for this task.

@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).

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.

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.