hibernate and mappedBy: Is it possible to automatically set foreign key without setting bidirectional relationship among objects?
Asked Answered
S

1

1

Welcome,

I have 2 classes: Conversation and Question. One Conversation have many questions.

Conversation.java:

package com.jcg.jpa.mappedBy;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "CONVERSATION_TABLE")
public class Conversation implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@Column(name = "CONV_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int conversationId;

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

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="conversation")
private Collection<Question> questions = new ArrayList<Question>();

public Conversation() { }

public int getConversationId() {
    return conversationId;
}

public void setConversationId(int conversationId) {
    this.conversationId = conversationId;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public Collection<Question> getQuestions() {
    return questions;
}

public void setQuestions(Collection<Question> questions) {
    this.questions = questions;
}

@Override
public String toString() {
    return "Employee [conversationId=" + conversationId + ", name=" + name + "]";
}
}

Question.java:

package com.jcg.jpa.mappedBy;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "QUESTION_TABLE")
public class Question implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;

@ManyToOne
@JoinColumn(name = "CONVERSATION_CONV_ID", nullable = false)
private Conversation conversation;


@Column(name = "QUESTION_TEXT")
private String questionText;

@Column(name = "ANSWER_TEXT")
private String answerText;



public Question() { }

public int getId() {
    return id;
}

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

public String getQuestionText() {
    return questionText;
}

public void setQuestionText(String questionText) {
    this.questionText = questionText;
}

public String getAnswerText() {
    return answerText;
}

public void setAnswerText(String answerText) {
    this.answerText = answerText;
}

public Conversation getConversation() {
    return conversation;
}

public void setConversation(Conversation conversation) {
    this.conversation = conversation;
}

@Override
public String toString() {
    return "Question [id=" + id +  ", questionText=" + questionText
            + ", answerText=" + answerText +"]";
}
}

persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="JPAMappedbyExample" transaction-type="RESOURCE_LOCAL">
    <class>com.jcg.jpa.mappedBy.Conversation</class>
    <class>com.jcg.jpa.mappedBy.Question</class>

    <!-- Configuring The Database Connection Details -->
    <properties>
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpatest" />
        <property name="javax.persistence.jdbc.user" value="root" />
        <property name="javax.persistence.jdbc.password" value="qwerty" />

    </properties>
</persistence-unit>

And now in Main.java I'm trying to create Conversation with two questions:

package com.jcg.jpa.mappedBy;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {

private static final EntityManagerFactory emFactoryObj;
private static final String PERSISTENCE_UNIT_NAME = "JPAMappedbyExample";   

static {
    emFactoryObj = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
}

// This Method Is Used To Retrieve The 'EntityManager' Object
public static EntityManager getEntityManager() {
    return emFactoryObj.createEntityManager();
}

private static void insertRecords() {
    EntityManager entityMgrObj = getEntityManager();
    if (null != entityMgrObj) {
        entityMgrObj.getTransaction().begin();

        Conversation conv = new Conversation();
        conv.setName("Discussion about something");

        Question question1 = new Question();
        question1.setQuestionText("2 plus 2");
        question1.setAnswerText("four");
        question1.setConversation(conv);

        Question question2 = new Question();
        question2.setQuestionText("what is Your name");
        question2.setAnswerText("Adam");
        question2.setConversation(conv);

        List<Question> questions = new ArrayList<Question>();
        questions.add(question1);
        questions.add(question2);
        conv.setQuestions(questions);

        entityMgrObj.persist(conv);
        entityMgrObj.getTransaction().commit();

        entityMgrObj.clear();
        System.out.println("Record Successfully Inserted In The Database");
    }
}



public static void main(String[] args) {
    insertRecords();

}
}

In insertRecords() I'm creating conv and two questions. each of questions has set conversation:

question1.setConversation(conv);
question2.setConversation(conv);

Next, a question list which contains these 2 questions is created and set to conv questions list:

conv.setQuestions(questions); 

An it is working ok, because data is inserted into both tables, and foreign key CONVERSATION_CONV_ID is filled:

conversation_table

question_table

However when I remove setting conversation in questions, lines:

question1.setConversation(conv);
question2.setConversation(conv);

foreign key is set to NULL. Why? We already added two questions to conversation questions list:

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="conversation")
private Collection<Question> questions = new ArrayList<Question>();

, so hibernate should know what is conversation foreign key for these two questions (because they are located on specified conversation questions list). So is it possible to avoid setting Conversation for each of the questions and only add questions to Conversation's question list ? How should I configure entities to do that? Or maybe it's impossible and we always need to set it both directions?

Sulph answered 15/2, 2018 at 22:23 Comment(0)
S
3

so hibernate should know what is conversation foreign key for these two questions (because they are located on specified conversation questions list).

No Hibernate shouldn't know what is the Conversation of these two Questions if you don't specify it using setConversation(), because it's dealing with objects here, and these two questions doesn't have any indication on which Conversation they are part of.

Explanation:

Because when you only add these two questions to the Conversation object, this information won't be visible in the Questions objects.

Another thing here, the mappedBy attribute is indicating which object is the owner of the mapping, so how can the mapping be made if there's no given object in the mappedBy side?

That's why you should specify the Conversation in Question object, so the mapping can be correctly evaluated by Hibernate.

Note:

It's recommended to use a Set for OneToMany mapping rather than any other Collection, because this collection shouldn't have any duplicates, so you better change it to:

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="conversation")
private Set<Question> questions = new HashSet<Question>();
Shelter answered 15/2, 2018 at 22:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.