breaktop
breaktop

Reputation: 2029

Mapping a bidirectional One-to-Many Hibernate Entity

I am trying to map a bi-directional (one to many and many to one) relationship in Hibernate. We're getting errors when attempting save a result.

The error we're getting is:

Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : com.example.Component.resultId at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:92)

From the source code below a Result can have many Components and many Components can belong to a single Result. One of the requirement is that the resultId in Result must be a String. This requirement is out of our control.

An example Entity source code is below:

@Entity
@Table(name = "result")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Result extends AbstractEntity {

    @OneToMany(cascade = CascadeType.MERGE)
    @JoinColumn(name = "resultId")
    private List<Component> component = new ArrayList<>();
}

@Entity
@Table(name = "cmpt")
@XmlRootElement
public class Component extends AbstractEntity {
    @ManyToOne(targetEntity = Result.class)
    private String resultId;
}

Here is the solution that worked for me

@Entity
@Table(name = "result")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Result extends AbstractEntity {

    @OneToMany(cascade = CascadeType.MERGE, nullable = false)
    @JoinColumn(name = "resultId")
    private List<Component> component = new ArrayList<>();
}

@Entity
@Table(name = "cmpt")
@XmlRootElement
public class Component extends AbstractEntity {
    @Column(name = "result_id", insertable = false, updatable = false, nullable = false)
    private String resultId;
}

Upvotes: 0

Views: 829

Answers (4)

breaktop
breaktop

Reputation: 2029

Here is the solution that worked for me:

@Entity
@Table(name = "result")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Result extends AbstractEntity {

    @OneToMany(cascade = CascadeType.MERGE, nullable = false)
    @JoinColumn(name = "resultId")
    private List<Component> component = new ArrayList<>();
}

@Entity
@Table(name = "cmpt")
@XmlRootElement
public class Component extends AbstractEntity {
    @Column(name = "result_id", insertable = false, updatable = false, nullable = false)
    private String resultId;
}

Upvotes: 0

yaswanth
yaswanth

Reputation: 2477

The correct way to create a bidirectional entity is this.

@Entity
@Table(name = "result")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Result extends AbstractEntity {

    @OneToMany(cascade = CascadeType.MERGE, mappedBy="result")
    private List<Component> component = new ArrayList<>();
}

@Entity
@Table(name = "cmpt")
@XmlRootElement
public class Component extends AbstractEntity {
    @ManyToOne(targetEntity = Result.class)
    @JoinColumn(name = "result_id")  //result_id is the column name (foreign key) in cmpt table
    private Result result;

    @Column(name = "result_id", insertable = false, updatable = false)
    private String resultId;
}

This will ensure there will be only one bi-direction relation (not two uni-directional relations etc). Let me know if you are expecting something else.

Update 1:

You can add another key to the entity as specified above. You'll get the foreign key as a string. But you need to remember that the actual mapping with JoinColumn should not be removed. I have tested the above configuration and it seems to work for me.

Upvotes: 1

CIPHER007
CIPHER007

Reputation: 376

I think you have to remove the annotation @ManyToOne(targetEntity = Result.class) from Component.java class and when you tries to save data first you have to save Result using save method and then you got the resultId returned by save method set this resultId in component entity using setter and getter and then save the component

Upvotes: 0

Maciej Kowalski
Maciej Kowalski

Reputation: 26572

You have to declare an entity as the field annotated with @ManyToOne. Also i think in your case you should put the @Join column on the Component side:

@Entity
@Table(name = "cmpt")
@XmlRootElement
public class Component extends AbstractEntity {
    @ManyToOne
    @JoinColumn(name = "resultId")
    private Result result;
}

now you dont need the targetEntity attrbute.

Also remove the @JoinColumn from @OneToMany and add mappedBy attribute:

@Entity
@Table(name = "result")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Result extends AbstractEntity {

    @OneToMany(cascade = CascadeType.MERGE, mappedBy="result")
    private List<Component> component = new ArrayList<>();
}

Upvotes: 0

Related Questions