I actually never quite understood this behavior in hibernate.
I am using a #OneToMany relationship in a Entity called 'Parent', which is annotated like this:
#OneToMany(cascade = {CascadeType.ALL, CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, orphanRemoval = true)
#JoinColumn(name = "entity_id", insertable = true, updatable = true, nullable = false)
private List<Child> children;
Now I want to do the following within one transaction:
Get the parent entity
iterate through the list of children
delete one of the children
insert a new child
So, basically I am just entirely replacing one of the children.
As far as I understand this problem, I should be able to do something like this:
(please note that this is just some java pseudocode to illustrate the problem)
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
Parent parent = entityManager.find(parentId);
for (Iterator it = parent.children.iterator(); it.hasNext();) {
Child child = it.next();
if (child.id == childId) {
it.remove();
}
}
Child newChild = new Child();
parent.children.add(newChild);
}
However, this fails in case the new Child has the same unique key values as the old one. So, basically it seems like the old child entity isn't removed properly, before the new one is persisted.
If I add a entityManager.flush() between deleting the old child and persisting the new child like this:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteAndAdd(Long parentId, Long childId) {
Parent parent = entityManager.find(parentId);
for (Iterator it = parent.children.iterator(); it.hasNext();) {
Child child = it.next();
if (child.id == childId) {
it.remove();
}
}
entityManager.flush();
Child newChild = new Child();
parent.children.add(newChild);
}
Everything works fine. The child is deleted before the new one is inserted, as it should.
As I don't want to asume that hibernate mixes up the order of the statements that are sent to the DB, there must be something else I am assuming about hibernate which isn't the case. Any ideas why the latter example works, while the first one doesn't?
Hibernate version is 3.5.
DB is Mysql InnoDB
Hibernate doesn't know about, nor respect, all database constraints (e.g. MySQL unique constraints). It's a known issue they don't plan on addressing anytime soon.
Hibernate has a defined order for the way operations occur during a flush.
Entity deletions will always happen after inserts. The only answers I know about are to remove the constraint or add the additional flush.
EDIT: By the way, the reason for the defined order is that this is the only way to guarantee foreign key constraints (one of the constraints they DO care about) aren't violated, even if the user does something out of order.
For the sake of future readers, one way to resolve this issue is to use deferred constraints. PostgreSQL and Oracle support them, maybe other RDBMS' too. Hibernate will issue all statements within a transaction, and deferral will ensure that constraints are enforced upon transaction commit only. In PostgreSQL, for example:
ALTER TABLE company
ADD CONSTRAINT name_unique UNIQUE (name) DEFERRABLE INITIALLY DEFERRED;
It is not ideal, but it is simple and effective.
Related
Let's say I have following model structure:
#Entity
#Table(....)
public class AnnotationGroup{
...
private List<AnnotationOption> options;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "annotation_group_id", nullable = false)
public List<AnnotationOption> getOptions() {
return options;
}
}
#Entity
#Table(...)
public class AnnotationOption {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Override
public Long getId() {
return id;
}
}
At the moment I have group1 with AnnotationOptions opt1 opt2 and opt3
Then I want to replace all option with only one option opt1
Additionally I have constraint in database:
CONSTRAINT "UQ_ANNOTATION_OPTION_name_annotation_group_id" UNIQUE (annotation_option_name, annotation_group_id)
And this one fires:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "UQ_ANNOTATION_OPTION_name_annotation_group_id"
Detail: Key (name, annotation_group_id)=(opt1, 3) already exists.
Actually isuue that hibernate removes orphans after update.
Can you suggest something t resolve issue?
There are so many things that are wrong in this example:
EAGER fetching on the #OneToManycollection is almost always a bad idea.
Unidirectional collections are also bad, use the bidirectional one.
If you get this exception, most likely you cleared all the elements and re-added back the ones that you want to be retained.
The best way to fix it is to explicitly merge the existing set of children with the incoming ones so that:
New child entities are being added to the collection.
The child entities that are no longer needed are removed.
The child entities matching the business key (annotation_group_name, study_id) are updated with the incoming data.
According to Hibernate documentation hibernate perform in the following order to preserve foreign-key constraint:
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
For your special need you should manually flush the transaction to force the deletion in database before.
I made my self a little hibernate sandbox to understand how it works.
I ve done quite well so far with all the basics. Everything works as expected.
I only have an unsolved issue.
To make it short, I have a Rats entity and a Sickness entity.
A Rat can have a single Sickness.
The association is correctly set into the DB and the entities files include this part:
in Rats class:
[...]
#ManyToOne(fetch = FetchType.LAZY )
#Cascade({ CascadeType.SAVE_UPDATE, CascadeType.DELETE})
#JoinColumn(name = "Sickness_Id")
public Sickness getSickness() {
return this.sickness;
}
[...]
in Sickness class:
[...]
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sickness")
#Cascade({ /*CascadeType.SAVE_UPDATE,*/ CascadeType.MERGE, CascadeType.REFRESH})
public Set<Rats> getRatses() {
return this.ratses;
}
[...]
If I create a new Rats with a new Sickness and save the Rats, the cascade works as expected and the Sickness is automatically added to the DB too.
Deletion part works too, when I delete a Rats, its sickness is deleted.
What does not work is trying to create a Sickness and try to spread it to many Rats via its SetRatses method:
String sick_name2 = "Tourista";
System.out.println("\nsetting new sickness: " + sick_name2 + " and assigning it to all rats");
Sickness sickness2 = new Sickness();
sickness2.setNom(sick_name2);
ArrayList<Rats> sickratsList = (ArrayList<Rats>) session.createCriteria(Rats.class).list();// new HashSet<Rats>();
Set<Rats> sickRatsSet = new HashSet<Rats>();
for(Rats rat : sickratsList){
sickRatsSet.add(rat);
}
sickness2.setRatses(sickRatsSet);
session.save(sickness2);
Debuging this shows that the Sickness is correctly inserted into the DB, its sickRatsSet field is correctly set with all the rats.
But... if I check the Rats status, their Sickness has not been updated.
Trying to set CascadeType. into the Sickness relationship did not help.
I know that I could solve it with something like:
ArrayList<Rats> sickratsList = (ArrayList<Rats>) session.createCriteria(Rats.class).list();// new HashSet<Rats>();
Set<Rats> sickRatsSet = new HashSet<Rats>();
for(Rats rat : sickratsList){
rat.setSickness(seckness2);
session.save(rat);
}
But I would like to understand how to do it via Sickness.setRatses.
So that I can find my way later with a many to many relationship (I suppose it will be pretty similar).
Thx in advance.
A bidirectional association has an owner side (the side without the mappedBy attribute), and an inverse side (the side with the mappedBy attribute).
Hibernate only considers the owner side when deciding which entities are associated with each other.
Adding rats to a sickness thus won't make Hibernate associate the rat to the sickness, since that only modifies the inverse side. You must set the rat's sickness.
Note that using a DELETE cascade on a ManyToXxx annotation doesn't make much sense. There is no reason to delete the tourista sickness from the database as soon as one of the thousand rats having the tourista is deleted. And that will obviously cause an exception anyway, since 999 other rats have a foreign key to the tourista sickness.
This error happens because you're retrieving the objects that are already cached in the 1st level cache (session), and those objects don't have the bidirectional association set correctly. In your code, you're never calling rat.setSickeness(sickness).
Try calling the following methods and check if the data is now correct
session.flush()
session.clear()
// load the rats /sickness again and the relations should be set.
Bottom line: when you have a bidirectional association is the developer responsibility to add/set the objects on boths ends, otherwise you'll get into this error. The simplest way to fix this is to only have one method in one of your 2 objects that know how to maintain the assocation. For example
public class Rat {
public void setSickness(Sickness sickness) {
this.sicknesses = sickness;
sickness.addRat(this);
}
}
public class Sickness {
// leave this as package protected! So the only way to set the association is from the Rat
void addRat(Rat rat) {
rats.add(rat);
}
}
You might want to read the Hibernate documentation about Session and how it works as a 1st level cache.
Thanks JB and Augusto, I got a much better understanding now.
I was able to solve my issue by overriding this way:
public void setRatses(Set<Rats> ratses) {
this.ratses = ratses;
for(Rats rat : ratses){
rat.setSickness(this);
}
This bring me another methodological question.
I found out that if I do the following:
raton.setSickness(sickness1);
raton.display(); -> raton.sickness = sickness1 as expected
sickness1.display(); -> sickness1.ratses does not contain raton for the reasons that you guys pointed.
I can either use session.flush()
session.clear() or commit the transaction and start a new one if I need sickness1.ratses to be up to date.
I suppose that I can also override Rats.setSickness this way:
public void setSickness(Sickness sickness) {
if(sickness.ratses.contain(this) sickness.ratses.remove(this);
this.sickness = sickness;
ickness.ratses.add(this);
}
this way, my sickness is up to date inside the session without I need to flush the session.
Would this be such a good idea?
On regarding performance side, I suppose that the override solution generates additional DB operations that might not be really needed?
I'm having trouble getting my merge to cascade down when there are new child objects.
I have a parent object (City) with a set of child objects
#OneToMany(mappedBy = "parent", cascade = { CascadeType.ALL }, fetch=FetchType.LAZY )
public Set<Street> getStreets() {
return streets;
}
And a child object (Street) referencing the parent
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="City", nullable=false)
public City getParentCity(){
return parentCity;
}
And I'm running a daily routine to dump information into the database. All well and dandy until JPA runs into a new street. When it gives me an EntityNotFoundException. I'm assuming that it means there's no street already in the database with the new ID. From what I've read it's because the child is the actual owner of the transaction, but I need it to persist the new object if it runs into this "EntityNotFound" exception
I'm not using an auto-generated primary key, It's a key that already exists in the dataset. Currently I have access to a micro-service that uses em.merge() on the parent object.
I see that there's an option to delete objects that no longer exist (orphanRemoval); is there a way to add new ones?
Thanks
I dont know if it is the case, but with me, only works if I put the hibernate's annotations above the variable attributes themselves, not on getter methods.
What worked for me was 'manually' saving first the street, and then the City.
I have a class called SynonymMapping which has a collection of values mapped as a CollectionOfElements
#Entity(name = "synonymmapping")
public class SynonymMapping {
#Id private String keyId;
//#CollectionOfElements(fetch = FetchType.EAGER)
#CollectionOfElements
#JoinTable(name="synonymmappingvalues", joinColumns={#JoinColumn(name="keyId")})
#Column(name="value", nullable=false)
#Sort(type=SortType.NATURAL)
private SortedSet<String> values;
public SynonymMapping() {
values = new TreeSet<String>();
}
public SynonymMapping(String key, SortedSet<String> values) {
this();
this.keyId = key;
this.values = values;
}
public String getKeyId() {
return keyId;
}
public Set<String> getValues() {
return values;
}
}
I have a test where I store two SynonymMapping objects to the database and then ask the database to return all saved SynonymMapping objects, expecting to receive the two objects I stored.
When I change the mapping of values to be eager (as shown in in the code by the commented out line) and run the test again, I receive four matches.
I have cleared out the database between runs and I can duplicate this problem swapping between eager and lazy.
I think it has to do with the joins that hibernate creates underneath but I can't find a definite answer online.
Can anyone tell me why an eager fetch is duplicating the objects?
Thanks.
I stepped into the same problem - when you set the FetchType.EAGER for a #CollectionOfElements, the Hibernate tries to get everything in one shot, i.e. using one single query for each entry of element linked to a "master" object. This problem can be successfully solved at a cost of N+1 query, if you add the #Fetch (FetchMode.SELECT) annotation to your collection.
In my case I wanted to have a MediaObject entity with a collection of its metadata items (video codec, audio codec, sizes, etc.). The mapping for a metadataItems collection looks as follows:
#CollectionOfElements (targetElement = String.class, fetch = FetchType.EAGER)
#JoinTable(name = "mo_metadata_item", joinColumns = #JoinColumn(name = "media_object_id"))
#MapKey(columns = #Column(name = "name"))
#Column (name = "value")
#Fetch (FetchMode.SELECT)
private Map<String, String> metadataItems = new HashMap<String, String>();
It's generally not a good idea to enforce eager fetching in the mapping - it's better to specify eager joins in appropriate queries (unless you're 100% sure that under any and all circumstances your object won't make sense / be valid without that collection being populated).
The reason you're getting duplicates is because Hibernate internally joins your root and collection tables. Note that they really are duplicates, e.g. for 2 SynonymMappings with 3 collection elements each you would get 6 results (2x3), 3 copies of each SynonymMapping entity. So the easiest workaround is to wrap results in a Set thereby ensuring they're unique.
I have faced this problem and I solved it using
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This clears out the duplicates which are caused by the join made to the child tables.
You could use a SELECT DISTINCT (Hibernate Query Language) clause as follows
SELECT DISTINCT synonym FROM SynonymMapping synonym LEFT JOIN FETCH synonym.values
DISTINCT clause removes duplicate references in Hibernate.
Although both component and value-type collection has its lifecycle bound to the owning entity class, you should declare them in select clause in order to retrieve them. (LEFT JOIN FETCH synonym.values)
ChssPly76's answer is another approach, but does not forget override equals and hashcode method according to Set semantic
regards,
Instead of FetchMode.SELECT with N+1 queries it is better using BatchSize e.q. #BatchSize(size = 200).
DISTINCT and Criteria.DISTINCT_ROOT_ENTITY doesn't help, if you have to fetch more than 1 association. For this case see other solutions: https://stackoverflow.com/a/46013654/548473
I have achieved it via simply add
session.createCriteria(ModelClass.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
This help to remove duplicate.
How do you get a OneToOne item to automatically remove with JPA/Hibernate? I would expect simply setting the OneToOne item to be null in the class that contains would be smart enough to allow Hibernate to delete it.
Given a simple object, simplified:
#Entity
public class Container {
private Item item;
#OneToOne(cascade=CascadeType.ALL)
public Item getItem() { return item; }
public void setItem(Item newItem) { item = newItem; }
}
When an Item is set on Container an Container is persisted with merge a row gets inserted.
Container container = new Container();
container.setItem(new Item());
container = entityManager.merge(container);
// Row count is 1
But when the item is set null, or to another item, the old object still exists in the table.
container.setItem(null);
container = entityManager.merge(container);
// Row count is STILL 1, leaving orphaned rows.
So, how do I remove these OneToOne orphans?
I'm guessing that the reasone behind hibernate not allowing DELETE_ORPHAN to OneToOne relations is related to this issue.
If you really want this bad, you can hack your way with these steps:
transform your OneToOne relation into a OneToMany:
add a method just to get the first element of the child's collection (this is optional but handy)
use the DELETE_ORPHAN annotation
Of course, this is a big hack.
As JPA 2.0 has been released for a very long time now, you could simply use:
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
Try to change to
#OneToOne
#Cascade(cascade = {CascadeType.ALL, CascadeType.DELETE_ORPHAN})
See also my answer on similar post here.
Unfortunately, there is no way to do this in JPA without tying yourself to Hibernate's implementation.
So yes, as Foxy says, you can use org.hibernate.annotations.CascadeType instead of the standard JPA annotation, which allows you to specify DELETE_ORPHAN. If you want to use the JPA abstraction, you must delete orphans yourself as of yet.