0

I'd like to insert data which I get from the @RequestBody into a mariaDB database. Currently I'm able to store data without a Foreign Key without any problems, but now I need to store also Foreign Keys and I don't know how to do that.

This is how my entities looks like now:

Superclass:

package webtoolbackend.Model.Superclass;

import webtoolbackend.Model.Reference.Reference_Category;

import javax.persistence.*;

@MappedSuperclass
public abstract class Category {
private long category_ID;
private String title;
private String description;

public Category() {

}

public Category(long category_ID, String title, String description) {
    this.category_ID = category_ID;
    this.title = title;
    this.description = description;
}

public Category(String title, String description) {
    this.title = title;
    this.description = description;
}

@Id
@Column(name ="Category_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
public long getCategory_ID() {
    return category_ID;
}

public void setCategory_ID(long category_ID) {
    this.category_ID = category_ID;
}

@Column(name ="Title")
public String getTitle() {
    return title;
}

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

@Column(name ="Description")
public String getDescription() {
    return description;
}

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

Category:

package webtoolbackend.Model.DE;

import webtoolbackend.Model.Reference.Reference_Category;
import webtoolbackend.Model.Superclass.Category;

import javax.persistence.*;
import java.util.Set;

@Entity
@Table(name="Category_DE")
public class Category_DE extends Category {
private Sector_DE sector_de;
private Set<Question_DE> questions_de;
private Set<Reference_Category> reference_categories;
private long sector_id;

public Category_DE() {
    super();
}

public Category_DE(long category_ID, String title, String description) {
    super(category_ID, title, description);
}
public Category_DE(String title, String description, Sector_DE sector_de) {
    super(title, description);
    this.sector_de = sector_de;
}

public Category_DE(String title, String description, long sector_id) {
    super(title, description);
    this.sector_id = sector_id;
}

@ManyToOne
@JoinColumn(name="Sector_IDFS")
public Sector_DE getSector_de(){
    return sector_de;
}

public void setSector_de(Sector_DE sector_de) {
    this.sector_de = sector_de;
}

@OneToMany(mappedBy="category_de", cascade=CascadeType.ALL)
public Set<Question_DE> getQuestions_de() {
    return questions_de;
}

public void setQuestions_de(Set<Question_DE> questions_de) {
    this.questions_de = questions_de;
}

@OneToMany(mappedBy="category_de", cascade = CascadeType.ALL)
public Set<Reference_Category> getReference_categories() {
    return reference_categories;
}

public void setReference_categories(Set<Reference_Category> reference_categories) {
    this.reference_categories = reference_categories;
}
}

My Controller:

@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api")
public class Category_Controller {
@Autowired
Category_Repository category_repository;

@PostMapping("/category-de")
public ResponseEntity<Category> createCategory(@RequestBody Category_DE category_de) {
    try{
        Category_DE savedCategory = category_repository
                .save(new Category_DE(category_de.getTitle(), category_de.getDescription(), category_de.getSector_de()));
        return new ResponseEntity<>(savedCategory, HttpStatus.CREATED);
    }
    catch (Exception e){
        return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
}

My JSON from the RequestBody:

{
"title": "test",
"description": "testDesc",
"sector_de": {
    "sector_id": 1,
    "title": "test"
}
}
swissmount
  • 5
  • 2
  • 7

1 Answers1

0

Since you are using same entity object as @RequestBody, you need to consider few things to save them into database. Let's take a look one by one

When both Category and Sector are new objects1

This approach will work when you are expecting the API to create new object(s) every time, since it is easy to cascade the changes to child tables through JPA using mapping. So suppose your request was something like this

{
   "title": "test",
   "description": "testDesc",
   "sector_de": {
     "title": "test"
   }
}

Here both object (main and inner) object missing the id field, so it's safe to save it to database directly and both tables will receive entry if we use this on parent. Since, json will be serialised to Category object and if setter was defined it will also have the parent Sector_DE object initialised.

@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinColumn(name="Sector_IDFS")
public Sector_DE getSector_de(){
    return sector_de;
}

So, spring JPA will save the Category object and due to cascade it will also save the Sector_DE object as well. Your request is not similar to this one, so this will not apply to this case but I mentioned it because this info will be useful in your case.

When Category is new and Sector is existing (your case)

Now, your request has sector_id provided

{
   "title": "test",
   "description": "testDesc",
   "sector_de": {
     "sector_id" : 1,
     "title": "test"
   }
}

So your Category request object will have Sector_DE object initialised with sector_id field and if you try to save it directly as per the code above, spring data JPA may give you error about detached object.

Reason for that is that, the persistence context may already have that object and since your object is created from outside and not fetched from repository, persistence context do not know about it. It may work if you don't have any Sector with same id in database.

So this is kind of update request, where you are updating/adding category to existing sector, therefore you need to fetch the related object from db for that sector_id.

In this case you would have to first check if there is a sector with same id, to do this you would need a SectorRepository similar to the CategoryRepostiory then you would check it like this

public ResponseEntity<Category> createCategory(@RequestBody Category_DE category_de) {
    try{
           //assuming SectorRepository available to this function
           Sector_DE sector = 
                 sectorRepository.findById(category_de.getSector().getSectorId())
                                 .orElseThrow(() -> new IllegalArgumentException());
         
            category_de.setSector_de(sector); //Important! will save foriegn key to table
            Category_DE savedCategory = category_repository
                .save(new Category_DE(category_de.getTitle(), category_de.getDescription(), category_de.getSector_de()));
        return new ResponseEntity<>(savedCategory, HttpStatus.CREATED);
    }
    catch (Exception e){
        return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Since, here we are operating on child, we don't need to cascade the changes to parent, unless you want to update some properties of parent (Sector_DE) as well, so, your existing mapping is fine.

@ManyToOne
@JoinColumn(name="Sector_IDFS")
public Sector_DE getSector_de(){
    return sector_de;
}

Footnotes


1Although, I mentioned the first case to show how mapping work in case both are new objects, but generally we don't create new objects from child side this way, we should always save the parent first and then add child to it. So it's a two step process, cascading changes from child to parent should be avoided.
code_mechanic
  • 1,097
  • 10
  • 16
  • But I had to add `@JsonBackReference` and `@JsonManagedReference` to the getters of the lists, to avoid a stack overflow error. This [Link](https://stackoverflow.com/questions/47693110/could-not-write-json-infinite-recursion-stackoverflowerror-nested-exception) maybe helps – swissmount Apr 07 '21 at 10:15
  • Yes, if you are returning entities as response, you could have also put `@JsonIgnore` on those getters, otherwise the it will be recursive, but that was not relevant to this question. – code_mechanic Apr 07 '21 at 10:19