Issue Details (XML | Word | Printable)

Key: EJB-46
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Major Major
Assignee: Emmanuel Bernard
Reporter: Johan Steiner
Votes: 8
Watchers: 10
Operations

If you were logged in you would be able to see more operations.
z - Hibernate Entity Manager

PrePersist callback method not called if entity's primary key is null

Created: 07/Aug/05 12:17 PM   Updated: 25/Sep/08 07:55 AM   Resolved: 14/Feb/07 01:45 PM
Component/s: EntityManager
Affects Version/s: 3.1beta1, 3.1beta2
Fix Version/s: 3.3.0.ga

Time Tracking:
Not Specified

File Attachments: 1. Text File EJB3EventListener.patch (7 kB)
2. Text File EJB3EventListener_v2.patch (8 kB)

Environment: MySQL4, Sun JRE5, WinXP

Bug Testcase Reminder (view):
REMINDER: Bug reports should generally be accompanied by a test case
Participants: Brian Curnow, Emmanuel Bernard, Grant Quimby, Johan Steiner, Mark Menard and Michał Borowiecki


 Description  « Hide

Hi,

the description is from http://forum.hibernate.org/viewtopic.php?t=944964 but I'm experiencing the exact same issue.

*********************
Hibernate does property validation such as not null checking before it does the EJB3 callback to prepersist/preupdate. I'm not sure if there's a good reason for this, but I think it would be particularly convenient if this behavior was reversed. IMHO it seems to better fit the semantics of the PRE callbacks, and it would allow callbacks to make modifications to the objects before they are persisted or updated – modifications that might in turn effect the property validation Hibernate is doing.

The "audit" example in the entity manager documentation does make changes to the object. What if these changes had effected the property validation done before the callback occurred? What if the object was in an invalid state before the callback, but a valid state after the callback? The latter case is what I think would be conveniently handled if hibernate did its property validation after prepersist/preupdate.

Just two cents worth, obviously there are workarounds. This EJB3 stuff is looking great.

Ryan

P.S. This might also allow those of us who assign our own IDs to objects to do so automatically within a callback.
*********************

In my case I'm working with an entity like:

public class MyEntity
{
@Basic(temporalType = TemporalType.TIMESTAMP)
@Column(name = "$createdOn", insertable = true, updatable = false, nullable = false)
private Date firstPersistedOn = null;

@Basic(temporalType = TemporalType.TIMESTAMP)
@Column(name = "$modifiedOn", insertable = true, updatable = false, nullable = true)
private Date lastPersistedOn = null;

@PrePersist
public void onPrePersist()

{ firstPersistedOn = new Date(); }

@PreUpdate
public void onPreUpdate()

{ lastPersistedOn = new Date(); }

}

Hibernate throws:

org.hibernate.PropertyValueException: not-null property references a null or transient value: MyEntity.firstPersistedOn
at org.hibernate.engine.Nullability.checkNullability(Nullability.java:72)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:262)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:164)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:114)
at org.hibernate.event.def.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:167)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:113)
at org.hibernate.event.def.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:60)
at org.hibernate.impl.SessionImpl.merge(SessionImpl.java:540)
at org.hibernate.ejb.AbstractEntityManagerImpl.merge(AbstractEntityManagerImpl.java:139)

Regards,
Johan



Grant Quimby added a comment - 11/May/06 07:34 PM

Hi,

This is also a problem with with my current project. And is also located at

http://jira.jboss.com/jira/browse/EJBTHREE-553

Anybody know of any fixes or workarounds.

Thanks

Grant


Michał Borowiecki added a comment - 13/May/06 05:00 PM

ebj3 event listeners currently perform lifecycle callbacks in the overriden invokeSaveLifecycle method, which is only invoked after validation has succeeded. I moved the callback invocation before validation by overriding the saveWithGeneratedId method. This seems to have fixed the issue. I attach the patch.

From what I could determine:
The EJB3PersistEventListener is invoked when an enitity is persisted by a call to EntityManager.persist
The EJB3MergeEventListener is invoked when an entity is persisted by a call to EntityManager.merge on some other entity which has been set to cascade on its relationship.

I've also modified EJB3SaveEventListener and EJB3SaveOrUpdateEventListener although I couldn't find the circumstances when they're used. However, since they all subclass AbstractSaveEventListener I figured it is better to modify them accordingly.
Also I have overriden the saveWithRequestedId method on all four classes, although again I couldn't establish when the method is called.

As for the PreUpdate callback I think it is called before validation, as it should be.
I set a non-nullible field to null, call merge on the entity and only set the field to a non-null value in PreUpdate and it works.
I did however find a bug in DefaultFlushEntityEventListener that is related, in that it also stems from the assumption that the callbacks do not alter the entity's persistant state. I will create a new issue for it.


Michał Borowiecki added a comment - 13/May/06 05:36 PM

The PreUpdate bug I referred to earlier is now EJB-180.


Mark Menard added a comment - 26/Sep/06 12:42 PM

This is an updated version of Michal's patch. This is done against hibernate-eventmanager-3.2.2.CR2.


Mark Menard added a comment - 26/Sep/06 12:46 PM

Correction to my note on the patch. This is done against hibernate-eventmanager-3.2.0.CR2.


Emmanuel Bernard added a comment - 26/Sep/06 02:46 PM

Hi,
I don't understand what this patch fixes.
This is not the PropertyValueException because the test is done after invokeSaveLifecycle for what I can tell.
Can somebody add a test case failing before the patch and working after it?


Mark Menard added a comment - 26/Sep/06 03:34 PM

Hi Emmanuel,

This patch fixes the following scenario. Create an entity with new (). Do not set the primary key. Call EntityManager.persist() on the new entity with a null primary key. The entity has a method marked with @PrePersist to set the primary key on the entity, but Hibernate checks the primary key before executing the @PrePersist method. Thus it throws an exception like the following:

Caused by: org.hibernate.id.IdentifierGenerationException: ids for this class must be manually assigned before calling save():
16:28:15,411 INFO [STDOUT] net.vitarara.quadran.core.data.jpa.PartyJpaImpl
at org.hibernate.id.Assigned.generate(Assigned.java:33)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:98)
at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:131)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:87)
at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:38)
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:618)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:592)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:596)
at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:212)

If you could give me pointers on how to create a test scenario that would illustrate this I'd be happy to try, but I'll admit I'm a complete Hibernate novice. The only experience I have with Hibernate was downloading Michal's patch, and applying it by hand to 3.2.0.RC2, building, and using the jars thus created.


Brian Curnow added a comment - 31/Oct/06 12:31 PM

This issue (I think) is also affecting Id Classes.

I have an Invoice class which contains a one to many relationship with InvoiceLine, InvoiceLine has a composite key made up of the Invoice Id (sequence generated) and the InvoiceLine Id. I'm using the @IdClass annotation and NOT embedded ids.

I'm trying to use @PostPersist on the Invoice object to update the InvoiceLines with the sequence generated Invoice id. However, it appears that Hibernate is creating the IdClass instance before I've had a chance to update the fields on InvoiceLine:

[DEBUG] AbstractSaveEventListener - generated identifier: component[invoiceId,invoiceLineId]{invoiceLineId=1, invoiceId=null}, using strategy: org.hibernate.id.Assigned

Therefore my @PostPersist method in Invoice has no affect because I can't change the value of invoiceId on the IdClass, only on the InvoiceLine.

If you think this is a separate issue let me know and I'll open another bug.


Emmanuel Bernard added a comment - 14/Feb/07 01:07 PM

Rename case, since the initial description has been fixed a long time ago and the discussion have slipped.


Emmanuel Bernard added a comment - 14/Feb/07 01:46 PM

Note that I still consider such a usage hacky and is not considered a supported behavior (mainly becasue it highly depend on the Hibernate state discovery mechanism).