JPA/Hibernate - Prevent deletion in PreRemove handler? - java

The question title basically says it all. Is it possible in JPA/Hibernate to gracefully prevent the deletion of an entity from the database? What I would like is to flag the entity as "hidden" instead of actually removing it.
I also want the Cascade semantics to be preserved, such that if I try to delete an entity that owns a collection of some other entity, the owning entity and every entity in its collection get marked as hidden without any extra work being necessary on my part, beyond implementing the #PreRemove handler that prevents the deletion and marks the entity as hidden.
Is this possible, or do I need to figure out some other approach?

Is it possible in JPA/Hibernate to gracefully prevent the deletion of an entity from the database?
Yes, as long as you avoid using EntityManager.remove(entity) this is possible. If you do use EntityManager.remove(), then the JPA provider will flag the object for deletion using a corresponding SQL DELETE statement implying that a elegant solution will not be possible once you flag the object for deletion.
In Hibernate, you can achieve this using #SQLDelete and #Where annotations. However, this will not play well with JPA, as EntityManager.find() is known to ignore the filter specified in the #Where annotation.
A JPA-only solution would therefore involve, adding a flag i.e. a column, in the Entity classes to distinguish logically deleted entities in the database from "alive" entities. You will need to use appropriate queries (JPQL and native) to ensure that logically deleted entities will not be available in the result sets. You can use the #PreUpdate and #PrePersist annotations to hook onto the entity lifecycle events to ensure that the flag is updated on persist and update events. Again, you will need to ensure that you will not invoke the EntityManager.remove method.
I would have suggested using the #PreRemove annotation to hook onto the lifecycle event that is triggered for removal of entities, but using an entity listener to prevent deletion is fraught with trouble for the reasons stated below:
If you need to prevent the SQL DELETE from occurring in a logical sense, you will need to persist object in the same transaction to recreate it*. The only problem is that it is not a good design decision to reference the EntityManager in a EntityListener, and by inference invoke EntityManager.persist in the listener. The rationale is quite simple - you might end up obtaining a different EntityManager reference in the EntityListener and this will only result in vague and confusing behavior in your application.
If you need to prevent the SQL DELETE in the transaction itself from occurring, then you must throw an Exception in your EntityListener. This usually ends up rolling back the transaction (especially if the Exception is a RuntimeException or an application exception that is declared to be one that causes rollbacks), and does not offer any benefit, for the entire transaction will be rolled back.
If you have the option of using EclipseLink instead of Hibernate, then it appears that an elegant solution is possible if you define an appropriate DescriptorCustomizer or by using the AdditionalCriteria annotation. Both of these appear to work well with the EntityManager.remove and EntityManager.find invocations. However, you might still need to write your JPQL or native queries to account for the logically deleted entities.
* This is outlined in the JPA Wikibook on the topic of cascading Persist:
if you remove an object to have it deleted, if you then call persist on the object, it will resurrect the object, and it will become persistent again. This may be desired if it is intentional, but the JPA spec also requires this behavior for cascade persist. So if you remove an object, but forget to remove a reference to it from a cascade persist relationship, the remove will be ignored.

Related

What collateral effect Propagation.REQUIRES_NEW will cause to my app

I was getting an error like this
a different object with the same identifier value was already associated with the session:
I've searched and found that it could be fixed with CascadeType.MERGE or refactoring a lot of code to prevent that a same database object becomes two instances within the session.
I can't refactor it.
CascadeType.MERGE worked, but that means I would have to code a lot to resolve remove problems, since it was .ALL before, right?
I got it working putting
#Transactional(propagation = Propagation.REQUIRES_NEW)
above a method, of a class annoted with #Service, that query database, which was the one that was throwing the exception I mentioned.
I need help to understand if this new annotated method can bring me any future headache like it is now.
It is being called from some cron jobs beside the action I'm fixing.
In fact you create a separate transaction for the method call by annotating the method with #Transactional(propagation = Propagation.REQUIRES_NEW)
It means in case of an Exception thrown out of the method all DB changes (saving etc.) are applied and are not rolled back. This could significantly damage the business logic and could be source of inconsistent data in DB.
I would reconsider applying the Propagation.REQUIRES_NEW.
Merge sounds much more suitable in this case.
None of the solution you listed are acceptable IMHO.
Deferring some part of treatment to a new transaction will break the atomicity (all or nothing) of your unit of work, and changing the cascading type will imply that you manually handle all operation automatically cascaded before.
The right approach is to understand why hibernate encounters 2 different object instances with the same identifier, the most common causes is because you manually persists (save) a detached / transient object with a fixed identifier while this one already exists in the session (a managed object with the same identifier is already in the session).
You could try to manually re-attach (merge / update /saveOrUpdate) the detached object instance causing the problem.
You have to be aware of the entity lifecycle to properly understand what happens here.

update() and merge behave differently in case of updating an item in OneToMany collection

I have this a class like bellow:
#Entity
#Table(name="work")
public class Work {
#Id
#Column(name="id")
private String id;
#OneToMany(orphanRemoval=true ,mappedBy="work", cascade=CascadeType.ALL , fetch=FetchType.EAGER)
private List<PersonRole> personRoleList;
}
As mine is an web application, when i update (comes from client) a personRoleList item and call :
session.update(work); //`work` is in detached state
It does not update the existing personRoleList item it actually add a new one.
Some other people also having the same problem. REF:
using-saveorupdate-in-hibernate-creates-new-records-instead-of-updating-existi
jpa-onetomany-not-deleting-child
I tried all suggested solution, but none of them work for me.
But then i just tried :
session.merge(work); //replacing session.update(work)
And it works as expected.!!
This is where I get confused. Because I can't find any explanation for this difference in behaviors in case of OneToMany relationship (or may be i missed ). I read some threads to understand the differences between update() and merge() and gone through the doc. REF:
what-are-the-differences-between-the-different-saving-methods-in-hibernate
differences-among-save-update-saveorupdate-merge-methods-in-session
But still it is not clear What are those behavioral pattern/logic/steps that creating this difference.?
Merge attempts to associate a currently transient object with a persistent object currently under management by the session by 'merging' them into one entity. Its intended use is when you have a detached object and an attached object and wish to resolve them.
In a merge(), Hibernate will read the entity from the database if there isn't already a managed instance in the session. In your example, this will result in Hibernate eagerly loading the collection (due to fetch=FetchType.EAGER). Then when your session ends, Hibernate will check for changes in the collection (due to cascade=CascadeType.ALL) and will perform the appropriate UPDATE in the database.
This differs from the update() scenario because in an update Hibernate always (by default) assumes the object is dirty and schedules an UPDATE. This update is likely what's causing creation of a new element in your collection - Hibernate hasn't looked in the database to bring the collection into session before issuing the UPDATE.
I'd bet you can get the desired behavior of update() by setting
select-before-update="true"
in your class mapping or by using the lock method to re-attach your object to the session before making changes.
From Chapter 9 of Java Persistence with Hibernate
It doesn’t matter if the item object is modified before or after it’s passed to
update(). The important thing here is that the call to update() is reattaching the detached instance to the new Session (and persistence context). Hibernate
always treats the object as dirty and schedules an SQL UPDATE, which will be executed during flush. You can see the same unit of work in figure 9.8.
You may be surprised and probably hoped that Hibernate could know that you
modified the detached item’s description (or that Hibernate should know you did
not modify anything). However, the new Session and its fresh persistence context
don’t have this information. Neither does the detached object contain some internal list of all the modifications you’ve made.
UDPATE in the database is needed. One way to avoid this UDPATE statement is to
configure the class mapping of Item with the select-before-update="true"
attribute. Hibernate then determines whether the object is dirty by executing a
SELECT statement and comparing the object’s current state to the current data-
base state.

Should Hibernate Session#merge do an insert when receiving an entity with an ID?

This seems like it would come up often, but I've Googled to no avail.
Suppose you have a Hibernate entity User. You have one User in your DB with id 1.
You have two threads running, A and B. They do the following:
A gets user 1 and closes its Session
B gets user 1 and deletes it
A changes a field on user 1
A gets a new Session and merges user 1
All my testing indicates that the merge attempts to find user 1 in the DB (it can't, obviously), so it inserts a new user with id 2.
My expectation, on the other hand, would be that Hibernate would see that the user being merged was not new (because it has an ID). It would try to find the user in the DB, which would fail, so it would not attempt an insert or an update. Ideally it would throw some kind of concurrency exception.
Note that I am using optimistic locking through #Version, and that does not help matters.
So, questions:
Is my observed Hibernate behaviour the intended behaviour?
If so, is it the same behaviour when calling merge on a JPA EntityManager instead of a Hibernate Session?
If the answer to 2. is yes, why is nobody complaining about it?
Please see the text from hibernate documentation below.
Copy the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. Return the persistent instance. If the given instance is unsaved, save a copy of and return it as a newly persistent instance.
It clearly stated that copy the state(data) of object in database. if object is not there then save a copy of that data. When we say save a copy hibernate always create a record with new identifier.
Hibernate merge function works something like as follows.
It checks the status(attached or detached to the session) of entity and found it detached.
Then it tries to load the entity with identifier but not found in database.
As entity is not found then it treat that entity as transient.
Transient entity always create a new database record with new identifier.
Locking is always applied to attached entities. If entity is detached then hibernate will always load it and version value gets updated.
Locking is used to control concurrency problems. It is not the concurrency issue.
I've been looking at JSR-220, from which Session#merge claims to get its semantics. The JSR is sadly ambiguous, I have found.
It does say:
Optimistic locking is a technique that is used to insure that updates
to the database data corresponding to the state of an entity are made
only when no intervening transaction has updated that data since the
entity state was read.
If you take "updates" to include general mutation of the database data, including deletes, and not just a SQL UPDATE, which I do, I think you can make an argument that the observed behaviour is not compliant with optimistic locking.
Many people agree, given the comments on my question and the subsequent discovery of this bug.
From a purely practical point of view, the behaviour, compliant or not, could lead to quite a few bugs, because it is contrary to many developers' expectations. There does not seem to be an easy fix for it. In fact, Spring Data JPA seems to ignore this issue completely by blindly using EM#merge. Maybe other JPA providers handle this differently, but with Hibernate this could cause issues.
I'm actually working around this by using Session#update currently. It's really ugly, and requires code to handle the case when you try to update an entity that is detached, and there's a managed copy of it already. But, it won't lead to spurious inserts either.
1.Is my observed Hibernate behaviour the intended behaviour?
The behavior is correct. You just trying to do operations that are not protected against concurrent data modification :) If you have to split the operation into two sessions. Just find the object for update again and check if it is still there, throw exception if not. If there is one then lock it by using em.(class, primary key, LockModeType); or using #Version or #Entity(optimisticLock=OptimisticLockType.ALL/DIRTY/VERSION) to protect the object till the end of the transaction.
2.If so, is it the same behaviour when calling merge on a JPA EntityManager instead of a Hibernate Session?
Probably: yes
3.If the answer to 2. is yes, why is nobody complaining about it?
Because if you protect your operations using pessimistic or optimistic locking the problem will disappear:)
The problem you are trying to solve is called: Non-repeatable read

Hibernate listener executed in separate transaction

In our application we have a PostDeleteEventListener registered that is executed for each deleted owning entity and possibly for related entities (if cascading delete is enabled).
The listener is used to inspect each entity for a certain property that represents the ID of an entity that is located in one big centralised table without foreign key relation.
The discovered ID's are stored in a java.util.Set held by a ThreadLocal.
What we're looking for is some 'transaction hook' to trigger one delete statement (for performance reasons) with all the ID's held by the java.util.Set.
We tried to achieve this by using a Spring aspect that is executed after a #Transactional method has run. The aspect could then fire the delete statement and clear the java.util.Set.
Unfortunately, it seems that the PostDeleteEventListener is executed AFTER the aspect is triggered. In other words, it gets executed in a separate transaction which I can clearly see by inspecting the stacktrace while debugging. So having an aspect for this doesn't look the way to go...
An other option seems to have a second listener (FlushEventListener) that is executed when the session is flushed.
Although this works, we don't know if this is a good solution?
Are there any other (and better) alternatives out there we can use for this issue?
Is there an other way (Spring or Hibernate) to get notified just before the transaction is committed?

JPA merge vs. persist [duplicate]

This question already has answers here:
JPA EntityManager: Why use persist() over merge()?
(16 answers)
Closed 2 years ago.
So far, my preference has been to always use EntityManager's merge() take care of both insert and update. But I have also noticed that merge performs an additional select queries before update/insert to ensure record does not already exists in the database.
Now that I am working on a project requiring extensive (bulk) inserts to the database. From a performance point of view does it make sense to use persist instead of merge in a scenario where I absolutely know that I am always creating a new instance of objects to be persisted?
It's not a good idea using merge when a persist suffices - merge does quite a lot more of work. The topic has been discussed on StackOverflow before, and this article explains in detail the differences, with some nice flow diagrams to make things clear.
I would definitely go with persist persist() if, as you said:
(...) I absolutely know that I am always creating a new instance of objects to be persisted (...)
That's what this method is all about - it will protect you in cases where the Entity already exists (and will rollback your transaction).
If you're using the assigned generator, using merge instead of persist can cause a redundant SQL statement, therefore affecting performance.
Also, calling merge for managed entities is also a mistake since managed entities are automatically managed by Hibernate and their state is synchronized with the database record by the dirty checking mechanism upon flushing the Persistence Context.
To understand how all this works, you should first know that Hibernate shifts the developer mindset from SQL statements to entity state transitions.
Once an entity is actively managed by Hibernate, all changes are going to be automatically propagated to the database.
Hibernate monitors currently attached entities. But for an entity to become managed, it must be in the right entity state.
First, we must define all entity states:
New (Transient)
A newly created object that hasn’t ever been associated with a Hibernate Session (a.k.a Persistence Context) and is not mapped to any database table row is considered to be in the New (Transient) state.
To become persisted we need to either explicitly call the EntityManager#persist method or make use of the transitive persistence mechanism.
Persistent (Managed)
A persistent entity has been associated with a database table row and it’s being managed by the current running Persistence Context. Any change made to such entity is going to be detected and propagated to the database (during the Session flush-time).
With Hibernate, we no longer have to execute INSERT/UPDATE/DELETE statements. Hibernate employs a transactional write-behind working style and changes are synchronized at the very last responsible moment, during the current Session flush-time.
Detached
Once the current running Persistence Context is closed all the previously managed entities become detached. Successive changes will no longer be tracked and no automatic database synchronization is going to happen.
To associate a detached entity to an active Hibernate Session, you can choose one of the following options:
Reattaching
Hibernate (but not JPA 2.1) supports reattaching through the Session#update method.
A Hibernate Session can only associate one Entity object for a given database row. This is because the Persistence Context acts as an in-memory cache (first level cache) and only one value (entity) is associated with a given key (entity type and database identifier).
An entity can be reattached only if there is no other JVM object (matching the same database row) already associated to the current Hibernate Session.
Merging
The merge is going to copy the detached entity state (source) to a managed entity instance (destination). If the merging entity has no equivalent in the current Session, one will be fetched from the database.
The detached object instance will continue to remain detached even after the merge operation.
Removed
Although JPA demands that managed entities only are allowed to be removed, Hibernate can also delete detached entities (but only through a Session#delete method call).
A removed entity is only scheduled for deletion and the actual database DELETE statement will be executed during Session flush-time.
To understand the JPA state transitions better, you can visualize the following diagram:
Or if you use the Hibernate specific API:

Categories