3

Say you have a unidirectional one-to-many association from class A to class B, like this:

public class A {
    @OneToMany(cascade = CascadeType.ALL)
    private List<B> myBs;
}

public class B {
    //I know nothing about A
}

In the database, these are connected through a third table, keeping their id's.

Now, I would like to delete a B-object, that is connected to an A. A has its own repository class, and B has its own repository class.

The way this has been done in similar settings in my project, is to first ask the A in question to remove the B in question, and then telling the EnitityManager to remove the B from the database.

This makes me a bit stuck between two choices, where neither of them are optimal in my mind:

  1. The repository method in BRepository handles both the removal of B from the A its connected to, and the removal from the database through the EntityManager. I don't like this, because then B's repository-class will have to manipulate A-objects.

  2. The repository method in BRepository only deals with removal through the EntityManager, leaving it up to the caller to remove it from A's collection. I like this even less, because if someone were to call the repository without first removing the B from A's collection, it will fail horribly.

Of the two I find the first to be by far the best. But still, I don't really think that it's clean.

Is there some construct in Hibernate that allows for the item to be deleted to be removed from any collection it is part of, upon removal from the database? (Trying to just delete the B fails because the A containing it is also loaded in the same transaction, so it fails upon the end of the transaction, when it's trying to store something that is deleted.)

(Would adding mappedBy on the @OneToMany-mapping in A solve the problem?)

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Tobb
  • 11,850
  • 6
  • 52
  • 77

3 Answers3

3

I've found a way to solve this, through the use of orphan deletion.

By changing the mapping in A to this:

public class A {
   @OneToMany(cascade = CascadeType.ALL)
   @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
   private List<B> myBs;
}

I can just say

a.getMyBs().remove(b);

and b will be deleted upon a being persisted.

Tobb
  • 11,850
  • 6
  • 52
  • 77
2

Step outside of the Hibernate mind set and ask yourself: Who owns Bs?

Owners are ultimately responsible for their property. Usually, this is true when there can't be a B without an A and each B is owned by exactly one A.

So if the As own the Bs, ARepository is responsible to clean the Bs up. I would create a method in ARepository that does the cleanup and which calls the delete method in BRepository.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • I like the idea, but in order to implement this, I will need a public method in BRepository that removes a given B. And then, I'll have no control of who will call this method, and no guarantee that the B to be deleted is not part of a relationship with an A. So by implementing it this way, the removal of B is not necessarily an atomic operation - it depends on the caller. – Tobb Sep 11 '12 at 13:40
  • Which is the correct way: Only the caller can know whether this is part of a bigger operation (involving an `A`) or a unit test for `BRepository` – Aaron Digulla Sep 11 '12 at 14:19
  • I disagree, deleting a B should always require it to be removed from any A's that its part of. And doing it this way gives no guarantee that it will. Since every B is connected to an A (might have forgotten to say that in the original question ;) ), the correct way would be to treat the removal of a B from any collection it is part of, and the removal of a B from the database as an atomic operation. And your suggestion does not do that. Also, altering code just in order to get it to comply with a unit-test is not that good, is it? – Tobb Sep 11 '12 at 14:27
  • How does the database look? Is there a FK column in B which contains the PK of A? – Aaron Digulla Sep 11 '12 at 15:27
  • No, for some reason it's a join-table.. Not necessary for a many-to-one, I know, so I might change it to what you are describing. Figured it out btw (added an answer myself), but thanks anyways :) – Tobb Sep 11 '12 at 15:34
0

If you truly want to keep the repositories separate the other option is to create a service class on top that is aware of both and will handle removals for you in a similar manner to your option 1 approach.

(Would adding mappedBy on the @OneToMany-mapping in A solve the problem?)

I think this depends on he repository implementation. For JPA EntityManager if the session is closed A will be detached when you delete B even if it is a bi-directional mapping.

Link19
  • 586
  • 1
  • 18
  • 47
  • That is similar to how things look today (it's actually done in the controller), but with this solution there is also no guarantee that noone just skips the Service and calls the repository-method directly, and then we're back to square 1. – Tobb Sep 11 '12 at 13:49
  • If your B's aren't aware of A does that mean there's a context within the application in which B's are used without even knowing that A's exist? And somewhere in another part of the application there's something dealing with A's that would like the A's it knows about to have their collections manipulated as the B deleter is churning away? – Link19 Sep 11 '12 at 13:51
  • It doesn't necessarily need to be a bi-directional mapping, just a change to how the relationship is implemented in the underlying database. So by adding mappedBy, the relationship between the A and B disappears when B is deleted (in the database that is..). But then, it might cause problems when the detached A, still containing a reference to a deleted B, is persisted upon the end of the transaction. – Tobb Sep 11 '12 at 13:51
  • mappedBy is a way of having a reference to A in B without changing the underlying database? – Link19 Sep 11 '12 at 13:54
  • Glen: Bs are always used in combination with As. All Bs are connected to an A. Not sure i understand the second question. mappedBy in A would require a new column in B, wouldn't it? So it would involve changes to the database.. – Tobb Sep 11 '12 at 13:54
  • Sorry, it's just my understanding that if you removed B from the collection in A and saved A the B would get deleted anyway. So I assumed that you had some need to deal with B's specifically. If you just want to always remove B's from A don't allow B's to be deleted directly at all. – Link19 Sep 11 '12 at 14:23
  • It would not, but it will be after adding CascadeType.DELETE_ORPHAN in A :) – Tobb Sep 11 '12 at 14:36