Подтвердить что ты не робот

Не удалось сериализовать объектную причину HibernateProxy

Я получаю следующий ответ об ошибке с сервера.

Состояние HTTP 500 -

Тип отчета об исключении

сообщение

descriptionСервер обнаружил внутреннюю ошибку(), которая предотвратила это от выполнения этого запроса.

исключения

javax.servlet.ServletException: java.lang.UnsupportedOperationException: попытка сериализации java.lang.Class: org.hibernate.proxy.HibernateProxy. Забыл зарегистрировать адаптер типа?

Основная причина

java.lang.UnsupportedOperationException: Попытка сериализации java.lang.Class: org.hibernate.proxy.HibernateProxy. Забыл зарегистрировать адаптер типа?

От отладчика Java:

[email protected]012e

Я использую Gson для преобразования объектов Java в JSON. Ниже я вставил некоторый код.

Это мой ресурс:

@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 String autoJson = gson.toJson(auto);

        return Response.ok(autoJson).build();
    }
}

CarAssembler - это просто служба, которая вызывает репозиторий. Я не вставлял код службы здесь.

Repository:

@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;
}

Как вы видите, я предоставляю как ленивую, так и нетерпевую загрузку объектов. Я использую Hibernate.initialize, чтобы заинтересовать ассоциации JPA. Однако вопрос заключается в том, как я могу исправить ошибку прокси, которую я получаю. Зачем приходить только AssemblyHouse, который все еще подключен к JavaAssist, в то время как у производителя нет (я видел тип в Java Debugger). Как узнать, когда для объектов unproxy? Должен ли я игнорировать все ассоциации, которые могут иметь это авто? И в каком слое моего кода? Это влияет на производительность моего приложения, когда я не доверяю? Существуют ли другие решения? Из сообщения об ошибке видно, что я могу сделать адаптер типа. Да, я мог бы, но тогда я должен сделать это для всех объектов домена, чтобы убедиться, что преобразование выполнено правильно. Возможно, другие объекты в моем домене начинают сбой, когда я пытаюсь преобразовать его в представление JSON, но я не знаю, когда и почему. Это просто удача, что другие объекты в порядке?

Вот как я делаю непрозрачные объекты, но я еще не реализовал его, потому что я не знаю, хорошо это или плохо, и на каком уровне это делать, и когда я это сделаю. Должен ли я unproxy объекты все время?

public class HibernateUtilities {

    public static <T> 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 по запросу:

[#|2012-11-22T17:17:13.285+0100|WARNING|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() for servlet javax.ws.rs.core.Application
threw exception java.lang.UnsupportedOperationException: Attempted to
serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot
to register a type adapter?
    at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:64)
    at com.google.gson.internal.bind.TypeAdapters$1.write(TypeAdapters.java:61)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ArrayTypeAdapter.write(ArrayTypeAdapter.java:93)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:96)
    at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.write(CollectionTypeAdapterFactory.java:60)
    at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.write(TypeAdapterRuntimeTypeWrapper.java:68)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.write(ReflectiveTypeAdapterFactory.java:89)
    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.write(ReflectiveTypeAdapterFactory.java:195)
    at com.google.gson.Gson.toJson(Gson.java:586)
    at com.google.gson.Gson.toJson(Gson.java:565)
    at com.google.gson.Gson.toJson(Gson.java:520)
    at com.myapp.AutoResource.produceAuto(AutoResource.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    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)
    at 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(Native Method)
    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)
    at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:5360)
    at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:5348)
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandler.invoke(EJBLocalObjectInvocationHandler.java:214)
    at com.sun.ejb.containers.EJBLocalObjectInvocationHandlerDelegate.invoke(EJBLocalObjectInvocationHandlerDelegate.java:89)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    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)
    at 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)
    at 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)
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
    at 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) |#]
4b9b3361

Ответ 1

Вы можете обойтись без ручного удаления всех, используя пользовательский TypeAdapter. Что-то в этом роде:

/**
 * 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);
    }
}

Чтобы использовать его, вы должны сначала зарегистрировать его:

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

Обратите внимание, что это будет рекурсивно инициализировать каждый прокси-сервер, который у вас есть в иерархии объектов; поскольку, тем не менее, вам нужно сериализовать все данные, вы все равно должны были это сделать.

Как это работает?

GSON содержит ряд реализаций TypeAdapterFactory для разных типов (примитивные типы, общие типы, такие как String или Date, списки, массивы...). Каждому factory задается вопрос, может ли он сериализовать определенный тип Java (параметр create является TypeToken вместо Class, чтобы захватить возможную информацию об общих типах, которые Class не иметь). Если factory способен сериализовать/десериализовать тип, он отвечает экземпляром TypeAdapter; в противном случае он отвечает null.

HibernateProxyTypeAdapter.FACTORY проверяет, реализует ли тип HibernateProxy; в этом случае он возвращает экземпляр HibernateProxyTypeAdapter для сериализации. Метод write вызывается, когда фактический объект должен быть сериализован; адаптер извлекает исходный тип базового объекта и запрашивает GSON для стандартного TypeAdapter для исходного типа, который обычно является ReflectiveTypeAdapter.

Затем он извлекает экземпляр исходного класса вместо прямого использования прокси. Это необходимо, потому что ReflectiveTypeAdapter обращается к непосредственно к полям вместо использования геттеров; доступ к полям прокси-объекта не работает и является классическим Hibernate pitfall.

В качестве возможного улучшения производительности делегат TypeAdapter должен быть приобретен в методе create. Я узнал, что вызов getSuperclass() на прокси Class, как представляется, дает исходный базовый класс. Затем код может стать:

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());
}

Ответ 2

В обычных случаях вы не хотите, чтобы ваши объекты домена отображались как XML/JSON через службы, часто вам нужно создать DTO, потому что ваше Entity не соответствует потребностям вашего потребителя. И даже если это произойдет сейчас, после внутреннего рефакторинга базы данных, он не будет соответствовать завтра. Поэтому мой совет - создать DTO прямо сейчас, если у вас возникнут такие проблемы. BTW, вы можете создавать эти DTO даже на уровне Hibernate, используя Transformers результата или создавая представления и сопоставляя объекты Hibernate с этими представлениями.

Другим трюком будет использование Dozer, чтобы скопировать нужные поля в другой класс (который фактически является тем же классом, но без прокси).

И примечание: вы используете Gson, который обращается к вашим полям, а не к ним, это делает невозможным работу с прокси-сервером Hibernate, потому что он попытается получить доступ к полям самого прокси-сервера, которые всегда являются null.

Ответ 3

Увидев, что вы упомянули, что ошибка сохраняется при активной загрузке, проблема, вероятно, заключается не столько в Hibernate, а, возможно, в реализации GSON. Я думаю, вам понадобится Тип при создании вашего JSON, не уверен, что он был зарегистрирован, но, возможно, что-то вроде этого:

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

Затем просто создайте AdapterClass, например:

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

Ответ 4

Да, вы можете просто unproxy все время, если у него есть HibernateProxy (который не будет сериализоваться), он будет удален и заменен фактической базовой реализацией, или он оставит класс как есть и даст вам реализацию, Я думаю, ваше решение должно работать нормально. Имейте в виду, что я действительно не использую Hibernate, но это имеет смысл для меня.

С другой стороны, вы можете доверять Hibernate еще, но более простой метод может быть:

Hibernate.getClass(obj);

Это решение не должно давать вам реализованные/инициализированные классы, просто класс или эта функция должна предлагаться:

HibernateProxyHelper.getClassWithoutInitializingProxy(superClass)

Я считаю, что последний может вернуть суперкласс, поэтому вы можете начать с Hibernate.getClass(obj);

и

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

Вышеприведенный код был заимствован из: Преобразование прокси-сервера Hibernate в реальный объект имена переменных из него, вероятно, лучше, поскольку они не подразумевают, что сущность всегда является прокси. Также он выдаст исключение, чтобы предупредить вас, но это зависит от вас, хотите ли вы исключение или нет.

Конечно, вы также можете избавиться от ленивой нагрузки, но я не думаю, что это лучшее решение.

Ответ 5

У меня возникла эта проблема, когда я столкнулся с этим сообщением, в котором я указал в правильном направлении для моей ситуации. Я понял, что мне не нужно сериализовать весь объект, особенно по той причине, что я отметил некоторые поля, которые будут ленивы загружаться. Поэтому я попытался найти способ пропустить эти поля, и ExclusionStrategy был волшебным. Это, похоже, устранило мою проблему.

public class ExcludeProxiedFields implements ExclusionStrategy{

    @Override
    public boolean shouldSkipField(FieldAttributes 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;
    }   
}

Затем я применил этот класс к GsonBuilder следующим образом:

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