Aggregation and Decomposition in JPA - java

How do you implement aggregation and decomposition with Java Persistence API? What are the best practices?
Thanks in advance,
Daniel

I've found the orphanRemoval attribute for #OneToMany and #OneToOne relationships:
When a target entity in one-to-one or one-to-many relationship is removed from the relationship, it is often desirable to cascade the remove operation to the target entity. Such target entities are considered “orphans,” and the orphanRemoval attribute can be used to specify that orphaned entities should be removed. For example, if an order has many line items, and one of the line items is removed from the order, the removed line item is considered an orphan. If orphanRemoval is set to true, the line item entity will be deleted when the line item is removed from the order.
Usage:
#OneToMany(mappedBy="customer", orphanRemoval=true)
public List<Order> orders;

There are two things which should be very clear while handling aggregation in JPA.
The relationship in the relational world.
The relationship required in the object world.
The relationship in Java world is governed by the domain need. For example a User might have many addresses so we keep the make the aggregation of address in User and not keep the inverse relationship. For composition, we need to handle the cascade behavior.
A more detail treatment can be see here

Related

JPA cascade for unidirectional relationships without loading everything

So I have some entities that are used as the basis for a coordinate system, for the purpose of this post we'll call them A, B, C and D. Each of these entities has multiple #OneToMany relationships, and I want to cascade deletes. i.e. When some A is deleted, all entities in each of the #OneToMany relationships are deleted too. Fairly standard stuff.
However, I don't see the point in having these entities explicitly tracking these relationships when all I want to do is cascade a delete. I don't see the point in loading all these entities (potentially millions!) into memory each time a new entity is added to the #OneToMany relationship (i.e. using lazy loading only loads in when it's accessed, but it's of course accessed when a new entity in the relationship is added).
Let's add a little example:
#Entity
public class A {
#Id
private long id;
// ... other fields ...
#OneToMany
private Collection<SomeClass> collection;
}
#Entity
public class SomeClass {
#Id
private long id;
// ... other fields ...
#ManyToOne
A a;
#ManyToOne
B b;
// ... likewise for C, D ...
}
There can be multiple classes similar to SomeClass, and so multiple #OneToMany relationships in A (and B,C,D) that require tacking. This gets tedious FAST. Also, every time a new instance of SomeClass is added, I'd need to load the entire collection and this seems exceedingly inefficient (I'd pretty much end up with my entire database loaded into memory just to cascade a delete!!!).
How can I achieve what I want without modifying the underlying database (e.g. specfying ON DELETE CASCADE in the definition), surely the designers of JPA have considered such a use case? Maybe I'm incorrect that I'd need to load the entire collection when adding an entity to the relationship (if so, please explain why :) ).
A similar question was asked here: JPA: unidirectional many-to-one and cascading delete but it doesn't have a satisfactory solution, and it doesn't discuss whether or not the entire relationship gets loaded into memory.
To achieve a multi-level cascade without initializing all the entities you can only use a DB cascade.
There's no other way! That's why you couldn't find a satisfactory solution.
As for the:
Also, every time a new instance of SomeClass is added, I'd need to
load the entire collection and this seems exceedingly inefficient (I'd
pretty much end up with my entire database loaded into memory just to
cascade a delete!!!).
You need to understand the unidirectional Collections taxonomy:
Adding one element to a Set, requires the whole collection to be initializes to enforce the uniqueness Set contract.
a java.util.Collection or an unindexed List means you have a Bag, which are very inefficient in the unidirectional use case. For inverse collections they are fine, but that's out of your current context.
An indexed List (where the order is materialized in the database) is what you might be looking for:
#OrderColumn(name="orders_index")
public List<Order> getOrders() { return orders; }
The indexed list will use the index key for add/remove/update operations. As opposed to a Bag which simply deletes all elements and recreates the collection with the remaining elements, an index List will use the index key to only remove the elements that no longer belong to the List.

Additional queries in JPA

I have two classes InvitedPerson and Flight with a one to one relationship with each other. Here is how they are annotated.
public class InvitedTech{
...
#OneToOne(mappedBy="invitedTech", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
public Flight flight;
#OneToOne(mappedBy="invitedTech", cascade = CascadeType.ALL, fetch=FetchType.LAZY)
public Hotel hotel;
...
}
public class Flight{
...
#OneToOne
#JoinColumn(name="invitedTechId", nullable=false)
public InvitedTech invitedTech;
...
}
As you can see Flight is the owner of the relationship and InvitedTech is the other side of this bidirectional relationship. InvitedTech also has a OneToOne relationship with Hotel Now, when I write a simple query to fetch all flights, it triggers three queries in total. 1st which gets me the results, but fires 2 additional queries.
List<Flight> flg = JPA.em().createQuery("SELECT flg from Flight flg").getResultList();
Query that gets all flights (This is the only one that I need)
Query with a join between InvitedTech and Flight
Query with a join between invitedTech and Hotel
Why are query 2&3 being executed even though I have set FetchType=Lazy. I am not accessing Hotel Information. And Flight should not be queries again as the first query returns the data.
After some playing around when I remove mappedBy attribute from both the annotations, those 2 addition queries don't get executed(i.e only 1st gets executed).
Why does the mappedBy attribute cause additional queries to be executed even though FetchType=Lazy. Is there a way to stop this?
I believe this is due to one of Hibernate's idiosyncrasies:
non-optional one-to-one relationships are eagerly loaded regardless of whether they are mapped as Lazy.
The reasoning behind this is that as the engine has to look in the association table anyway - to determine whether it should set the association as a proxy or as null - then it may as well load the associated entity anyway.
I have experienced this myself and as far as I know the only way round it is to mark the relationship with optional=false which tells Hibernate it can always set a proxy.
If the relationship is optional then the only other option seems to be byte code instrumentation.
See also:
https://community.jboss.org/wiki/SomeExplanationsOnLazyLoadingone-to-one
Making a OneToOne-relation lazy
You have not set the association from Flight to InvitedTech lazy. So it loads the InvitedTech associated with the flight.
Since it can't know from the InvitedTech if there exist a Hotel and a Flight associated with the InvitedTech, it can't decide if these fields should be null or should be proxies. So it's forced to execute additional queries to know if a hotel/flight exists for the InvitedTech.

Hibernate OnetoMany,ManyToOne Mapping Giving null

I have 2 classes called PurchaseList.java and PurchaseListItems.java
I have to map PurchaseList in PurchaseListItems
PurchaseList.java
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="pl_id",referencedColumnName="id")
private List<PurchaseListItems> purchaseListItems;
PurchaseListItems.java
#ManyToOne
#JoinColumn(name="pl_id")
private PurchaseList purchaseListId;
Everything is fine but i am getting null in pl_id. Please tell where i am wrong
For some reason mapped by didn't work for me with postgres sql and Hibernate4
Below mapping worked
PurchaseList.java
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="pl_id",nullable=false)
private List<PurchaseListItems> purchaseListItems;
PurchaseListItems.java
#ManyToOne
#JoinColumn(name="pl_id", nullable=false,insertable=false,updatable=false )
private PurchaseList purchaseListId;
Note: you have to use the Identity or Explicitly mention the Sequence for id columns for postgres.
#GeneratedValue(strategy=GenerationType.IDENTITY)
Your mapping actually defines two independent unidirectional relations. What you want is one bidirectional relation.The following code will establish the bidirectional relation
#OneToMany(cascade = CascadeType.ALL, mappedBy = "purchaseListId")
#JoinColumn(name="pl_id",referencedColumnName="id")
private List<PurchaseListItems> purchaseListItems;
The mappedBy attribute is necessary since there is no way for the provider to automatically determine that the specified relations actually form a single relation. One could use the Java type of the instance member but then what if you have multiple members of the same type. And there are many scenarios where you have two single relations. Example:
OneToMany: User -> ForumThread (the threads created by the user)
ManyToOne: ForumThread -> User (the user who closed the thread. obviously not necessarily the one who started the thread)
These are two independent relations and must be treated as such. You would be quite surprised if your persistence provide just made a bidirectional relation out of that just because the types and multiplicity matched.
Also note that bidirectional relations are not automatically managed by any JPA provider, meaning that the inverse side is not automatically updated/set in your object model and thus not in the db. You have to do that yourself. By the way, in all my projects bidirectional relationships were a pain in the ass and I think it is advisable to avoid them.
The #JoinColumn annotation belongs on the #ManyToOne side of the relationship - but not on the #OneToMany side - remove it from the #OneToMany side.
Cascade is used to cascade DELETE/READ/UPDATE operations..., but it does not automatically populate the ID column on the "child" side of a foreign key. In fact, it doesn't populate the java references to objects on either side of the FK relationship. You need to manually setup relationship data on both sides of bidirectional relationships:
myPurchaseListItem.setPurchaseList(myPurchaseList);
myPurchaseList.setPurchaseListItem(myPurchaseListItem);
From the JPA 2 spec:
Bidirectional relationships between managed entities will be persisted based on references held by the owning side of the relationship. It is the developer’s responsibility to keep the in-memory references held on the owning side and those held on the inverse side consistent with each other when they change. In the case of unidirectional one-to-one and one-to-many relationships, it is the developer’s responsibility to insure (sic) that the semantics of the relationships are adhered to.[29]
It is particularly important to ensure that changes to the inverse side of a relationship result in appropriate updates on the owning side, so as to ensure the changes are not lost when they are synchronized to the database.
for(PurchaseListItems item:purchaseListItemsList)
item.purchaseListId(PurchaseList);
This is what I missed when i am creating an object.
Thnaks for your answers
The jpa specification looks good, but verify you have given valid parent to child relationship in the database. If there is not a reference then it will return null.
try this
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "purchaseListId")
Check if you have populated purchaseListId with valid value (a created PurchaseList instance) when you create a PurchaseListItems value.
It's better to use mappedBy as below code to let many-side to maintian the relationship.
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "purchaseListId")
#JoinColumn(name="pl_id",referencedColumnName="id")
private List<PurchaseListItems> purchaseListItems;

What is the meaning of the CascadeType.ALL for a #ManyToOne JPA association

I think I misunderstood the meaning of cascading in the context of a #ManyToOne relationship.
The case:
public class User {
#OneToMany(fetch = FetchType.EAGER)
protected Set<Address> userAddresses;
}
public class Address {
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
protected User addressOwner;
}
What is the meaning of the cascade = CascadeType.ALL? For example, if I delete a certain address from the database, how does the fact that I added the cascade = CascadeType.ALL affect my data (the User, I guess)?
The meaning of CascadeType.ALL is that the persistence will propagate (cascade) all EntityManager operations (PERSIST, REMOVE, REFRESH, MERGE, DETACH) to the relating entities.
It seems in your case to be a bad idea, as removing an Address would lead to removing the related User. As a user can have multiple addresses, the other addresses would become orphans. However the inverse case (annotating the User) would make sense - if an address belongs to a single user only, it is safe to propagate the removal of all addresses belonging to a user if this user is deleted.
BTW: you may want to add a mappedBy="addressOwner" attribute to your User to signal to the persistence provider that the join column should be in the ADDRESS table.
You shouldn't use CascadeType.ALL on #ManyToOne since entity state transitions should propagate from parent entities to child ones, not the other way around.
The #ManyToOne is on the child side of the association as it maps the underlying Foreign Key column.
Therefore, you should move the CascadeType.ALL from the #ManyToOne association to the #OneToMany side, which should also use the mappedBy attribute since it's the most efficient one-to-many table relationship mapping.
See here for an example from the OpenJPA docs. CascadeType.ALL means it will do all actions.
Quote:
CascadeType.PERSIST: When persisting an entity, also persist the entities held in its fields. We suggest a liberal application of this cascade rule, because if the EntityManager finds a field that references a new entity during the flush, and the field does not use CascadeType.PERSIST, it is an error.
CascadeType.REMOVE: When deleting an entity, it also deletes the entities held in this field.
CascadeType.REFRESH: When refreshing an entity, also refresh the entities held in this field.
CascadeType.MERGE: When merging entity state, also merge the entities held in this field.
Sebastian
From the EJB3.0 Specification:
Use of the cascade annotation element may be used to propagate the
effect of an operation to associated entities. The cascade
functionality is most typically used in parent-child relationships.
If X is a managed entity, the remove operation causes it to become
removed. The remove operation is cascaded to entities referenced by X,
if the relationships from X to these other entities is annotated with
the cascade=REMOVE or cascade=ALL annotation element value.
So in a nutshell, entity relationships defined with CascadeType.All will ensure that all persistence events such as persist, refresh, merge and remove that occur on the parent, will be passed to the child. Defining other CascadeType options provides the developer with a more granular level of control over how the entity association handles persistence.
For example if I had an object Book that contained a List of pages and I add a page object within this list. If the #OneToMany annotation defining the association between Book and Page is marked as CascadeType.All, persisting the Book would result in the Page also being persisted to the database.
In JPA 2.0 if you want to delete an address if you removed it from a User entity you can add orphanRemoval=true (instead of CascadeType.REMOVE) to your #OneToMany.
More explanation between orphanRemoval=true and CascadeType.REMOVE is here.
If you just want to delete the address assigned to the user and not to affect on User entity class you should try something like that:
#Entity
public class User {
#OneToMany(mappedBy = "addressOwner", cascade = CascadeType.ALL)
protected Set<Address> userAddresses = new HashSet<>();
}
#Entity
public class Addresses {
#ManyToOne(cascade = CascadeType.REFRESH) #JoinColumn(name = "user_id")
protected User addressOwner;
}
This way you dont need to worry about using fetch in annotations. But remember when deleting the User you will also delete connected address to user object.

JPA update many-to-many deleting records

I have a #ManyToMany relationship between two entities. When I perform an update on the owning side, it appears that JPA deletes all the linked records from my database and re-inserts them. For me this is a problem because I have a MySQL trigger that fires before a record is deleted. Any ideas on how to get around this problem?
#Entity
public class User {
#Id
#Column(name="username")
private String username;
...
#ManyToMany
#JoinTable(name="groups", joinColumns=
#JoinColumn(name="username", referencedColumnName="username"),
inverseJoinColumns=#JoinColumn(name="groupname",
referencedColumnName="type_id"))
private List<UserType> types;
...
}
#Entity
public class UserType {
#Id
#Column(name="type_id")
private String id;
#ManyToMany(mappedBy="types")
private List<User> users;
...
}
Use Set instead of List solved the problem. But I have no idea why it works.
Another solution provided by Hibernate is to split the #ManyToMany association into two bidirectional #OneTo#Many relationships. See Hibernate 5.2 documentation for example.
If a bidirectional #OneToMany association performs better when
removing or changing the order of child elements, the #ManyToMany
relationship cannot benefit from such an optimization because the
foreign key side is not in control. To overcome this limitation, the
link table must be directly exposed and the #ManyToMany association
split into two bidirectional #OneToMany relationships.
Try this one:
1) change declaration to:
private List<UserType> types = new Vector<UserType>();
2) never call
user.setTypes(newTypesList)
3) only call
user.getTypes().add(...);
user.getTypes().remove(...);
Its probably related to this question. You have to ensure you have an appropriately defined hashCode an equals method in your mapped object so that Eclipselink can determine equality and thus determine that the existing objects map to existing objects in the DB. Otherwise it has no choice but to recreate the child objects every time.
Alternatively, I've read that this kind of join can only support efficient adding and removing of list items if you use an index column, but that's going to be EclipseLink specific, since the JPA annotations don't seem to support such a thing. I know there is an equivalent Hibernate annotation, but I don't know what it would be in Eclipselink, if such a thing exists.
It appears my problem was that I was not merging the entity.

Categories