Impossible de sérialiser la cause de l’object HibernateProxy

Je reçois la réponse d’erreur suivante du serveur.

Statut HTTP 500 –

type Rapport d’exception

message

descriptionLe serveur a rencontré une erreur interne () qui l’empêchait de répondre à cette requête.

exception

javax.servlet.ServletException: java.lang.UnsupportedOperationException: tentative de sérialisation de java.lang.Class: org.hibernate.proxy.HibernateProxy. Vous avez oublié d’enregistrer un adaptateur de type?

cause première

java.lang.UnsupportedOperationException: Vous avez tenté de sérialiser java.lang.Class: org.hibernate.proxy.HibernateProxy. Vous avez oublié d’enregistrer un adaptateur de type?

À partir du débogueur Java:

org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer@7632012e 

J’utilise Gson pour convertir mes objects Java en JSON. Ci-dessous, j’ai collé une partie de mon code.

Ceci est ma ressource:

 @Stateless @LocalBean @Path("/autos") @Produces(MediaType.APPLICATION_JSON) public class AutoResource { @EJB private CarAssembler warehouse; @Context private UriInfo uriInfo; @GET public Response allAutos() { // Building a context, lots of code... // Creating a Gson instance and configures it... final Auto auto = warehouse.list(context); final Ssortingng autoJson = gson.toJson(auto); return Response.ok(autoJson).build(); } } 

CarAssembler est juste un service qui appelle un référentiel. Je n’ai pas collé le code du service ici.

Dépôt:

 @Override public Question findById(final int id, final FetchType fetchType) { final Auto question = getEntityManager().find(Auto.class, id); if (fetchType == FetchType.LAZY) { return auto; } Hibernate.initialize(auto.getManufacturer()); Hibernate.initialize(auto.getAssemblyHouse()); return auto; } 

Comme vous le voyez, je fournis des objects à la fois paresseux et impatients. J’utilise Hibernate.initialize pour rechercher des associations JPA. Cependant, la question est de savoir comment résoudre l’erreur de proxy que je reçois. Pourquoi venir il n’y a que AssemblyHouse qui est toujours attaché à JavaAssist, alors que Manufacturer n’est pas (j’ai vu le type dans Java Debugger). Comment savoir quand désoxyder des objects? Dois-je désoxyder toutes les associations que cette auto peut avoir? Et dans quelle couche de mon code? Cela affecte-t-il les performances de mon application lorsque je me dégage? Y a-t-il d’autres solutions? Je vois dans le message d’erreur que je peux créer un adaptateur de type. Oui, mais je dois le faire pour tous les objects du domaine pour être sûr que la conversion est correctement effectuée. Peut-être que d’autres objects de mon domaine commencent à échouer lorsque j’essaie de les convertir en représentation JSON, mais je ne sais pas quand ni pourquoi. Est-ce juste la chance que les autres objects vont bien?

C’est comme ça que je dépouille les objects, mais je ne l’ai pas encore implémenté, car je ne sais pas si c’est bon ou mauvais, et dans quelle couche le faire et quand je devrais le faire. Dois-je désoxyder les objects tout le temps?

 public class HibernateUtilities { public static  T unproxy(T proxy) { if (proxy == null) { return null; } if (proxy instanceof HibernateProxy) { Hibernate.initialize(proxy); HibernateProxy hibernateProxy = (HibernateProxy) proxy; T unproxiedObject = (T) hibernateProxy.getHibernateLazyInitializer().getImplementation(); return unproxiedObject; } return proxy; } } 

Stacktrace comme demandé:

 [# | 2012-11-22T17: 17: 13.285 + 0100 | ATTENTION | glassfish3.1.2 | javax.enterprise.system.container.web.com.sun.enterprise.web | _ThreadID = 71; _ThreadName = Thread-8; | StandardWrapperValve [javax.ws.rs.core.Application]:
 PWC1406: Servlet.service () pour servlet javax.ws.rs.core.Application
 java.lang.UnsupportedOperationException: a tenté d'exécuter
 sérialiser java.lang.Class: org.hibernate.proxy.HibernateProxy.  Oublié
 enregistrer un adaptateur de type?
     à com.google.gson.internal.bind.TypeAdapters $ 1.write (TypeAdapters.java:64)
     à com.google.gson.internal.bind.TypeAdapters $ 1.write (TypeAdapters.java:61)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.ArrayTypeAdapter.write (ArrayTypeAdapter.java:93)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ 1.write (ReflectiveTypeAdapterFactory.java:89)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ Adapter.write (ReflectiveTypeAdapterFactory.java:195)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ 1.write (ReflectiveTypeAdapterFactory.java:89)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ Adapter.write (ReflectiveTypeAdapterFactory.java:195)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ 1.write (ReflectiveTypeAdapterFactory.java:89)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ Adapter.write (ReflectiveTypeAdapterFactory.java:195)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.CollectionTypeAdapterFactory $ Adapter.write (CollectionTypeAdapterFactory.java:96)
     à l'adresse com.google.gson.internal.bind.CollectionTypeAdapterFactory $ Adapter.write (CollectionTypeAdapterFactory.java:60)
     à l'adresse com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write (TypeAdapterRuntimeTypeWrapper.java:68)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ 1.write (ReflectiveTypeAdapterFactory.java:89)
     à l'adresse com.google.gson.internal.bind.ReflectiveTypeAdapterFactory $ Adapter.write (ReflectiveTypeAdapterFactory.java:195)
     à com.google.gson.Gson.toJson (Gson.java:586)
     sur com.google.gson.Gson.toJson (Gson.java:565)
     à com.google.gson.Gson.toJson (Gson.java:520)
     à com.myapp.AutoResource.produceAuto (AutoResource.java:48)
     at sun.reflect.NativeMethodAccessorImpl.invoke0 (méthode native)
     at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:57)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke (Method.java:601)
     at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod (EJBSecurityManager.java:1052)
     at org.glassfish.ejb.security.application.EJBSecurityManager.invoke (EJBSecurityManager.java:1124)
     at com.sun.ejb.containers.BaseContainer.invokeBeanMethod (BaseContainer.java:5388)
     at com.sun.ejb.EjbInvocation.invokeBeanMethod (EjbInvocation.java:619)
     at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext (InterceptorManager.java:800)
     à com.sun.ejb.EjbInvocation.proceed (EjbInvocation.java:571)
     at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.doAround (SystemInterceptorProxy.java:162)
     at com.sun.ejb.containers.interceptors.SystemInterceptorProxy.aroundInvoke (SystemInterceptorProxy.java:144)
     at sun.reflect.NativeMethodAccessorImpl.invoke0 (méthode native)
     at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:57)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke (Method.java:601)
     at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept (InterceptorManager.java:861)
     at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext (InterceptorManager.java:800)
     at com.sun.ejb.containers.interceptors.InterceptorManager.intercept (InterceptorManager.java:370)
     à intercept com.sun.ejb.containers.BaseContainer .__ (BaseContainer.java:5360)
     à l'adresse com.sun.ejb.containers.BaseContainer.intercept (BaseContainer.java:5348)
     at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke (EJBLocalObjectInvocationHandler.java:214)
     à com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke (EJBLocalObjectInvocationHandlerDelegate.java:89)
     at sun.reflect.NativeMethodAccessorImpl.invoke0 (méthode native)
     at sun.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:57)
     at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43)
     at java.lang.reflect.Method.invoke (Method.java:601)
     at com.sun.jersey.spi.container.JavaMethodInvokerFactory $ 1.invoke (JavaMethodInvokerFactory.java:60)
     at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider $ ResponseOutInvoker._dispatch (AbstractResourceMethodDispatchProvider.java:205)
     at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch (ResourceJavaMethodDispatcher.java:75)
     at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept (HttpMethodRule.java:288)
     at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept (ResourceClassRule.java:108)
     at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept (RightHandPathRule.java:147)
     at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept (RootResourceClassesRule.java:84)
     at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest (WebApplicationImpl.java:1469)
     at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest (WebApplicationImpl.java:1400)
     at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest (WebApplicationImpl.java:1349)
     at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest (WebApplicationImpl.java:1339)
     at com.sun.jersey.spi.container.servlet.WebComponent.service (WebComponent.java:416)
     at com.sun.jersey.spi.container.servlet.ServletContainer.service (ServletContainer.java:537)
     at com.sun.jersey.spi.container.servlet.ServletContainer.service (ServletContainer.java:708)
     at javax.servlet.http.HttpServlet.service (HttpServlet.java:770)
     at org.apache.catalina.core.StandardWrapper.service (StandardWrapper.java:1550)
     at org.apache.catalina.core.StandardWrapperValve.invoke (StandardWrapperValve.java:281)
     at org.apache.catalina.core.StandardContextValve .__ invoke (StandardContextValve.java:175)
     at org.apache.catalina.core.StandardContextValve.invoke (StandardContextValve.java)
     at org.apache.catalina.core.StandardPipeline.doInvoke (StandardPipeline.java:655)
     at org.apache.catalina.core.StandardPipeline.invoke (StandardPipeline.java:595)
     à org.apache.catalina.core.StandardHostValve .__ invoke (StandardHostValve.java:161)
     at org.apache.catalina.core.StandardHostValve.invoke (StandardHostValve.java)
     at org.apache.catalina.connector.CoyoteAdapter.doService (CoyoteAdapter.java:331)
     at org.apache.catalina.connector.CoyoteAdapter.service (CoyoteAdapter.java:231)
     at com.sun.enterprise.v3.services.impl.ContainerMapper $ AdapterCallable.call (ContainerMapper.java:317)
     à l'adresse com.sun.enterprise.v3.services.impl.ContainerMapper.service (ContainerMapper.java:195)
     at com.sun.grizzly.http.ProcessorTask.invokeAdapter (ProcessorTask.java:860)
     at com.sun.grizzly.http.ProcessorTask.doProcess (ProcessorTask.java:757)
     à l'adresse com.sun.grizzly.http.ProcessorTask.process (ProcessorTask.java:1056)
     à com.sun.grizzly.http.DefaultProtocolFilter.execute (DefaultProtocolFilter.java:229)
     à com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter (DefaultProtocolChain.java:137)
     à com.sun.grizzly.DefaultProtocolChain.execute (DefaultProtocolChain.java:104)
     à com.sun.grizzly.DefaultProtocolChain.execute (DefaultProtocolChain.java:90)
     sur com.sun.grizzly.http.HttpProtocolChain.execute (HttpProtocolChain.java:79)
     at com.sun.grizzly.ProtocolChainContextTask.doCall (ProtocolChainContextTask.java:54)
     à com.sun.grizzly.SelectionKeyContextTask.call (SelectionKeyContextTask.java:59)
     at com.sun.grizzly.ContextTask.run (ContextTask.java:71)
     at com.sun.grizzly.util.AbstractThreadPool $ Worker.doWork (AbstractThreadPool.java:532)
     at com.sun.grizzly.util.AbstractThreadPool $ Worker.run (AbstractThreadPool.java:513)
     at java.lang.Thread.run (Thread.java:722) | #]

Vous pouvez faire sans tout défaire manuellement en utilisant un TypeAdapter personnalisé. Quelque chose dans ce sens:

 /** * This TypeAdapter unproxies Hibernate proxied objects, and serializes them * through the registered (or default) TypeAdapter of the base class. */ public class HibernateProxyTypeAdapter extends TypeAdapter { public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @Override @SuppressWarnings("unchecked") public  TypeAdapter create(Gson gson, TypeToken type) { return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter) 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; } // Resortingeve 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); } } 

Pour l’utiliser, vous devez d’abord l’enregistrer:

 GsonBuilder b = new GsonBuilder(); ... b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY); ... Gson gson = b.create(); 

Notez que cela initialisera de manière récursive chaque proxy que vous avez dans la hiérarchie des objects; car toutefois vous devez sérialiser l’intégralité des données, vous auriez dû le faire de toute façon.

Comment cela marche-t-il?

GSON contient un certain nombre d’ TypeAdapterFactory de TypeAdapterFactory , pour différents types (types primitifs, types communs tels que Ssortingng ou Date , listes, tableaux …). On demande à chaque fabrique si elle est capable de sérialiser un certain type Java (le paramètre à create est un TypeToken au lieu d’une Class afin de capturer des informations possibles sur les types génériques, ce que la Class n’a pas). Si la fabrique est capable de sérialiser / désérialiser un type, elle répond avec une instance de TypeAdapter ; sinon il répond avec null .

HibernateProxyTypeAdapter.FACTORY vérifie si le type implémente HibernateProxy ; dans ce cas, il retourne une instance de HibernateProxyTypeAdapter pour la sérialisation. La méthode write est appelée lorsqu’un object doit être sérialisé; L’adaptateur extrait le type d’origine de l’object sous-jacent et demande à GSON le TypeAdapter standard pour le type d’origine, qui est généralement un ReflectiveTypeAdapter .

Ensuite, il récupère une instance de la classe d’origine, au lieu d’utiliser directement le proxy. Cela est nécessaire car ReflectiveTypeAdapter accède directement aux champs , au lieu d’utiliser des getters; accéder aux champs d’un object traité par proxy ne fonctionne pas et constitue un piège d’Hibernate classique.

En tant TypeAdapter amélioration des performances, le TypeAdapter délégué doit être acquis dans la méthode create . J’ai découvert que l’appel de getSuperclass() sur la Class proxy semble produire la classe de base d’origine. Le code peut alors devenir:

 public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { @Override @SuppressWarnings("unchecked") public  TypeAdapter create(Gson gson, TypeToken type) { return (HibernateProxy.class.isAssignableFrom(type.getRawType()) ? (TypeAdapter) new HibernateProxyTypeAdapter((TypeAdapter)gson.getAdapter(TypeToken.get(type.getRawType().getSuperclass()))) : null); } }; private final TypeAdapter delegate; private HibernateProxyTypeAdapter(TypeAdapter 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()); } 

Dans les cas habituels, vous ne voulez pas que vos objects de domaine soient exposés au format XML / JSON via des services, vous devez souvent créer un DTO car votre entité ne répond pas aux besoins de votre consommateur. Et même si cela se fait maintenant, après le refactoring interne de la firebase database, il ne sera plus adapté demain. Donc, mon conseil serait de créer des DTO dès maintenant si vous rencontrez un tel problème. BTW, vous pouvez créer ces DTO même au niveau Hibernate en utilisant des transformateurs de résultats ou en créant des vues et en mappant des entités Hibernate sur ces vues.

Une autre astuce serait d’utiliser Dozer afin de copier les champs nécessaires dans l’autre classe (qui est en fait la même classe, mais sans proxy).

Et une note: vous utilisez Gson, qui accède à vos champs , au lieu des accesseurs, cela rend impossible de travailler avec le proxy Hibernate car il essaiera d’accéder aux champs de proxy lui-même qui sont toujours null .

Voyant que vous avez mentionné que l’erreur persiste avec un chargement rapide, le problème n’est probablement pas tellement Hibernate, mais peut-être l’implémentation GSON. Je pense que vous aurez besoin d’un Type lors de la création de votre JSON, pas sûr s’il a été enregistré, mais peut-être quelque chose comme ceci:

 public Ssortingng autosToJson(Auto autos) { GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = gsonBuilder.registerTypeAdapter(Auto.class, new AutoAdapter()).create(); return gson.toJson(autos); } 

Ensuite, créez simplement un AdapterClass, par exemple:

 public class AutoAdapter implements JsonSerializer { @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().toSsortingng()); return jsonObject; } } 

Oui, vous pouvez simplement désoxyder tout le temps, s’il a un HibernateProxy (qui ne se sérialisera pas), il disparaîtra et sera remplacé par l’implémentation sous-jacente réelle, ou il laissera la classe telle quelle et vous implémentera. Je pense que votre solution devrait fonctionner correctement. Remarquez que je n’utilise pas beaucoup Hibernate, mais cela a du sens pour moi.

D’un autre côté, vous pourriez faire confiance à Hibernate, mais une méthode plus simple pourrait être:

 Hibernate.getClass(obj); 

Cette solution ne doit pas vous donner de classes implémentées / initialisées, juste la classe, ou cette fonction devrait être proposée par:

 HibernateProxyHelper.getClassWithoutInitializingProxy(superClass) 

Je crois que ce dernier pourrait renvoyer la super classe, donc vous pourriez commencer par Hibernate.getClass (obj);

aussi:

 public static  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; } 

Le code ci-dessus a été emprunté à: Conversion de proxy Hibernate en object réel Les noms de variables qu’il contient sont probablement meilleurs, car ils n’impliquent pas que l’entité est toujours un proxy. En outre, il va lancer une exception pour vous avertir, mais c’est à vous de décider si vous voulez ou non l’exception.

Bien sûr, vous pouvez également vous débarrasser de la charge paresseuse, mais je ne pense pas que ce soit la meilleure solution.

Je rencontrais ce problème quand je suis tombé sur ce post, ce qui m’a orienté dans la bonne direction pour ma situation. J’ai réalisé que je n’avais pas besoin de sérialiser l’entité entière, précisément pour la raison même pour laquelle j’ai marqué certains champs comme étant chargés paresseux. J’ai donc cherché un moyen de sauter ces champs et ExclusionStrategy était magique. Cela semble avoir résolu mon problème

 public class ExcludeProxiedFields implements ExclusionStrategy{ @Override public boolean shouldSkipField(FieldAtsortingbutes fa) { return fa.getAnnotation(ManyToOne.class) != null || fa.getAnnotation(OneToOne.class) != null || fa.getAnnotation(ManyToMany.class) != null || fa.getAnnotation(OneToMany.class) != null ; } @Override public boolean shouldSkipClass(Class type) { return false; } } 

Ensuite, j’ai appliqué cette classe à GsonBuilder comme ceci:

 Gson gson = new GsonBuilder().setExclusionStrategies(new ExcludeProxiedFields()).create();