Save child objects automatically using JPA Hibernate
Asked Answered
O

10

95

I have a one-to-many relation between Parent and Child table. In the parent object I have a

List<Child> setChildren(List<Child> childs)

I also have a foreign key in the Child table. This foreign key is an ID that references a Parent row in database. So in my database configuration this foreign key can not be NULL. Also this foreign key is the primary key in the Parent table.

So my question is how I can automatically save the children objects by doing something like this:

session.save(parent);

I tried the above but I'm getting a database error complaining that the foreign key field in the Child table can not be NULL. Is there a way to tell JPA to automatically set this foreign key into the Child object so it can automatically save children objects?

Omari answered 13/10, 2010 at 19:4 Comment(2)
Show us the mapping, the code, and the original error.Jolty
Does this answer your question? hibernate and mappedBy: Is it possible to automatically set foreign key without setting bidirectional relationship among objects?Hybris
D
107

I tried the above but I'm getting a database error complaining that the foreign key field in the Child table can not be NULL. Is there a way to tell JPA to automatically set this foreign key into the Child object so it can automatically save children objects?

Well, there are two things here.

First, you need to cascade the save operation (but my understanding is that you are doing this or you wouldn't get a FK constraint violation during inserts in the "child" table)

Second, you probably have a bidirectional association and I think that you're not setting "both sides of the link" correctly. You are supposed to do something like this:

Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);

List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChildren(children);

session.save(parent);

A common pattern is to use link management methods:

@Entity
public class Parent {
    @Id private Long id;

    @OneToMany(mappedBy="parent")
    private List<Child> children = new ArrayList<Child>();

    ...

    protected void setChildren(List<Child> children) {
        this.children = children;
    }

    public void addToChildren(Child child) {
        child.setParent(this);
        this.children.add(child);
    }
}

And the code becomes:

Parent parent = new Parent();
...
Child c1 = new Child();
...

parent.addToChildren(c1);

session.save(parent);
References
Dd answered 14/10, 2010 at 21:48 Comment(7)
Yeah my problem was that I actually wanted a uni-directional mapping, but instead had something like a bi-directional mapping. Problem was that I had the foreign key mapped in the Child table. So I removed it from Child table and it worked. I used @OneToMany and @JoinColumn in the parent table. Thanks.Omari
@pascal Great explanation ! Thanks Pascal. I understand that while setting from the Parent side you need to do the above. But if we set from the child side, is the following sufficient child.setParent(parent) ?Sheepskin
Yeah, I call this the "reciprocal" relationship...in my code. Another way to handle it is... just before a .save (at jpa layer) is.. "ensure reciprocal" via a last ditch check.Howzell
private Collection<MyParentJpaEntity> ensureReciprocal(Collection<MyParentJpaEntity> inputItems) { Collection<MyParentJpaEntity> returnItems = null; if (null != inputItems) { returnItems = inputItems;Howzell
returnItems.parallelStream().forEach((par) -> { if (null != par.getMyChilds()) { for (MyChildJpaEntity chd : par.getMyChilds()) { if (null == detail.getParentMyParentJpaEntity()) { /* jpa does not like empty "reciprocal" relationship. missing reciprocal will sometimes show up as no surrogate-key-persist on the child entity */Howzell
chd.setParentMyParentJpaEntity(par); } } } }); } return returnItems; }Howzell
So between my non code comments is code you can consider (3 comments total, i kept hitting max character limit)Howzell
J
42

I believe you need to set the cascade option in your mapping via xml/annotation. Refer to Hibernate reference example here.

In case you are using annotation, you need to do something like this,

@OneToMany(cascade = CascadeType.PERSIST) // Other options are CascadeType.ALL, CascadeType.UPDATE etc..
Jolty answered 14/10, 2010 at 4:21 Comment(5)
True, default cascade behavior is just NOTHING.Menhir
I think it should be @OneToMany(cascade = CascadeType.ALL), insert does not exist!Lazuli
newnoise: I'm not in-touch with Hibernate these days. But that time, it was one of the option available.Jolty
CascadeType.INSERT is replaced by CascadeType.PERSIST.Jolty
I tried to use CascadeType.PERSIST. It didn't work. Replacing it with CascadeType.ALL fixed the problem.Mighell
A
5

Following program describe how bidirectional relation work in hibernate.

When parent will save its list of child object will be auto save.

On Parent side:

    @Entity
    @Table(name="clients")
    public class Clients implements Serializable  {

         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)     
         @OneToMany(mappedBy="clients", cascade=CascadeType.ALL)
          List<SmsNumbers> smsNumbers;
    }

And put the following annotation on the child side:

  @Entity
  @Table(name="smsnumbers")
  public class SmsNumbers implements Serializable {

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     int id;
     String number;
     String status;
     Date reg_date;
     @ManyToOne
     @JoinColumn(name = "client_id")
     private Clients clients;

    // and getter setter.

 }

Main class:

 public static void main(String arr[])
 {
    Session session = HibernateUtil.openSession();
      //getting transaction object from session object
    session.beginTransaction();

    Clients cl=new Clients("Murali", "1010101010");
    SmsNumbers sms1=new SmsNumbers("99999", "Active", cl);
    SmsNumbers sms2=new SmsNumbers("88888", "InActive", cl);
    SmsNumbers sms3=new SmsNumbers("77777", "Active", cl);
    List<SmsNumbers> lstSmsNumbers=new ArrayList<SmsNumbers>();
    lstSmsNumbers.add(sms1);
    lstSmsNumbers.add(sms2);
    lstSmsNumbers.add(sms3);
    cl.setSmsNumbers(lstSmsNumbers);
    session.saveOrUpdate(cl);
    session.getTransaction().commit(); 
    session.close();    

 }
Aenneea answered 8/8, 2017 at 16:16 Comment(3)
Please elaborate your answer so that asker know how and why your answer works.Condorcet
It will insert 1 client and then 3 SMS Numbers and after that it will update fk of 3 SMS numbers. This is not good for performace. Use LinkManagement in BidrectionalEarvin
A list of entities cannot be the @Id.Morehead
C
3

in your setChilds, you might want to try looping thru the list and doing something like

child.parent = this;

you also should set up the cascade on the parent to the appropriate values.

Compost answered 13/10, 2010 at 19:10 Comment(0)
O
3

Here are the ways to assign parent object in child object of Bi-directional relations ?

Suppose you have a relation say One-To-Many,then for each parent object,a set of child object exists. In bi-directional relations,each child object will have reference to its parent.

eg : Each Department will have list of Employees and each Employee is part of some department.This is called Bi directional relations.

To achieve this, one way is to assign parent in child object while persisting parent object

Parent parent = new Parent();
...
Child c1 = new Child();
...
c1.setParent(parent);

List<Child> children = new ArrayList<Child>();
children.add(c1);
parent.setChilds(children);

session.save(parent);

Other way is, you can do using hibernate Intercepter,this way helps you not to write above code for all models.

Hibernate interceptor provide apis to do your own work before perform any DB operation.Likewise onSave of object, we can assign parent object in child objects using reflection.

public class CustomEntityInterceptor extends EmptyInterceptor {

    @Override
    public boolean onSave(
            final Object entity, final Serializable id, final Object[] state, final String[] propertyNames,
            final Type[] types) {
        if (types != null) {
            for (int i = 0; i < types.length; i++) {
                if (types[i].isCollectionType()) {
                    String propertyName = propertyNames[i];
                    propertyName = propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1);
                    try {
                        Method method = entity.getClass().getMethod("get" + propertyName);
                        List<Object> objectList = (List<Object>) method.invoke(entity);

                        if (objectList != null) {
                            for (Object object : objectList) {
                                String entityName = entity.getClass().getSimpleName();
                                Method eachMethod = object.getClass().getMethod("set" + entityName, entity.getClass());
                                eachMethod.invoke(object, entity);
                            }
                        }

                    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return true;
    }

}

And you can register Intercepter to configuration as

new Configuration().setInterceptor( new CustomEntityInterceptor() );
Olnek answered 9/9, 2016 at 9:13 Comment(1)
can u please share the code of saving it in configurationRetina
T
1

In JPA @*To* relationships both parent and child entities must be cross assigned before (parent) saving.

Tricia answered 3/9, 2021 at 14:40 Comment(0)
P
0

Use org.hibernate.annotations for doing Cascade , if the hibernate and JPA are used together , its somehow complaining on saving the child objects.

Pillow answered 3/3, 2017 at 6:30 Comment(0)
H
0

In short set cascade type to all , will do a job; For an example in your model. Add Code like this . @OneToMany(mappedBy = "receipt", cascade=CascadeType.ALL) private List saleSet;

Hippolytus answered 20/3, 2018 at 12:57 Comment(0)
P
0

If you do not have bidirectional relationship and want to only save/update the the single column in the child table, then you can create JPA repository with child Entity and call save/saveAll or update method.

Note: if you come across FK violations then it means your postman request having primary and foreign key ids is not matching with generated ids in child table , check the ids in your request and child table which your are going to update(they should match/if they don't means you get FK violations) whatever ids generated while saving the parent and child in before transactions, those ids should match in your second call when you try to update the single column in your child table.

Parent:

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

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)  
     private UUID customerId ;
     
     @OneToMany(cascade = CascadeType.ALL) 
     @JoinColumn(name ="child_columnName", referencedColumnName= 
                "parent_columnName")
     List<Accounts> accountList;
}

Child :

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

         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)  
         private UUID accountid;
         
        
    }
Phosphocreatine answered 15/7, 2021 at 19:40 Comment(0)
W
0

I saw very useful tip for assigning parent to any child in bidirectional connections. If you don't want to use child.setParent(parent) every time, you can create method in your parent entity with annotation @PrePersist which is one of JPA Entity Lifecycle Events and there you can define this.children.forEach(child -> child.setParent(this)); which will create this required connection between parent and children before persisting it in db.

Wayward answered 13/5, 2023 at 10:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.