Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA EntityManager find method returns proxy [duplicate]

I have two entities. One inherited from the other.

Example:

@Entity
@Table(name = "vehicle")
@Inheritance(strategy = InheritanceType.JOINED)
public class VehicleEntity {
    //id, etc., all reference fetch type is LAZY
}

@Entity
@Table(name = "car")
public class CarEntity extends VehicleEntity {
    //special parameters, all reference fetch type is LAZY
}

When I call a find() on EntityManager with an existing car's id like this:

VehicleEntity vehicleEntity = entityManager.find(VehicleEntity.class, carID);

I get back a proxy object, but I need to access the special methods of the CarEntity class, because I would like to set some new parameters.

Can someone help me how can I do this?

Of course this is just an example problem. More specifically: after I call find I'm checking the instance of the returned object, if it is "CarEntity" I set the parameters, if not I don't do anything. I have much more inherited classes beside "CarEntity", and I do the same procedure like before. I know I could solve this with "find"s on the specific object classes, but then I have to call "find" several times, and when I'm looking for any "VehicleEntity" I'm always interested in the actual object so the best would be a global solution.

like image 814
Csaba Avatar asked Dec 25 '22 14:12

Csaba


1 Answers

Consider the following sequence of statements:

VehicleEntity vehicleEntityProxy = entityManager.getReference(VehicleEntity.class, carID);
VehicleEntity vehicleEntityInitialized = entityManager.find(VehicleEntity.class, carID);

The vehicleEntityProxy is a proxy, because you wanted it to be (obtained via getReference).

In the second statement, you want an initialized instance, so you are obtaining it with find:

Find by primary key. Search for an entity of the specified class and primary key. If the entity instance is contained in the persistence context, it is returned from there.

So, find will check whether the instance is already in the persistence context, it will determine that it is because there is already a proxy created in the first statement, it will initialize the proxy (because it delegates to Hibernate Session.get which never returns an uninitialized instance) and will return the proxy.

The similar thing happens if instead of the first statement you have loaded some other entity which has a lazy to-one association to the VehicleEntity with the carID id. Then a proxy is created in place of the real entity instance, and that proxy will be returned (and initialized if not already) from the find method when finding the entity for the same id.

All of this further implies that instanceof is not your friend when working with Hibernate proxies (putting aside that it is not your friend in general when it comes to good OOP code).

As a workaround you could deproxy the object:

if (entity instanceof HibernateProxy) {
  entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}

But this is tedious and error prone (and unnecessarily exposes Hibernate specific API). Also, you may need to do it for all of the associated objects as well since they can be proxies also.

Better approach is to use proven OOP constructs and patterns, like the Visitor pattern:

class Vehicle {
  ...
  public void accept(VehicleVisitor visitor) {
  }
}

class Car extends Vehicle {
  ...
  public void accept(VehicleVisitor visitor) {
    visitor.visit(this);
  }
}

class Bus extends Vehicle {
  ...
  public void accept(VehicleVisitor visitor) {
    visitor.visit(this);
  }
}

interface VehicleVisitor {
  void visit(Car car);
  void visit(Bus bus);
}

Now, instead of:

Vehicle vehicle = entityManager.find(Vehicle.class, vehicleId);
if (vehicle istanceof Car) {
  Car car = (Car) vehicle;
  // Do something with car
}
if (vehicle istanceof Bus) {
  Bus bus = (Bus) vehicle;
  // Do something with bus
}

you can do:

class SomeVehicleVisitor implements VehicleVisitor {
  public void visit(Car car) {
    // Do something with car
  }
  public void visit(Bus bus) {
    // Do something with bus
  }
}

SomeVehicleVisitor visitor = ...
Vehicle vehicle = entityManager.find(Vehicle.class, vehicleId);
vehicle.accept(visitor);
like image 88
Dragan Bozanovic Avatar answered Dec 30 '22 10:12

Dragan Bozanovic