I'm having a problem with a Hibernate entity that does not get initialised.
It seems that it's still returning a not initialised proxy...
If I take a look at my debug info I would expect my entity to be initialised.
But it looks like the following:
entity = {SomeEntity_$$_jvst47c_1e@9192}"SomeEntityImpl@1f3d4adb[id=1,version=0]"
handler = {org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer@9196}
interfaces = {java.lang.Class[2]@9197}
constructed = true
persistentClass = {java.lang.Class@3605}"class SomeEntityImpl"
getIdentifierMethod = null
setIdentifierMethod = null
overridesEquals = true
componentIdType = null
replacement = null
entityName = {java.lang.String@9198}"SomeEntityImpl"
id = {java.lang.Long@9199}"1"
target = {SomeEntityImpl@9200}"SomeEntityImpl@1f3d4adb[guid=<null>,id=1,version=0]"
initialized = true
readOnly = true
unwrap = false
session = {org.hibernate.internal.SessionImpl@6878}"SessionImpl(PersistenceContext[entityKeys=[EntityKey[EntityReferenceImpl#2], EntityKey[SomeEntityImpl#1], EntityKey[...
readOnlyBeforeAttachedToSession = null
sessionFactoryUuid = null
allowLoadOutsideTransaction = false
Notice that my Hibernate POJO still only contains a handler
even after doing an explicit initialisation...
In my debug view, I can see the 'real' property values (not displayed above) when I expand the target
node.
What I'm doing:
EntityReferenceImpl entityReference = findEntityReference(session);
SomeEntity entity = null;
if (entityReference != null) {
// initialize association using a left outer join
HibernateUtil.initialize(entityReference.getSomeEntity());
entity = entityReference.getSomeEntity();
}
return entity;
Notice the HibernateUtil.initialize
call!
SomeEntity
mapping:
public class SomeEntityImpl extends AbstractEntity implements SomeEntity {
@OneToMany(mappedBy = "someEntity", fetch = FetchType.EAGER, targetEntity = EntityReferenceImpl.class, orphanRemoval = true)
@Cascade(CascadeType.ALL)
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<EntityReference> entityReferences = new HashSet<>();
@Target(EntityName.class)
@Embedded
private Name name;
@Target(EntityAddress.class)
@Embedded
private Address address;
...
}
EntityReferenceImpl
mapping:
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = SomeEntityImpl.class)
@JoinColumn(name = "entity_id")
private SomeEntity someEntity;
...
}
So what is the side effect: When the POJO later comes with updated properties I'm still having the same structure (as mentioned above) and I can see the updated properties under the target
node.
But when I'm trying to update the entity using session.merge()
or session.update()
or session.saveOrUpdate()
, Hibernate does not detect the 'dirty' properties and does not invoke an update query to the database.
Does anyone have some clues about this weird behavior? I have tried everything what I can but without any results.
All help is very welcome!!
Entity in your debug window looks like properly initialized.
When you have some entity that may be proxied by hibernate, this entity is stored inside proxy object even after being properly initialized. After initialisation proxy object itself doesn't disappear...
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
@ManyToOne(fetch = FetchType.LAZY, ...)
private SomeEntity someEntity;
...
In your example you have EntityReferenceImpl
entity which has @ManyToOne(LAZY)
to SomeEntity
entity.
When hibernate loads EntityReferenceImpl
it fills all fields from resultSet values but someEntity
field is set to proxy object.
This proxy objects looks like this:
class SomeEntity_$$_javassist_3 extends SomeEntity implements HibernateProxy {
+ firstname = NULL;
+ lastname = NULL;
+ age = 0;
+ handler; //of type: JavassistLazyInitializer
getFirstname() {
handler.invoke(..., Method thisMethod, Method proceed, args);
}
getLastName() {...}
}
Your SomeEntity
class has (for example) methods getFirstName()
etc, but javassist generated class simply extends your SomeEntity
and has few new bytecode-generated methods like c7getFirstName()
etc.
And most important - proxy class has new field: handler
of type JavassistLazyInitializer
.
Lets see how JavassistLazyInitializer
looks like:
JavassistLazyInitializer {
+ target; //holds SomeEntity object
invoke(..., Method thisMethod, Method proceed, args) {
if (target == null) {
target = initialize(); // calls sessionImpl.immediateLoad
}
return thisMethod.invoke( target, args );
}
}
So when you look into your proxy object - it has your fields like firstname
, lastname
etc.
When you initialize this proxy, SomeEntity
is loaded into target field. Your firstname
, lastname
fields on proxy objects are null as before - proxy doesn't use them, but real data is in SomeEntity
object held by target
field.
This is how proxy is implemented in hibernate.
You may ask - why such solution? Such design comes from polymorphism issues. If SomeEntity
would be abstract parent class with 2 subclasses EntityA
and EntityB
hibernate has no problem - someEntity field holds proxy (generated) class extending SomeEntity
but having concrete EntityA
or EntityB
inside target
field.
However there are some pitfalls with this solution and polymorphism. Your someEntity
field will be instance of SomeEntity
but never instance of EntityA
nor instance of EntityB
.
Hibernate uses Proxies to intercept calls to LAZY entities. That structure you see in debug is how a Proxy looks like.
You don't need to call HibernateUtil.initialize
, but simply use "fetch joins" to load all entities you are interested in a single query.
If the entity is attached to the current Session, the dirty checking mechanism will automatically translate all entity state transitions to database DML statements.
Session.update
is meant to re-attach detached entities (entities that were loaded in a Session that's been closed).
Session.merge
is for copying the entity state onto an already loaded entity (which is loaded on the fly, if not loaded previously).
Check if you have enabled transactions, as otherwise you can only select entities. For persist/merge and dirty checking updates you must use transactions (use Java EE or Spring @Transactional
support).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With