Explicit delete on JPA relationships

2019-07-12 16:05发布

问题:

I am a bit confused about managing relationship in JPA. basically I have two entities with a One to Many relationship

A configuration can have have a one or many email list associated with it.

@Entity
public class Config {
    @OneToMany(mappedBy="owner",cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    private List<Email> emailReceivers;
}
@Entity
public class Email {
    @ManyToOne
    private Config owner;
}

In an EJB and during update/merge operation wherein I would edit the list of emails associated with a configuration, I thought that I dont need to explicitly call the delete operation on my email Entity and I would just manage the relationship by deleting the email in my configuration email list.

@Stateless
public class ConfigFacadeImpl implements ConfigFacade{
    @EJB
    private ConfigDao configDao;
    @EJB
    private EmailDao emailDao;

    @Override
    public void update(Config Config, List<Email> emailsForDelete) {
        if(emailsForDelete!=null && emailsForDelete.size() > 0){
            for(Email emailTemp: emailsForDelete){
                Email email = emailDao.find(emailTemp.getId());
                emailDao.delete(email);  // Do I need to explicitly call the remove??
                config.getEmailReceivers().remove(email);
            }
        }
        configDao.update(config);
    }
}

If I don't execute the delete and only remove it from the list, it wont erase my table row.

The UI and the database is now not in sync as the UI would not show the email(s) that I have deleted but when you check the database, the row(s) are still there.

Is it required? I thought JPA would handle this for me if I would just remove it in my entities.

UPDATE

I have tweaked my code to get the entity from the database first before making any changes but still it is not deleting my child email entities. I wonder if this is an apache derby issues. (This is the correct way right as I am passing my entities from my JSF managed bean into my EJB so I need to get the sync from the DB first.)

@Override
public void update(Config config, List<Email> emailsForDelete) {
    Config configTemp = configDao.find(config.getId());
    if(emailsForDelete!=null && emailsForDelete.size() > 0){
        for(Email emailTemp: emailsForDelete){
            configTemp.getEmailReceivers().remove(emailTemp);
        }
    }
    configDao.update(config);
}

回答1:

Since you have already defined cascade type = CascadeType.ALL, JPA should take care of the deletion. Explicit Delete statement is not required.

These two statements are not required:

     Email email = emailDao.find(emailTemp.getId());
     emailDao.delete(email);  // Do I need to explicitly call the remove??

Instead, you may want to just find the matching emailReceiver in config.getEmailReceivers() and remove the matching EmailReceivers as you are doing. There is no need to load the Email entity from the database.

EDIT: To delete orphan objects, you may want to include CascadeType.DELETE_ORPHAN cascade attribute along with CascadeType.ALL.



回答2:

This is the same issue as in Why merging is not cascaded on a one to many relationship

Basically, JPA can only cascade over entities in your collection. So changes to child objects removed from the collection are never putinto the context, and so can't be pushed to the database. In this case, the oneToMany is controlled by the manytones back pointer, so even collection changes won't show up unless the child is also merged. Once a child is pruned from the tree, it needs to be managed and merged individually for changes to it to be picked up.



回答3:

With JPA 2.0, you can use the option orphanRemoval=true in parent entity

Example:

@Entity
public class Parent {
    ...

    @OneToMany(mappedBy="parentId",cascade=CascadeType.ALL, orphanRemoval=true)
    private List<Child> childList;
    ...

}