4

I am new to exploring spring boot and hibernate and facing a certain issue which i believe is not new. However with all the suggestions in place, I still could not find a way to resolve the problem that I am currently facing.

Can anyone of you please point where I am going wrong?

Following is the scenario -

I have a Category class and each instance of the category class can have many instances of sub-categories.

I have setup the relationship using @OneToMany annotation. However when trying to save records to the database, I am facing the org.hibernate.exception.ConstraintViolationException with exception reported saying foreign key value cannot be NULL.

Please find below the class declarations

Category.class

import java.util.Date;
import java.util.Set;


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

import org.hibernate.annotations.Cascade;
import org.springframework.context.annotation.Scope;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Table(name="Category")
@Scope("session")
@EntityListeners(AuditingEntityListener.class)
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Category_Id")
    private Long Id;

    private String CategoryName;

    private String CategoryValue;

    @Column(name = "IsActive", columnDefinition = "BOOLEAN")
    private Boolean IsActive;

//  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "category")
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "category")
//  @JoinTable(name = "Category_SubCategory", joinColumns = { @JoinColumn(name = "Category_Id") }, inverseJoinColumns = { @JoinColumn(name = "Sub_Category_Id") })
    private Set<SubCategory> SubCategories;

    @CreatedBy
    @Column(nullable = false, updatable = false)
    private String CreatedBy;

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private Date CreatedDate;

    @LastModifiedBy
    @Column(nullable = false)
    private String ModifiedBy;

    @LastModifiedDate
    @Column(nullable = false)
    private Date ModifiedDate;

    public Long getId() {
        return Id;
    }

    public void setId(Long id) {
        Id = id;
    }

    public String getCategoryName() {
        return CategoryName;
    }

    public void setCategoryName(String categoryName) {
        CategoryName = categoryName;
    }

    public String getCategoryValue() {
        return CategoryValue;
    }

    public void setCategoryValue(String categoryValue) {
        CategoryValue = categoryValue;
    }

    public Boolean getIsActive() {
        return IsActive;
    }

    public void setIsActive(Boolean isActive) {
        IsActive = isActive;
    }

    public Set<SubCategory> getSubCategories() {
        return SubCategories;
    }

    public void setSubCategories(Set<SubCategory> subCategories) {
        SubCategories = subCategories;
    }

    public String getCreatedBy() {
        return CreatedBy;
    }

    public void setCreatedBy(String createdBy) {
        CreatedBy = createdBy;
    }

    public Date getCreatedDate() {
        return CreatedDate;
    }

    public void setCreatedDate(Date createdDate) {
        CreatedDate = createdDate;
    }

    public String getModifiedBy() {
        return ModifiedBy;
    }

    public void setModifiedBy(String modifiedBy) {
        ModifiedBy = modifiedBy;
    }

    public Date getModifiedDate() {
        return ModifiedDate;
    }

    public void setModifiedDate(Date modifiedDate) {
        ModifiedDate = modifiedDate;
    }

}

SubCategory.class

import java.util.Date;

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

import org.springframework.context.annotation.Scope;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@Entity
@Table(name="SubCategory")
@Scope("session")
@EntityListeners(AuditingEntityListener.class)
public class SubCategory {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Sub_Category_Id")
    private Long Id;

    private String SubCategoryName;

    private String SubCategoryValue;

    @Column(name = "IsActive", columnDefinition = "BOOLEAN")
    private Boolean IsActive;

    @ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name = "Category_Id", nullable = false)
    private Category category;

    @CreatedBy
    @Column(nullable = false, updatable = false)
    private String CreatedBy;

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private Date CreatedDate;

    @LastModifiedBy
    @Column(nullable = false)
    private String ModifiedBy;

    @LastModifiedDate
    @Column(nullable = false)
    private Date ModifiedDate;

    public Long getId() {
        return Id;
    }

    public void setId(Long id) {
        Id = id;
    }

    public String getSubCategoryName() {
        return SubCategoryName;
    }

    public void setSubCategoryName(String subCategoryName) {
        SubCategoryName = subCategoryName;
    }

    public String getSubCategoryValue() {
        return SubCategoryValue;
    }

    public void setSubCategoryValue(String subCategoryValue) {
        SubCategoryValue = subCategoryValue;
    }

    public Boolean getIsActive() {
        return IsActive;
    }

    public void setIsActive(Boolean isActive) {
        IsActive = isActive;
    }

    public String getCreatedBy() {
        return CreatedBy;
    }

    public void setCreatedBy(String createdBy) {
        CreatedBy = createdBy;
    }

    public Date getCreatedDate() {
        return CreatedDate;
    }

    public void setCreatedDate(Date createdDate) {
        CreatedDate = createdDate;
    }

    public String getModifiedBy() {
        return ModifiedBy;
    }

    public void setModifiedBy(String modifiedBy) {
        ModifiedBy = modifiedBy;
    }

    public Date getModifiedDate() {
        return ModifiedDate;
    }

    public void setModifiedDate(Date modifiedDate) {
        ModifiedDate = modifiedDate;
    }

    public Category getCategory() {
        return category;
    }

    public void setCategory(Category category) {
        this.category = category;
    }



}

ServiceImpl -

@Override
    public void save(Category category) {

        Set<SubCategory> subCategoryRec = category.getSubCategories();
        if(subCategoryRec != null && subCategoryRec.size() > 0) {
            for(SubCategory rec: subCategoryRec) {
                try {
                    subcategoryRepository.save(rec);
                }catch (Exception ex) {
                    ex.printStackTrace();
                }

            }
        }

        try {
            categoryRepository.save(category);
        } catch (Exception ex) {
            ex.printStackTrace();
        }


    }

Not sure where I am wrong.

The exception reported has the following stacktrace -

Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
        at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
        at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
        at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:207)
        at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
        at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:42)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2855)
        at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3426)
        at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
        at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:619)
        at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:273)
        at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:254)
        at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:299)
        at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:317)
        at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:272)
        at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178)
        at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:109)
        at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
        at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
        at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
        at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
        at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
        at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
        at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
        at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:347)
        at com.sun.proxy.$Proxy113.persist(Unknown Source)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
        at com.sun.proxy.$Proxy113.persist(Unknown Source)
        at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:508)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:513)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:498)
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:475)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
        ... 104 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'category_id' cannot be null
Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
svijay.aug12
  • 531
  • 3
  • 13
  • 32

2 Answers2

2

The reason is you are trying to save a child object before saving parent object. Hence change the implementation as first save parent object followed by child object as shown below.

    categoryRepository.save(category);
    Set<SubCategory> subCategoryRec = category.getSubCategories();
    if(subCategoryRec != null && subCategoryRec.size() > 0) {
        for(SubCategory rec: subCategoryRec) {
            subcategoryRepository.save(rec);  
        }
    }
Tharsan Sivakumar
  • 6,351
  • 3
  • 19
  • 28
  • Hi, Thank you for your response. That was a horrible mistake from my side. Now when I try to execute it, the records are saved in the master and child. However the foreign key in the child is still NULL and does not reference the master. Is there anything that I am missing here? – svijay.aug12 Apr 04 '18 at 08:56
  • In your service class (public void save(Category category)) when you pass category object everything should be set. If it''s not set, then set category of subcategory as subCategory.setCategory(category) before save – Tharsan Sivakumar Apr 04 '18 at 10:09
0

Well, actually you should not do this manually. For this exact problem you should use CASCADING

From here:

In cascade, after one operation (save, update and delete) is done, it decide whether it need to call other operations (save, update and delete) on another entities which has relationship with each other.

So this should basically solve your problem:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade=CascadeType.PERSIST)
private Set<SubCategory> SubCategories;

You can choose between several Cascade-Types, if you want a different behaviour for DELETE etc.:

The cascade types supported by the Java Persistence Architecture are as below:

CascadeType.PERSIST : means that save() or persist() operations cascade to related entities. CascadeType.MERGE : means that related entities are merged when the owning entity is merged.

CascadeType.REFRESH : does the same thing for the refresh() operation. CascadeType.REMOVE : removes all related entities association with this setting when the owning entity is deleted.

CascadeType.DETACH : detaches all related entities if a “manual detach” occurs. CascadeType.ALL : is shorthand for all of the above cascade operations.

See here.

EDIT: (Referencing the other proposed answer): Calling .save will not automatically commit the transaction, try .saveAndFlush if you really want to do it manually, but i would recommend using Cascade. See: Difference between save and saveAndFlush in Spring data jpa