You can do without manually unproxying everything by using a custom TypeAdapter
.
Something along these lines:
/**
* This TypeAdapter unproxies Hibernate proxied objects, and serializes them
* through the registered (or default) TypeAdapter of the base class.
*/
public class HibernateProxyTypeAdapter extends TypeAdapter<HibernateProxy> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter<T>) new HibernateProxyTypeAdapter(gson) : null);
}
};
private final Gson context;
private HibernateProxyTypeAdapter(Gson context) {
this.context = context;
}
@Override
public HibernateProxy read(JsonReader in) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
// Retrieve the original (not proxy) class
Class<?> baseType = Hibernate.getClass(value);
// Get the TypeAdapter of the original class, to delegate the serialization
TypeAdapter delegate = context.getAdapter(TypeToken.get(baseType));
// Get a filled instance of the original class
Object unproxiedValue = ((HibernateProxy) value).getHibernateLazyInitializer()
.getImplementation();
// Serialize the value
delegate.write(out, unproxiedValue);
}
}
To use it you must first register it:
GsonBuilder b = new GsonBuilder();
...
b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY);
...
Gson gson = b.create();
Notice that this will recursively initialize every proxy you have in the object hierarchy; since however you have to serialize the whole data, you should have done that anyway.
How does this work?
GSON contains a number of TypeAdapterFactory
implementations, for various types (primitive types, common types like String
or Date
, lists, arrays...). Each factory is asked if it is able to serialize a certain Java type (the parameter to create
is a TypeToken
instead of a Class
in order to capture possible information about generic types, which Class
does not have). If the factory is able to serialize/deserialize a type, it responds with a TypeAdapter
instance; otherwise it responds with null
.
HibernateProxyTypeAdapter.FACTORY
verifies whether type implements HibernateProxy
; in that case, it returns an instance of HibernateProxyTypeAdapter
for serialization.
The write
method is called when an actual object has to be serialized; the adapter extracts the original type of the underlying object, and asks GSON for the standard TypeAdapter
for the original type, which generally is a ReflectiveTypeAdapter
.
Then it retrieves an instance of the original class, instead of directly using the proxy. This is necessary because ReflectiveTypeAdapter
accesses directly to fields, instead of using getters; accessing to the fields of a proxied object does not work, and is a classical Hibernate pitfall.
As a possible performance improvement, the delegate TypeAdapter
should be acquired in the create
method. I found out that calling getSuperclass()
on the proxy Class
appears to yield the original base class. The code can then become:
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@Override
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
return (HibernateProxy.class.isAssignableFrom(type.getRawType())
? (TypeAdapter<T>) new HibernateProxyTypeAdapter((TypeAdapter)gson.getAdapter(TypeToken.get(type.getRawType().getSuperclass())))
: null);
}
};
private final TypeAdapter<Object> delegate;
private HibernateProxyTypeAdapter(TypeAdapter<Object> delegate) {
this.delegate = delegate;
}
@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public void write(JsonWriter out, HibernateProxy value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
delegate.write(out, ((HibernateProxy) value).getHibernateLazyInitializer()
.getImplementation());
}
Seeing as though you mentioned that the error persists with eager loading, then the issue likely is not so much Hibernate, but possibly on the GSON implementation. I think you will need a Type when creating your JSON, not sure if it was registered, but perhaps something like this:
public String autosToJson(Auto autos) {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.registerTypeAdapter(Auto.class, new AutoAdapter()).create();
return gson.toJson(autos);
}
Then just create an AdapterClass, for example:
public class AutoAdapter implements JsonSerializer<Auto> {
@Override
public JsonElement serialize(Auto auto, Type type, JsonSerializationContext jsc) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("auto_id", auto.getId());
jsonObject.addProperty("auto_name", auto.getAutoName());
jsonObject.addProperty("auto__manufacture_date", auto.getManufactureDate().toString());
return jsonObject;
}
}
In usual cases you don't want your domain objects to be exposed as XML/JSON via services, often you need to create a DTO because your Entity does not fit the needs of your consumer. And even if it does now, after internal refactoring of the database, it won't fit tomorrow. So my advice would be to create DTOs right now if you got into such a trouble. BTW, you can create those DTOs even on the Hibernate level by using Result Transformers or creating Views and mapping Hibernate entities onto these Views.
Another trick would be to use Dozer in order to copy needed fields into the other class (which actually is the same class, but without proxy).
And a note: you're using Gson, which access your fields, instead of accessors, this makes it impossible to work with Hibernate proxy because it will try to access the fields of proxy itself which always are null
.
Yes, you can just unproxy all the time, if it has a HibernateProxy (which won't serialize) it will be gone and replaced with the actual underlying implementation, or it will leave the class as is and give you an implementation. I think your solution should work fine. Mind you I don't really use Hibernate much, but it does make sense to me.
On the other hand you might be trusting Hibernate some more, but a simpler method might be:
Hibernate.getClass(obj);
This solution should not give you implemented/initialized classes, just the class, or that function should be offered by:
HibernateProxyHelper.getClassWithoutInitializingProxy(superClass)
I believe the latter might return the super class though, so you might start with Hibernate.getClass(obj);
also:
public static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
throw new
NullPointerException("Entity passed for initialization is null");
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
.getImplementation();
}
return entity;
}
The above code was borrowed from: Converting Hibernate proxy to real object the variable names from it are probably better, as they don't imply the entity is always a proxy. Also it will throw an exception to warn you, but that is up to you whether you want the exception or not.
Of course you can also get rid of lazy load, but I don't think thats the best solution.
Try parsing through ObjectMapper as
final Auto auto = warehouse.list(context);
final String autoJson = new ObjectMapper().writeValueAsString(auto);
return Response.ok(autoJson).build();
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