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?
Related
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
Background
I have a java/spring system where transactions are managed manually via a custom HandlerInterceptor. That is to say:
at the begining of every request a transaction is opened (an unfortunate part of the system is that any request might result in a write to the db)
an EntityManager instance joins the transaction
the entity manager is used to load entities which are modified. The EntityManager tracks all changes
at the end of every request the EntityManager is flushed and committed
Yes this is not ideal, but I did not create this system and it's simple enough to allow us to work within it's confines - I'm not looking to change it without good reason.
I am not used to commit-all-tracked-entities-on-flush behavior and so have been doing something like:
//change entity
if(ovalValidator.isValid(entity))
em.persist(entity);
I need to fix this to work with my new understanding and switching the above to this seems to work:
//change entity
if(!ovalValidator.isValid(entity))
em.detach(entity);
My question
It is my understanding that this just removes the entity from the flush queue even if it IS marked as dirty. Is this correct? Is there a better way to achieve what I am trying to (don't save changes to that entity)? Is there anything I need to look out for if I'm doing this?
detache removes the entity from the session (changeTracking, lazyloading, ...) it does what you want. You could also implement en interceptor removing the dirty mark of the invalid entities but i think your solution would work as well
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.
Working with JPA / Hibernate in an OSIV Web environment is driving me mad ;)
Following scenario: I have an entity A that is loaded via JPA and has a collection of B entities. Those B entities have a required field.
When the user adds a new B to A by pressing a link in the webapp, that required field is not set (since there is no sensible default value).
Upon the next http request, the OSIV filter tries to merge the A entity, but this fails as Hibernate complains that the new B has a required field is not set.
javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value
Reading the JPA spec, i see no sign that those checks are required in the merge phase (i have no transaction active)
I can't keep the collection of B's outside of A and only add them to A when the user presses 'save' (aka entitymanager.persist()) as the place where the save button is does not know about the B's, only about A.
Also A and B are only examples, i have similar stuff all over the place ..
Any ideas? Do other JPA implementaions behave the same here?
Thanks in advance.
I did a lot reading and testing. The problem come from my misunderstanding of JPA / Hibernate. merge() always does a hit on the DB and also schedules an update for the entity. I did not find any mention of this in the JPA spec, but the 'Java Persistence with Hibernate' book does mention it.
Looking through the EntityManager (and Session as fallback) API it looks as if there is no means of just assigning an entity to the current persistent context WITHOUT scheduling an update. After all, what I want is to navigate the object graph, changing properties as needed and trigger an update (with version check if needed) later on. Something i think every Webapp out there using ORM must do?
The basic workflow i 'm looking for:
load an entity from the DB (or create a new one)
let the entity (and all its associations become detached (as the EntitManager closes at the end of a HTTP request)
when the next HTTP request comes in, work again with those objects, navigating the tree without fear of LazyInitExceptions
call a method that persists all changes made during 1-3)
With the OSIV filter from spring in conjunction with an IModel implementation from wicket i thought i have archived this.
I basically see 2 possible ways out of it:
a) load the entity and all the associations needed when entering a certain page (use case), letting them become detached, adding/ changing them as needed in the course of several http requests. Than reattach them when the user initiates a save (validators will ensure a valid state) and submit them to the database.
b) use the current setup, but make sure that all newly added entities have all their required fields set (probably using some wizard components). i would still have all the updates to the database for every merge(), but hopefully the database admin won't realize ;)
How do other people work with JPA in a web environment? Any other options for me?
We are using Hibernate Spring MVC with OpenSessionInView filter.
Here is a problem we are running into (pseudo code)
transaction 1
load object foo
transaction 1 end
update foo's properties (not calling session.save or session.update but only foo's setters)
validate foo (using hibernate validator)
if validation fails ?
go back to edit screen
transaction 2 (read only)
load form backing objects from db
transaction 2 end
go to view
else
transaction 3
session.update(foo)
transaction 3 end
the problem we have is if the validation fails
foo is marked "dirty" in the hibernate session (since we use OpenSessionInView we only have one session throughout the http request), when we load the form backing objects (like a list of some entities using an HQL query), hibernate before performing the query checks if there are dirty objects in the session, it sees that foo is and flushes it, when transaction 2 is committed the updates are written to the database.
The problem is that even though it is a read only transaction and even though foo wasn't updated in transaction 2 hibernate doesn't have knowledge of which object was updated in which transaction and doesn't flush only objects from that transaction.
Any suggestions? did somebody ran into similar problem before
Update: this post sheds some more light on the problem: http://brian.pontarelli.com/2007/04/03/hibernate-pitfalls-part-2/
You can run a get on foo to put it into the hibernate session, and then replace it with the object you created elsewhere. But for this to work, you have to know all the ids for your objects so that the ids will look correct to Hibernate.
There are a couple of options here. First is that you don't actually need transaction 2 since the session is open you could just load the backing objects from the db, thus avoiding the dirty check on the session. The other option is to evict foo from the session after it is retrieved and later use session.merge() to reattach it when you what your changes to be stored.
With hibernate it is important to understand what exactly is going on under the covers. At every commit boundary it will attempt to flush all changes to objects in the current session regardless of whether or not the changes where made in the current transaction or any transaction at all for that matter. This is way you don't actually need to call session.update() for any object that is already in the session.
Hope this helps
There is a design issue here. Do you think an ORM is a transparent abstraction of your datastore, or do you think it's a set of data manipulation libraries? I would say that Hibernate is the former. Its whole reason for existing is to remove the distinction between your in-memory object state and your database state. It does provide low-level mechanisms to allow you to pry the two apart and deal with them separately, but by doing so you're removing a lot of Hibernate's value.
So very simply - Hibernate = your database. If you don't want something persisted, don't change your persistent objects.
Validate your data before you update your domain objects. By all means validate domain objects as well, but that's a last line of defense. If you do get a validation error on a persistent object, don't swallow the exception. Unless you prevent it, Hibernate will do the right thing, which is to close the session there and then.
What about using Session.clear() and/or Session.evict()?
What about setting singleSession=false on the filter? That might put your operations into separate sessions so you don't have to deal with the 1st level cache issues. Otherwise you will probably want to detach/attach your objects manually as the user above suggests. You could also change the FlushMode on your Session if you don't want things being flushed automatically (FlushMode.MANUAL).
Implement a service layer, take a look at spring's #Transactional annotation, and mark your methods as #Transactional(readOnly=true) where applicable.
Your flush mode is probably set to auto, which means you don't really have control of when a DB commit happens.
You could also set your flush mode to manual, and your services/repos will only try to synchronize the db with your app when you tell them to.