1

I am working on spring boot api where I have a entity model course and another is subjects. Under a course I want to have a list of multiple subjects.I am using a OneToMany annotation to do this.

Here's my code:

course.java

@Entity
@EntityListeners(AuditingEntityListener.class)
public class course {

    @Override
    public String toString() {
        return "course [id=" + id + ", title=" + title + ", description=" + description + "]";
    }

    public long getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
   
    
    public List<subject> getSubjects() {
        return subjects;
    }

    public void setSubjects(List<subject> subjects) {
        this.subjects = subjects;
    }

    public String getProperty(String key) {
        switch (key) {
        case "title":

            return this.title;
        case "description":
            return this.description;
        case "date":
            return this.date.toString();
        case "id":
            return Integer.toString(this.id);

        default:
            return "helo";
        }
    }

    public course() {
        super();
        // TODO Auto-generated constructor stub
    }



    public course(int id, String title, List<subject> subjects, String description, Date date) {
        super();
        this.id = id;
        this.title = title;
        this.subjects = subjects;
        this.description = description;
        this.date = date;
    }



    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String title;
    @OneToMany
    private List<subject> subjects;
    private String description;
    @CreatedDate
    @Temporal(TemporalType.DATE)
    private Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

subject.java

@Entity
public class subject {
   public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getComplexity() {
        return complexity;
    }
    public void setComplexity(String complexity) {
        this.complexity = complexity;
    }
public subject(String name, String complexity) {
        super();
        this.name = name;
        this.complexity = complexity;
    }
@Id
   private String name;
   private String complexity;
}

Now When I send a POST request like:

{
    "title":"test course sossk",
    "description":"Is it",
    "subjects":[
        {
            "name":"java",
            "complexity":"easy"
        },
        {
            "name":"c++",
            "complexity":"easy"
        }
    ]
}

I get a error like this:

 SQL Error: 1452, SQLState: 23000
2021-07-18 20:01:48.818 ERROR 10408 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : Cannot add or update a child row: a foreign key constraint fails (`courses`.`course_subjects`, CONSTRAINT `FKgy72njp8nmip43dy1cwk43ue6` FOREIGN KEY (`subjects_name`) REFERENCES `subject` (`name`))
2021-07-18 20:01:48.819  INFO 10408 --- [nio-8080-exec-2] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements
2021-07-18 20:01:48.861 ERROR 10408 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException:

How can I solve this? How can I add a foreign key?

any help will be highly appreciated.

Update

course.java

@Entity
@EntityListeners(AuditingEntityListener.class)
public class course {

    @Override
    public String toString() {
        return "course [id=" + id + ", title=" + title + ", description=" + description + "]";
    }

    

    public int getId() {
        return id;
    }



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



    public String getTitle() {
        return title;
    }



    public void setTitle(String title) {
        this.title = title;
    }







    public List<subject> getSubjects() {
        return subjects;
    }



    public void setSubjects(List<subject> subjects) {
        this.subjects = subjects;
    }



    public String getDescription() {
        return description;
    }



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



    public String getProperty(String key) {
        switch (key) {
        case "title":

            return this.title;
        case "description":
            return this.description;
        case "date":
            return this.date.toString();
        case "id":
            return Integer.toString(this.id);

        default:
            return "helo";
        }
    }

    public course() {
        super();
        // TODO Auto-generated constructor stub
    }
public course(int id, String title, List<subject> subjects, String description, Date date) {
        super();
        this.id = id;
        this.title = title;
        this.subjects = subjects;
        this.description = description;
        this.date = date;
    }














    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String title;
    @OneToMany(targetEntity = subject.class,cascade = CascadeType.ALL)
    private List<subject> subjects;
    private String description;
    @CreatedDate
    @Temporal(TemporalType.DATE)
    private Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

subject.java

@Entity
public class subject {
   public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getComplexity() {
        return complexity;
    }
    public void setComplexity(String complexity) {
        this.complexity = complexity;
    }
public subject(String name, String complexity) {
        super();
        this.name = name;
        this.complexity = complexity;
    }
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
  private int id;
   private String name;
   private String complexity;
   
}
rudeTool
  • 526
  • 1
  • 11
  • 25
  • The `@ManyToOne`-annotation on the owning side (i.e. `subject`) is missing. I recommend reading a tutorial on the subject, e.g. [this one over at Baeldung](https://www.baeldung.com/hibernate-one-to-many). --- Two remarks: the explicit call to `super()` in constructors is superfluous, they are inserted implicitly if no explicit call to `super(...)` or `this(...)` is present. - Classes in Java should always start with an uppercase letter (`course` -> `Course`, `subject` -> `Subject`). – Turing85 Jul 18 '21 at 14:51
  • can you guide me with a simple implementation using the above? – rudeTool Jul 18 '21 at 15:00
  • Sorry, we are not a code-writing service. I will not provide an implementation. If I were to provide an implementation, it would devoid you of the possibility to solve it for yourself. Solving the challenge by yourself should benefit you more in the long run. – Turing85 Jul 18 '21 at 15:06
  • no worries,thanks I will try it out myself – rudeTool Jul 18 '21 at 15:12

2 Answers2

2
 @OneToMany(mappedBy="name")
 private List<subject> subjects;


 @ManyToOne
 @JoinColumn(name="course_id", nullable=false) // should reference mapped column
 private String name;

Your solution will be something like this. Please refer this as well, OneToMany & ManyToOne mapping JPA / Hibernate

VedantK
  • 9,728
  • 7
  • 66
  • 71
  • I did the first part of the solution and the implementation is working fine.Is there a specific reason I should use the 2nd part i.e `ManyToOne` part? – rudeTool Jul 18 '21 at 15:49
  • `@OneToMany` annotation is used to define the property in the Subject class that will be used to map the mappedBy variable. – VedantK Jul 18 '21 at 15:52
  • that I understood,but without using ManyToOne my api is working perfect.what is the ManyToone part used for? – rudeTool Jul 18 '21 at 15:54
  • can you please update your new code in question? – VedantK Jul 18 '21 at 15:56
  • If the relationship is bidirectional, the `mappedBy` element must be used to specify the relationship field or property of the entity that is the owner of the relationship. Your case `targetEntity` (The entity class that is the target of the association) is defined so it seems correct to me. – VedantK Jul 18 '21 at 16:08
0

Kindly see below and in-comment explanation. I used the lombok @Data to shorten the code for getters, setters, constructors, tostring.

// Course.java
@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    
    @OneToMany(
      cascade = CascadeType.ALL,
      fetch = FetchType.EAGER,
      mappedBy = "course", // we map the connection field in Subject entity
      orphanRemoval = true // will delete Subjects linked
    )
    private List<Subject> subjects;

    private String title;
    private String description;
    @CreatedDate
    @Temporal(TemporalType.DATE)
    private Date date;
}

// Subject.java
@Entity
@Data
public class Subject {
   @Id
   @GeneratedValue(strategy=GenerationType.AUTO)
   private int id;
   
   // Here we complete the connection/relationship to the course
   @ManyToOne
   @JoinColumn(
     name = "course_id",
     nullable = false,
     referencedColumnName = "id", // this `id` is the Course.id
     foreignKey = @ForeignKey(name = "course_subject_fk")
   )
   private Course course;

   private String name;
   private String complexity;
   
}

To summarize the relationships: A Course can have many Subjects, but a Subject can only be assigned to one Course.

EDIT What I did here is to make the relationship bidirectional. Meaning to say, when you fetch a course, it automatically fetches the subjects using the fetch = FetchType.EAGER. You can change the value to FetchType.LAZY if you do not want that behavior. While fetching the Subject will also return the Course. If you also do not want this, you can use the Lombok's @ToString(exclude = "course")

Rye
  • 445
  • 4
  • 15