Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HIbernate proxies are not unwrapped for lazy fetched inherited domain objects #1468

Closed
cip123 opened this issue Jun 14, 2021 · 3 comments
Closed

Comments

@cip123
Copy link

cip123 commented Jun 14, 2021

Hi, we are working on updating an application from Grails 2.5.6 to Grails 4.0.10 and the automatic unwrapping of proxies seem to no longer work for inherited classes.

This seems to be an old issue which was first reported here, #1072 . It happened because this line was commented.

#/grails-datastore-gorm-hibernate5/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java#L2686

 HibernateUtils.handleLazyProxy(domainClass, grailsProperty);

The issue was fixed with this commit grails/gorm-hibernate5@5c4e6ed

But then it resurfaced, the same line got commented again https://github.com/grails/gorm-hibernate5/blob/9512dbcb91164776acabf128401f235c93b493be/grails-datastore-gorm-hibernate5/src/main/groovy/org/grails/orm/hibernate/cfg/GrailsDomainBinder.java#L2686

Steps to Reproduce

Given the domain classes :

class Event  {
  Person person
}

class Person {
}

class Adult extends Person {
    isMarried() {
        return true
    }
}

Where event.person is a lazy loaded property.

Call event.person.isMarried()

Expected Behaviour

Grails should cast unwrap the Proxy (which wraps an Adult class) before calling the method. This was the actual behavior in grails 2.5.6

Actual Behaviour

Grails throws java.lang.ClassCastException: com.aaa.domain.Person$HibernateProxy$fn2kTwKv cannot be cast to com.aaa.domain.Adult

Environment Information

  • Operating System: MacOS 11.3.1
  • GORM Version: 7.0.8
  • Grails Version (if using Grails): 7.0.4
  • JDK Version: 1.8.0
@osscontributor
Copy link
Member

osscontributor commented Jul 15, 2021

See the app at https://github.com/jeffbrown/issue1468. That may help while troubleshooting.

grails-app/domain/issue1468/Person.groovy

package issue1468

class Person {
    String name

    boolean isMarried() {
        false
    }
}

grails-app/domain/issue1468/Adult.groovy

package issue1468

class Adult extends Person {

    boolean isMarried() {
        true
    }
}

grails-app/domain/issue1468/Event.groovy

package issue1468

class Event {
    Person person
}

grails-app/init/issue1468/BootStrap.groovy

package issue1468

class BootStrap {

    def init = { servletContext ->
        new Event(person: new Adult(name: 'Jeff')).save()
    }
    def destroy = {
    }
}

grails-app/controllers/issue1468/DemoController.groovy

package issue1468

import org.grails.orm.hibernate.cfg.GrailsHibernateUtil

class DemoController {

    def index() {
        def event = Event.first()
        // this works
//        render GrailsHibernateUtil.unwrapIfProxy(event.person).isMarried()

        // This will throw ClassCastException
        render event.person.isMarried()
    }
}

@JasonTypesCodes
Copy link
Member

We've reviewed this issue and have determined that GORM is behaving as expected in this situation.

One of the changes in GORM for Hibernate 7, is that it no longer creates custom proxy factories. It also no longer automatically unwraps Hibernate proxies. This change makes the behavior in GORM more consistent with standard Hibernate.

While the change was documented in the GORM for Hibernate Upgrade Notes we did find that our documentation needed to be updated regarding this change including guidance for situations where inheritance is involved such as the example in this issue.

The updated documentation will be published with the next release of GORM.

For the example above, there are two ways to approach this:

1 - If you know in advance that you will be accessing the person attribute, you can explicitly join the attribute in making the proxy unnecessary

def event = Event.where { id == 1 }.join("person")[0]

2 - If you are not sure if the association will be used, the proxy can be manually unwrapped by injecting an instance of ProxyHandler.

proxyHandler.unwrap(event.person)

@miq
Copy link

miq commented Jul 26, 2021

@JasonTypesCodes : I wonder why you prefer exposing the leaky abstraction of hibernate and it's proxies to helping your users by hiding these implementation details?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants