2

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);
}
Mark Estrada
  • 9,013
  • 37
  • 119
  • 186

3 Answers3

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.

Yogendra Singh
  • 33,927
  • 6
  • 63
  • 73
  • What do you mean by this? I am not actually removing all of my emailReceivers.. Only some items that were deleted from my JSF UI.. If I comment out the emailDao.delete, it wont delete the entries in my email table – Mark Estrada Oct 31 '12 at 02:55
  • @MarkEstrada NO. `CascadeType.ALL` is equivalent to cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}. On save, it will delete only those records which you remove from the collection e.g. if your config as 5 email receivers and you remove 2 of them and then save config; it will delete only 2 not all 5. – Yogendra Singh Oct 31 '12 at 02:58
  • This is my understanding also as I am just reading and following online tutorials... I am just not sure why my code still isn't deleting my email list if I remove the line of code that deletes it explicitly. – Mark Estrada Oct 31 '12 at 03:18
  • @MarkEstrada Please refer my updated answer. I am just suspecting, that its really not removing the element from the list because of the way your are removing them. This you can verify by printing/logging the `emailReceivers` size before and after your remove calls. – Yogendra Singh Oct 31 '12 at 03:21
  • I have updated my update method also. During update, I sync frist my config table from my database and then perform the delete. Before delete, I have five...after the for loop, it now has 3... but upon calling merge, it is not deleting the two row in the database.. – Mark Estrada Oct 31 '12 at 03:24
  • @MarkEstrada Why are you calling merge? Please call `persist()`. If still issues, please share `configDao.update(config);` method code. – Yogendra Singh Oct 31 '12 at 03:27
  • It came from GenericDao and uses the public T update(T entity) { return em.merge(entity); } – Mark Estrada Oct 31 '12 at 03:29
  • I turned on logging and found out that when I call em.merge, it does not call or execute any SQL into the database. What could be the reason behind this? – Mark Estrada Oct 31 '12 at 06:05
  • @MarkEstrada It is not recognizing the removed elements. Once question: Are you trying to remove the elements in the object loaded through JPA? JPA keeps the track of addition/removal through some specific internal arrays. If you perform the operation on plain Entity Objects which are not attached to the session, I think it doesn't perform the comparison and hence doesn't know. I would advice you do the load of parent object(leave the object attached to the session), removal of child objects and merge in the same session. I am hopeful, that should work. – Yogendra Singh Oct 31 '12 at 16:01
  • Hi, I found out about the orphanRemoval attribute. Setting it to true solved my problem.. Thanks for your lead... – Mark Estrada Nov 05 '12 at 08:27
  • @MarkEstrada: You mean `CascadeType.DELETE_ORPHAN`? I will include this in the answer for other's help. – Yogendra Singh Nov 05 '12 at 13:48
1

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.

Community
  • 1
  • 1
Chris
  • 20,138
  • 2
  • 29
  • 43
1

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;
    ...

}
KoZto
  • 11
  • 2