Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hibernate Entity proxy initialization

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 handlereven 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!!

like image 446
user2054927 Avatar asked Oct 02 '14 16:10

user2054927


2 Answers

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.

like image 60
przemek hertel Avatar answered Oct 05 '22 22:10

przemek hertel


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).

like image 28
Vlad Mihalcea Avatar answered Oct 05 '22 23:10

Vlad Mihalcea