Search code examples
javaspringspring-bootjpaspring-data-jpa

Spring Data Jpa ManyToMany Relation How To Disable Two Directional Insertion


here is the my model, i ve subCategories in main abject, when i try to insert new record, it being two directional insertion defaultly, i want to disable it, i need one way insertion, is it possible?

model

package com.eck.data.api.contract.model.produce;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

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.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import com.eck.data.api.contract.model.site.SiteModel;
import com.fasterxml.jackson.annotation.JsonIgnore;

@Entity
@Table(name = "produceCategory", uniqueConstraints = {
    @UniqueConstraint(name = "UniqueProduceCategoryModelName", columnNames = { "name" }) })
public class ProduceCategoryModel implements Serializable {

    private static final long serialVersionUID = 6532245350339799470L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

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

    @Column(name = "creationDate")
    private Date creationDate;

    @Column(name = "lastModification")
    private Date lastModification;

    @Column(name = "active", columnDefinition = "boolean default false")
    private boolean active;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "produceCategory")
    private List<ProduceCategoryDescriptionModel> descriptions;

    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "produceCategories")
    private List<ProduceModel> produces;

    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
    @JoinTable(name = "subProduceCategories", joinColumns = { @JoinColumn(name = "id") }, inverseJoinColumns = {
        @JoinColumn(name = "subProduceCategoryId") }, uniqueConstraints = @UniqueConstraint(name = "UniqueSubProduceCategoryId", columnNames = {
            "id", "subProduceCategoryId" }))
    private List<ProduceCategoryModel> subCategories;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "siteId", nullable = false)
    private SiteModel site;

    public long getId() {
    return id;
    }

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

    public String getName() {
    return name;
    }

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

    public Date getCreationDate() {
    return creationDate;
    }

    public void setCreationDate(Date creationDate) {
    this.creationDate = creationDate;
    }

    public Date getLastModification() {
    return lastModification;
    }

    public void setLastModification(Date lastModification) {
    this.lastModification = lastModification;
    }

    public boolean isActive() {
    return active;
    }

    public void setActive(boolean active) {
    this.active = active;
    }

    public List<ProduceCategoryDescriptionModel> getDescriptions() {
    return descriptions;
    }

    public void setDescriptions(List<ProduceCategoryDescriptionModel> descriptions) {
    this.descriptions = descriptions;
    }

    public List<ProduceModel> getProduces() {
    return produces;
    }

    public void setProduces(List<ProduceModel> produces) {
    this.produces = produces;
    }

    public List<ProduceCategoryModel> getSubCategories() {
    return subCategories;
    }

    public void setSubCategories(List<ProduceCategoryModel> subCategories) {
    this.subCategories = subCategories;
    }

    public SiteModel getSite() {
    return site;
    }

    public void setSite(SiteModel site) {
    this.site = site;
    }

    public void addSubCategory(ProduceCategoryModel category) {
    this.subCategories.add(category);
    category.getSubCategories().add(this);
    }

    public void removeSubCategory(long categoryId) {
    ProduceCategoryModel category = this.subCategories.stream().filter(t -> t.getId() == categoryId).findFirst()
        .orElse(null);
    if (category != null) {
        this.subCategories.remove(category);
        category.getSubCategories().remove(this);
    }
    }

    public void addProduce(ProduceModel produce) {
    this.produces.add(produce);
    produce.getProduceCategories().add(this);
    }

    public void removeProduce(long produceId) {
    ProduceModel produce = this.produces.stream().filter(t -> t.getId() == produceId).findFirst().orElse(null);
    if (produce != null) {
        this.produces.remove(produce);
        produce.getProduceCategories().remove(this);
    }
    }
}

service

    @PostMapping("/admin/insertSubProduceCategoryRelation/{produceCategoryId}/{subProduceCategoryId}")
    public ApiResponseModel insertSubProduceCategoryRelation(
        @PathVariable(value = "produceCategoryId") long produceCategoryId,
        @PathVariable(value = "subProduceCategoryId") long subProduceCategoryId) {

    ApiResponseModel response = new ApiResponseModel();
    try {
        ProduceCategoryModel produceCategoryResponse = produceCategoryService.findById(produceCategoryId)
            .orElse(null);
        if (produceCategoryResponse != null) {
        produceCategoryResponse
            .addSubCategory(produceCategoryService.findById(subProduceCategoryId).orElse(null));
        produceCategoryService.save(produceCategoryResponse);
        response.setSuccess(true);
        } else {
        response.setErrorCode(produceCategoryId + " produceCategoryResponse is null");
        response.setErrorMessage(produceCategoryId + " produceCategoryResponse is null");
        }
    } catch (Exception e) {
        response.setErrorCode(e.getMessage());
        response.setErrorMessage(e.getMessage());
    }

    return response;
    }

when i use service for insertion

produceCategoryId = 68 subProduceCategoryId = 69

i ve 2 record in sub_produce_categories table and operation breaks down beacuse of it, i want 1 record, how can i do that ?

table record details

i want change Two Directional Insertion to Direct Insertion.

answer: i could not do anything with many to many relation so i rewrote the code like this

model

package com.eck.data.api.contract.model.produce;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

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.JoinColumn;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

import com.eck.data.api.contract.model.site.SiteModel;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonManagedReference;

@Entity
@Table(name = "produceCategory", uniqueConstraints = {
    @UniqueConstraint(name = "UniqueProduceCategoryModelName", columnNames = { "name" }) })
public class ProduceCategoryModel implements Serializable {

    private static final long serialVersionUID = 6532245350339799470L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

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

    @Column(name = "creationDate")
    private Date creationDate;

    @Column(name = "lastModification")
    private Date lastModification;

    @Column(name = "active", columnDefinition = "boolean default false")
    private boolean active;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "produceCategory")
    private List<ProduceCategoryDescriptionModel> descriptions;

    @JsonIgnore
    @ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "produceCategories")
    private List<ProduceModel> produces;

    @JsonManagedReference
    @OneToMany(mappedBy = "parentCategory")
    private List<ProduceCategoryModel> childrenCategories;

    @ManyToOne
    @JsonBackReference
    private ProduceCategoryModel parentCategory;

    @JsonIgnore
    @ManyToOne
    @JoinColumn(name = "siteId", nullable = false)
    private SiteModel site;
    
    public long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Date getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(Date creationDate) {
        this.creationDate = creationDate;
    }

    public Date getLastModification() {
        return lastModification;
    }

    public void setLastModification(Date lastModification) {
        this.lastModification = lastModification;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    public List<ProduceCategoryDescriptionModel> getDescriptions() {
        return descriptions;
    }

    public void setDescriptions(List<ProduceCategoryDescriptionModel> descriptions) {
        this.descriptions = descriptions;
    }

    public List<ProduceModel> getProduces() {
        return produces;
    }

    public void setProduces(List<ProduceModel> produces) {
        this.produces = produces;
    }
    public SiteModel getSite() {
        return site;
    }

    public void setSite(SiteModel site) {
        this.site = site;
    }
    
    public List<ProduceCategoryModel> getChildrenCategories() {
        return childrenCategories;
    }

    public void setChildrenCategories(List<ProduceCategoryModel> childrenCategories) {
        this.childrenCategories = childrenCategories;
    }

    public ProduceCategoryModel getParentCategory() {
        return parentCategory;
    }

    public void setParentCategory(ProduceCategoryModel parentCategory) {
        this.parentCategory = parentCategory;
    }

    public void addProduce(ProduceModel produce) {
    this.produces.add(produce);
    produce.getProduceCategories().add(this);
    }

    public void removeProduce(long produceId) {
    ProduceModel produce = this.produces.stream().filter(t -> t.getId() == produceId).findFirst().orElse(null);
    if (produce != null) {
        this.produces.remove(produce);
        produce.getProduceCategories().remove(this);
    }
    }
}

service

package com.eck.data.api.controller.produce.service;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;

import com.eck.data.api.contract.model.produce.ProduceCategoryModel;

public interface IProduceCategoryService extends JpaRepository<ProduceCategoryModel, Long> {

    List<ProduceCategoryModel> findAllBySiteId(long siteId);

    List<ProduceCategoryModel> findAllByParentCategoryId(long parentCategoryId);

    ProduceCategoryModel findByChildrenCategoriesId(long id);

}

now i can manage parent category and children categories.

if any one can handle with change Two Directional Insertion to Direct Insertion with many to many relation please comment the solution.

happy coding !


Solution

  • This is the correct operation and if you want to have a tree format in your DB, you can simply create a "parent_id" column and just save the "parent_id" in the service layer directly and it doesn't need the relationship's tags, just define the column and then save the "sub_produce_category_id" directly in the service layer.

    And if it is so necessary to use the relationship's tags, I suggest you write the code like the below:

    // Fetch the sub-category response
    ProduceCategoryResponse sub = produceCategoryService.findById(subProduceCategoryId).orElse(null);
    
    // Add the sub to the category response
    produceCategoryResponse.addSubCategory(sub);
            
    // Save the category response 
    produceCategoryService.save(produceCategoryResponse);