In a #OneToMany relationship if I want to remove a child, do I need to explicitly delete that child from parent's collection as well or just deleting the child will suffice?
For instance, Person and Phone. Each person has many phone numbers. If I want to delete one phone number from a person is this enough:
EntityManager.remove(phone);
Or I need to to this beforehand:
Person.getPhone().remove(phone);
Not to mention, the CascadeType is set to MERGE.
You need to remove the Phone explicitly from the phones collection, it's not enough to remove it with the EntityManager.
From the other side, it might be sufficient to use orphanRemoval, so if you remove an entity from a collection, it gets automatically deleted. Something like:
#OneToMany(mappedBy="person", orphanRemoval="true")
private List<Phone> phones;
See also: http://docs.oracle.com/cd/E19798-01/821-1841/giqxy/index.html
Cascade.REMOVE only removes the child entity if the parent entity is removed as well. Cascase.MERGE has nothing to do with this problem.
Not sure if MERGE is enough to get entities deleted cascaded, you will probably have to also define DELETE cascading and depending on how the data is mapped (with or without a secondary table in between) it might even be necessary to apply orphan removal too.
If you don't apply cascading for deletion but rather use a JPA query or an entityManager.remove(), then it is certainly a good idea to manually evict it from the oneToMany collection as well. The reason is simple: you may manually remove it from the database, but that doesn't mean it automagically gets removed from the collection too so for the lifetime of the parent entity, it will still be referencing an entity which is not supposed to exist anymore. Things get weird when you then also accidentally change the state of said entity.
Related
I'll simplify the example to get the idea across. My model looks like the following :
Projects have a ManyToMany relationship with Jobs. In order to create the join table, I created a ProjectJob entity that has an added column, "job_order". Project references an OneToMany link to a list of ProjectJob, with CascadeType.ALL.
I have a UniqueConstraint on ProjectJob that looks like this :
#UniqueConstraint(columnNames = {"project_id", "job_order"}
Everything works like a charm unless I want to reorder the jobs in the project. Basically, I flip the orders around in my service and made sure that every ProjectJob was a unique job_order in the list, and then save the Project (since the ProjectJobs will be cascaded). Problems arise when JPA/Hibernate tries to flush the transaction since it tries to update "row by row", and obviously the UniqueConstraint is violated after the first update (even though the whole batch of updates would give a coherent state).
Weirdly enough, the service call happens inside a transaction, so I'm not sure why I get this error.
Is there any way to update the child members of a collection in a unique statement so that the UniqueConstraint gets checked after all the children have been updated, instead of getting triggered after the first one? If not, what solution is available for my use case?
You could try to use deferred constraints. Or you use #ManyToMany #OrderColumn List<Job> jobs in Project instead which handles all of this automatically for you.
Is it possible, when using Hibernate and cascade delete, to remove the cascade-deleted entity from all other entities that have a reference to it?
Take this diagram for example:
User has a MembershipOffer to join a Group. In my model is possible to remove groups with pending offers, and when that happens, I want all the offers of the deleted group to be deleted as well.
I understand that in order to do that, I will need to change the MembershipOffer-Group association to be bidirectional and set the cascade flag on it. However, when MembershipOffer is deleted, what happens to the User's reference to it?
I assume Hibernate won't go through every reference to the deleted object and remove it from appropriate collections. Will I have to delete the references manually, or is there a automatic way to do it?
Note:
I am using a relational database and from purely SQL-based look I don't see a problem. MembershipOffer table contains FK of both User and Group. When Group is deleted, so are all Offers containing its FK. With their removal the link between Offer and User is deleted. The problem I have is that in the code User has a field Set<MembershipOffer> membershipOffers which may contain a reference to the just-deleted Offer (which will be invalid if I try to access it).
I've been using JPA 2.0 for a while but, sad to admit, I haven't had enough time to learn it properly. It seems like I lack the basics of how to work with Entity Manager.
Moving one step at a time, I'd like to first ask you about maintaining relationships between mapped entities. Of course I know how to create mappings between entities, different types of available associations (OneToOne, etc.) and how databases work in general. I'm purely focused on maintaining it via Entity Manager, so please do not send me to any kind of general knowledge tutorial :-).
The questions are:
Am I right that as a programmer I'm responsible for maintaining (creating/updating/removing) relationships between instances of entities?
Do I have to always update (set to null, remove from collection, etc.) instances by hand?
Plain SQL can set entities to NULL on deleting, but it seems like JPA can't do such a simple thing. It also seems like a burden to do it manually. Is there a way to achieve that with JPA?
If I have OneToMany relationship and set to NULL the entity on the Many side of the relationship. Then I persist the changes in a Set by saving the entity on the One side. Do I then have to update the entities in the Many side and set association to NULL in each instance? Seems pure silliness for one-directional bindings!
Thanks in advance!
The main thing you need to investigate is the different options you have when mapping on entity. For example in the next piece of code the cascade all option will instruct jpa to delete the child list when the parent is deleted.
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL }, mappedBy = "parent")
private Set<Child> events = new HashSet<Child>();
Yes. You maintain the object tree and modify it to look like what
you want.
Yes and no. If you want the entity to reference null, then yes.
For instance, if you are removing one Entity, then you should clean
up any references to it held by other entities that you are not
removing. A practical example: its good practice to let an Employee
know his/her Manager has been let go. If the Employee is going to
stay, it should either have its manager reference nulled out or set
to a different manager, before the current manager can be removed.
If the employee is going to be removed as well, then cascade remove
can cascade to all the Manager's subordinates, in which case you do
not need to clean up their references to the manager - as they are
going away too.
I don't quite understand what SQL is setting to null. Deleting
removes the row in the database, so there isn't anything to set to
null. Cleaning up a reference shouldn't be that difficult in the
object model, as JPA has a number of events to help such as
preremove preupdate etc. In the end though, the problem is with
your java objects. They are just java objects, so if you want
something done, your application will need to do it for the most
part. JPA handles building them and pushing them to the database,
not changing the state for you.
Yes and no. If you set up a bidirectional relationship, you must
maintain both sides as mentioned above. If you set the child's
parent reference to null, you should let the parent know it no
longer has a child, wouldn't you? Your parent will continue to
reference its child for as long as that Parent instance exists. So
even though the database is updated/controlled through the side that
owns a relationship, the object model will be out of synch with the
database until it is refreshed or somehow reloaded. JPA allows for
multiple levels of caching, so it all depends on your provider setup
how long that Parent instance will exist referencing a Child that no
longer exists in the database.
I have the following use case: There's a class called Template and with that class I can create instances of the ActualObject class (ActualObject copies its inital data from the Template). The Template class has a list of Product:s.
Now here comes the tricky part, the user should be able to delete Products from the database but these deletions may not affect the content of a Template. In other words, even if a Product is deleted, the Template should still have access to it. This could be solved by adding a flag "deleted" to the Product. If a Product is deleted, then it may not be searched explicitly from the database, but it can be fetched implicitly (for example via the reference in the Template class).
The idea behind this is that when an ActualObject is created from a template, the user is notified in the user interface that "The Template X had a Product Z with the parameters A, B and C, but this product has been deleted and cannot be added as such in ActualObject Z".
My problem is how I should mark these deleted objects as deleted. Before someone suggests that just update the delete flag instead of doing an actual delete query, my problem is not that simple. The delete flag and its behaviour should exist in all POJOs, not just in Product. This means I'll be getting cascade problems. For example, if I delete a Template, then the Products should also be deleted and each Product has a reference to a Price-object which also should be deleted and each Price may have a reference to a VAT-object and so forth. All these cascaded objects should be marked as deleted.
My question is how can I accomplish this in a sensible manner. Going through every object (which are being deleted) checking each field for references which should be deleted, going through their references etc is quite laborious and bugs are easy to slip in.
I'm using Hibernate, I was wondering if Hibernate would have any such inbuilt features. Another idea that I came to think of was to use hibernate interceptors to modify an actual SQL delete query to an update query (I'm not even 100% sure this is possible). My only concern is that does Hibernate rely on cascades in foreign keys, in other words, the cascaded deletes are done by the database and not by hibernate.
My problem is how I should mark these
deleted objects as deleted.
I think you have choosen a very complex way to solve the task. It would be more easy to introduce ProductTemplate. Place into this object all required properties you need. And also you need here a reference to a Product instance. Than instead of marking Product you can just delete it (and delete all other entities, such as prices). And, of course, you should clean reference in ProductTemplate. When you are creating an instance of ActualObject you will be able to notify the user with appropriate message.
I think you're trying to make things much more complicated than they should be... anyway, what you're trying to do is handling Hibernate events, take a look at Chapter 12 of Hibernate Reference, you can choose to use interceptors or the event system.
In any case... well good luck :)
public interface Deletable {
public void delete();
}
Have all your deletable objects implement this interface. In their implementations, update the deleted flag and have them call their children's delete() method also - which implies that the children must be Deletable too.
Of course, upon implementation you'll have to manually figure which children are Deletable. But this should be straightforward, at least.
If I understand what you are asking for, you add an #OneToMany relationship between the template and the product, and select your cascade rules, you will be able to delete all associated products for a given template. In your product class, you can add the "deleted" flag as you suggested. This deleted flag would be leveraged by your service/dao layer e.g. you could leverage a getProdcuts(boolean includeDeleted) type concept to determine if you should include the "deleted" records for return. In this fashion you can control what end users see, but still expose full functionality to internal business users.
The flag to delete should be a part of the Template Class itself. That way all the Objects that you create have a way to be flagged as alive or deleted. The marking of the Object to be deleted, should go higher up to the base class.
I'm trying to make an application that keeps an object model in sync with a database by observing all changes and then immediately persisting the objects in questions. Many of the object in the model have children in large lists or trees.
When I load an object from the database, I rely on a one-way cascading relationship to also retrieve all of its children and include them in the application.
However, it is possible to alter a field in the parent object which requires persistence and I can determine that none of the children are affected. So I would like to persist the parent, without hitting the database with all the cascaded child persists.
eg
#Entity
public class Parent {
#OneToMany(cascade=CascadeType.ALL)
public List children;
}
How can I override the cascade option when I persist a Parent object? Or should I just set it to REFRESH and make sure I never need a cascading persist?
Reading the objects from the database and persisting them rely upon two different annotations.
When you load an object, it will also get the other end of any eager (FetchType.EAGER) relationships, as defined by the fetch property on the relationship.
Depending on your JPA provider, you may have options to override this behaviour. EclipseLink, via the incredibly useful QueryHint.BATCH, certainly does.
When you persist, delete or refresh, the cascade type is what's relevant.
So, lose the cascade, keep the fetch and problem solved.
Personally I think cascade all is asking for trouble but opinions will vary.
A decent JPA provider will have a pretty sophisticated (configurable) caching scheme already. Perhaps you should be asking why you're reinventing that particular wheel?
Is it an issue of asynchronous updates purely for performance? Or is something else the reason?