Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grails: simulate a hibernate proxy for testing

When using grails with a domain class hierarchy similar to the following:

abstract class Vehicle { ... }
class Car extends Vehicle { ... }
class Motorcycle extends Vehicle { ... }

and a service as the following:

class VehicleService {
    def startRepairing(Car car) { ... }
    def startRepairing(Motorcycle motorcycle) { ... }
}

We very frequently we face errors as the following in production:

No signature of method: VehicleService.startRepairing() is applicable for argument types: (Car_$$_javassist_156) values: [Id: 42343, Class: Car]. Possible solutions: startRepairing(Car)

We believe this happens because we retrieve the Vehicle instance from a collection such as static hasMany = [vehicles: Vehicle], which causes the proxy to implement the abstract class Vehicle but not the concrete class (Car, Motorcycle, etc).

We used to remove the argument type from the method as a solution, but we would rather have it - code is cleaner, method overload is possible, more IDE friendly...

One solution we thought about is to use the infamous GrailsHibernateUtil.unwrapIfProxy when the type doesn't match any other method:

class VehicleService {
    def startRepairing(Vehicle vehicle) { 
        startRepairing(GrailsHibernateUtil.unwrapIfProxy(vehicle)) 
    }
    def startRepairing(Car car) { 
        /* actual business logic here */ 
    }
    def startRepairing(Motorcycle motorcycle) { 
        /* actual business logic here */ 
    }
}

But then the question is, how can we test this? When running code in development we very rarely find the javassist problem, and even in production it seems to happen "randomly" (or more precisely, due to conditions that escape our knowledge :).

Is it possible to force an instance to be a javassist proxy? What would be a good strategy for these kind of problems in general?

like image 320
Deigote Avatar asked Dec 10 '14 11:12

Deigote


Video Answer


1 Answers

Hibernate creates proxies when you need a class instance that is lazily loaded. You need something that is an instance of or subclass of the expected class, and that once fully loaded, it basically acts like an eagerly loaded instance would. Hibernate's approach of using a bytecode library to create a subclass of your classes to use as proxies works well.

So for a 1-1 and for the 'one' side of a 1-many, the lazy-loaded instance will be a proxy. Also in lazy-loaded collections that are "extra-lazy", the instances will all be proxies. This works better when you know you'll only need data from some of the collection - to "populate" the collection when it needs to be loaded on-demand, the query only looks for ids, and the instances in the collections will be proxies with only the id stored. If you loop through the whole collection you' end up running it's N+1 queries, but if you only need a few, then in general it should have been less resource-intensive than loading all of the data for all instances when the collection is populated, and creating non-proxy collection members if only some are needed.

One other easy place where you see proxies is with the load() method. get() looks in the 1st and 2nd (if active and enabled for the domain class) for previously loaded values and goes to the database immediately otherwise, returning null if there's no record for that id. It doesn't throw an exception since it's easy to know if it succeeded. load() however only hits the database if you access a property other than id. If there's no record, an exception is thrown both because you are not necessarily near (time-wise or code-wise) the initial load() call that created the proxy, and also because there's an implicit assumption that by lazy-loading, you're expecting a result, so a null is exceptional in this case.

like image 104
Burt Beckwith Avatar answered Oct 12 '22 20:10

Burt Beckwith