developer tip

가져 오지 않은 지연 객체에 대한 Jackson 직렬화 방지

optionbox 2020. 10. 29. 08:02
반응형

가져 오지 않은 지연 객체에 대한 Jackson 직렬화 방지


User 개체를 반환하는 간단한 컨트롤러가 있으며이 사용자는 최대 절전 모드 FetchType.LAZY 속성이있는 특성 좌표를 가지고 있습니다.

이 사용자를 얻으려고 할 때 항상 사용자 개체를 가져 오기 위해 모든 좌표를로드해야합니다. 그렇지 않으면 Jackson이 User를 직렬화하려고 할 때 예외가 발생합니다.

com.fasterxml.jackson.databind.JsonMappingException : 프록시를 초기화 할 수 없음-세션 없음

이것은 Jackson이 가져 오지 않은이 객체를 가져 오려고하기 때문입니다. 개체는 다음과 같습니다.

public class User{

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    @JsonManagedReference("user-coordinate")
    private List<Coordinate> coordinates;
}

public class Coordinate {

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    @JsonBackReference("user-coordinate")
    private User user;
}

그리고 컨트롤러 :

@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {

    User user = userService.getUser(username);

    return user;

}

가져 오지 않은 객체를 직렬화하지 않도록 Jackson에게 알리는 방법이 있습니까? jackson-hibernate-module을 구현하기 전에 3 년 전에 게시 된 다른 답변을 찾고 있습니다. 그러나 아마도 새로운 jackson 기능으로 달성 할 수 있습니다.

내 버전은 다음과 같습니다.

  • 봄 3.2.5
  • 최대 절전 모드 4.1.7
  • 잭슨 2.2

미리 감사드립니다.


마침내 해결책을 찾았습니다! 나에게 단서를 준 indybee에게 감사합니다.

튜토리얼 Spring 3.1, Hibernate 4 및 Jackson-Module-Hibernate 에는 Spring 3.1 및 이전 버전에 대한 좋은 솔루션이 있습니다. 그러나 버전 3.1.2부터 Spring에는 튜토리얼에있는 것과 거의 동일한 기능을 가진 자체 MappingJackson2HttpMessageConverter 가 있으므로이 사용자 지정 HTTPMessageConverter를 만들 필요가 없습니다.

javaconfig를 사용하면 HibernateAwareObjectMapper만들 필요가 없습니다. Spring이 이미 가지고 있는 기본 MappingJackson2HttpMessageConverter에 Hibernate4Module추가하고이를 애플리케이션의 HttpMessageConverters에 추가하기 만하면 됩니다.

  1. WebMvcConfigurerAdapter 에서 스프링 구성 클래스를 확장하고 configureMessageConverters 메서드를 재정의합니다 .

  2. 이 메서드 에서 이전 메서드에 등록 된 Hibernate4Module 과 함께 MappingJackson2HttpMessageConverter추가합니다 .

구성 클래스는 다음과 같아야합니다.

@Configuration
@EnableWebMvc
public class MyConfigClass extends WebMvcConfigurerAdapter{

    //More configuration....

    /* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
     * to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
    public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();

        ObjectMapper mapper = new ObjectMapper();
        //Registering Hibernate4Module to support lazy objects
        mapper.registerModule(new Hibernate4Module());

        messageConverter.setObjectMapper(mapper);
        return messageConverter;

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //Here we add our custom-configured HttpMessageConverter
        converters.add(jacksonMessageConverter());
        super.configureMessageConverters(converters);
    }

    //More configuration....
}

xml 구성이있는 경우 자체 MappingJackson2HttpMessageConverter를 만들 필요가 없지만 자습서 (HibernateAwareObjectMapper)에 표시되는 개인화 된 매퍼를 만들어야하므로 xml 구성은 다음과 같아야합니다.

<mvc:message-converters>
    <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
        <property name="objectMapper">
            <bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
        </property>
    </bean>
</mvc:message-converters>

이 답변이 이해하기 쉽고 누군가가이 문제에 대한 해결책을 찾는 데 도움이되기를 바랍니다. 질문이 있으면 언제든지 물어보십시오!


Spring 4.2부터 Spring Boot 및 javaconfig를 사용하여 Hibernate4Module을 등록하는 것은 이제 다음을 구성에 추가하는 것만 큼 간단합니다.

@Bean
public Module datatypeHibernateModule() {
  return new Hibernate4Module();
}

심판 : https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring


이것은 @rick이 허용하는 솔루션과 유사합니다.

기존 메시지 변환기 구성을 건드리지 않으려면 Jackson2ObjectMapperBuilder다음과 같이 빈을 선언하면됩니다 .

@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
        .modulesToInstall(Hibernate4Module.class);
}

Gradle 파일 (또는 Maven)에 다음 종속성을 추가하는 것을 잊지 마십시오.

compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'

애플리케이션이 있고 application.properties파일 에서 Jackson 기능을 수정하는 기능을 유지하려는 경우 유용 합니다.


Spring Data Rest의 경우 @ r1ckr가 게시 한 솔루션이 작동하는 동안 필요한 것은 Hibernate 버전에 따라 다음 종속성 중 하나를 추가하는 것입니다.

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>${jackson.version}</version>
</dependency>

또는

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>${jackson.version}</version>
</dependency>

SpringData Rest에는 클래스가 있습니다.

org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper

애플리케이션 시작시 모듈을 자동 감지하고 등록합니다.

그러나 문제가 있습니다.

Lazy @ManyToOne 직렬화 문제


XML 구성을 사용하고 사용 한다면 Jackson Github 계정 에서 권장하는대로 내부 <annotation-driven />에 중첩해야한다는 것을 알았습니다 .<message-converters><annotation-driven>

이렇게 :

<annotation-driven>
  <message-converters>
    <beans:bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
      <beans:property name="objectMapper">
        <beans:bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
      </beans:property>
    </beans:bean> 
  </message-converters>
</annotation-driven>

`


Apache CXF 기반 RESTful 서비스에 대한 솔루션을 찾고자 여기에 온 사람들을 위해이를 수정하는 구성은 다음과 같습니다.

<jaxrs:providers>
    <bean class="com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider">
        <property name="mapper" ref="objectMapper"/>
    </bean>
</jaxrs:providers>

<bean id="objectMapper" class="path.to.your.HibernateAwareObjectMapper"/>

HibernateAwareObjectMapper는 다음과 같이 정의됩니다.

public class HibernateAwareObjectMapper extends ObjectMapper {
    public HibernateAwareObjectMapper() {
        registerModule(new Hibernate5Module());
    }
}

다음 종속성은 2016 년 6 월부터 필요합니다 (Hibernate5를 사용하는 경우).

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>2.7.4</version>
</dependency>  

SpringData Rest 프로젝트에서 다음 도우미를 사용할 수 있습니다.

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);

다음 솔루션은 Spring 4.3, (non-boot) & Hibernate 5.1에 대한 것입니다. 여기서 우리 는 성능상의 이유로 모든 fetchtype을 fetch=FetchType.LAZY케이스에서 전환했습니다 fetch=FetchType.EAGER. com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy지연로드 문제로 인한 예외를 즉시 확인 했습니다.

먼저 다음과 같은 Maven 종속성을 추가합니다.

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>  

그런 다음 Java MVC 구성 파일에 다음이 추가됩니다.

@Configuration
@EnableWebMvc 
public class MvcConfig extends WebMvcConfigurerAdapter {

@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {

    Hibernate5Module h5module = new Hibernate5Module();
    h5module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
    h5module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);

    for (HttpMessageConverter<?> mc : converters){
        if (mc instanceof MappingJackson2HttpMessageConverter || mc instanceof MappingJackson2XmlHttpMessageConverter) {
            ((AbstractJackson2HttpMessageConverter) mc).getObjectMapper().registerModule(h5module);
        }
    }
    return;
}

메모:

  • 이 모듈없이 Jackson과 유사한 동작을 얻으려면 Hibernate5Module을 만들고 구성해야합니다. 기본값은 호환되지 않는 가정을합니다.

  • 우리 WebMvcConfigurerAdapter는 다른 많은 구성을 가지고 있으며 다른 구성 클래스를 피하고 싶었습니다. 이것이 WebMvcConfigurationSupport#addDefaultHttpMessageConverters다른 게시물에서 언급 된 기능을 사용하지 않은 이유 입니다.

  • WebMvcConfigurerAdapter#configureMessageConverters disables all of Spring's internal configuration of message converters. We preferred to avoid the potential issues around this.

  • Using extendMessageConverters enabled access to all the automatically-configured Jackson classes without losing the configuration of all other message converters.

  • Using getObjectMapper#registerModule we were able to add the Hibernate5Module to the existing converters.

  • The module was added to both the JSON and XML processors

This addition solved the issue with Hibernate and lazy loading but caused a residual issue with the generated JSON format. As reported in this github issue, the hibernate-jackson lazy load module currently ignores the @JsonUnwrapped annotation, leading to potential data errors. This happens regardless of the force-loading feature setting. The problem has been there since 2016.


Note

It appears that by adding the following to classes that are lazy-loaded, the built-in ObjectMapper works without adding the hibernate5 module:

@JsonIgnoreProperties(  {"handler","hibernateLazyInitializer"} )
public class Anyclass {

I tried @rick's useful answer, but ran into the problem that "well-known modules" such as jackson-datatype-jsr310 weren't automatically registered despite them being on the classpath. (This blog post explains the auto-registration.)

Expanding on @rick's answer, here's a variation using Spring's Jackson2ObjectMapperBuilder to create the ObjectMapper. This auto-registers the "well-known modules" and sets certain features in addition to installing the Hibernate4Module.

@Configuration
@EnableWebMvc
public class MyWebConfig extends WebMvcConfigurerAdapter {

    // get a configured Hibernate4Module
    // here as an example with a disabled USE_TRANSIENT_ANNOTATION feature
    private Hibernate4Module hibernate4Module() {
        return new Hibernate4Module().disable(Hibernate4Module.Feature.USE_TRANSIENT_ANNOTATION);
    }

    // create the ObjectMapper with Spring's Jackson2ObjectMapperBuilder
    // and passing the hibernate4Module to modulesToInstall()
    private MappingJackson2HttpMessageConverter jacksonMessageConverter(){
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
            .modulesToInstall(hibernate4Module());
        return new MappingJackson2HttpMessageConverter(builder.build());
  }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(jacksonMessageConverter());
        super.configureMessageConverters(converters);
    }
}

Although this question is slightly different to this one : Strange Jackson exception being thrown when serializing Hibernate object, the underlying problem can be fixed in the same way with this code:

@Provider
public class MyJacksonJsonProvider extends JacksonJsonProvider {
    public MyJacksonJsonProvider() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        setMapper(mapper);
    }
}

나는 이것을 시도하고 일했습니다.

// 지연로드를위한 맞춤 구성

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

    @Override
    public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator,
            SerializerProvider serializerProvider)
            throws IOException, JsonProcessingException {
        jsonGenerator.writeNull();
    }
}

매퍼를 구성하십시오.

    mapper = new JacksonMapper();
    SimpleModule simpleModule = new SimpleModule(
            "SimpleModule", new Version(1,0,0,null)
    );
    simpleModule.addSerializer(
            JavassistLazyInitializer.class,
            new HibernateLazyInitializerSerializer()
    );
    mapper.registerModule(simpleModule);

이 문제에 대한 아주 간단한 해결책을 만들었습니다.

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public Set<Pendency> getPendencies() {
    return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>();
}

제 경우에는 오류를 표시했습니다. 왜냐하면 나는 펜던시를 반환 할 때마다 수정할 수없는 목록으로 변환했지만 인스턴스를 가져 오는 데 사용한 방법에 따라 게 으르거나 아닐 수도 있기 때문입니다. (페치 유무에 관계없이) Hibernate에 의해 초기화되기 전에 테스트를 수행하고 빈 속성을 직렬화하는 것을 방지하고 내 문제를 해결하는 주석을 추가합니다.

참고 URL : https://stackoverflow.com/questions/21708339/avoid-jackson-serialization-on-non-fetched-lazy-objects

반응형