web-dev-qa-db-ja.com

フェッチされていない遅延オブジェクトでのジャクソンのシリアル化を避ける

Userオブジェクトを返す単純なコントローラーがあります。このユーザーには、hibernateプロパティFetchType.LAZYを持つ属性座標があります。

このユーザーを取得しようとすると、常にすべての座標を読み込んでユーザーオブジェクトを取得する必要があります。そうしないと、ジャクソンがユーザーをシリアル化しようとすると例外がスローされます。

com.fasterxml.jackson.databind.JsonMappingException:プロキシを初期化できませんでした-セッションなし

これは、ジャクソンがこの未取得オブジェクトを取得しようとしているためです。オブジェクトは次のとおりです。

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;

}

フェッチされていないオブジェクトをシリアル化しないようにジャクソンに伝える方法はありますか?私は3年前にjackson-hibernate-moduleを実装する他の回答を探してきました。しかし、おそらくそれは新しいジャクソン機能で達成できるでしょう。

私のバージョンは:

  • 春3.2.5
  • Hibernate 4.1.7
  • ジャクソン2.2

前もって感謝します。

68
r1ckr

私はついに解決策を見つけました!手がかりを与えてくれたindybeeに感謝します。

チュートリアル Spring 3.1、Hibernate 4およびJackson-Module-Hibernate には、Spring 3.1以前のバージョンに適したソリューションがあります。ただし、バージョン3.1.2 Springには独自のMappingJackson2HttpMessageConverterがあり、チュートリアルの機能とほぼ同じ機能を備えているため、これを作成する必要はありません。カスタムHTTPMessageConverter。

Javaconfigを使用すると、HibernateAwareObjectMapperも作成する必要がなく、Hibernate4ModuleSpringが既に持っているデフォルトMappingJackson2HttpMessageConverterに追加し、アプリケーションのHttpMessageConvertersに追加します。

  1. WebMvcConfigurerAdapterからスプリング設定クラスを拡張し、メソッドconfigureMessageConvertersをオーバーライドします。

  2. そのメソッドで、MappingJackson2HttpMessageConverterとpreviusメソッドで登録されたHibernate4Moduleを追加します。

構成クラスは次のようになります。

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

この回答が理解可能であり、誰かがこの問題の解決策を見つけるのに役立つことを願っています。どんな質問でもお気軽に!

90
r1ckr

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

28
chrismarx

これは、@ rickが承認したソリューションに似ています。

既存のメッセージコンバーターの構成を変更したくない場合は、次のようにJackson2ObjectMapperBuilder Beanを宣言するだけです。

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

Gradleファイル(またはMaven)に次の依存関係を追加することを忘れないでください。

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

spring-boot アプリケーションがあり、application.propertiesファイルからJacksonの機能を変更する機能を保持したい場合に便利です。

14
Luis Belloch

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>

Spring Data Restには次のクラスがあります。

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

これにより、アプリケーションの起動時にモジュールが自動検出および登録されます。

ただし、問題があります。

レイジー@ManyToOneのシリアル化の問題

6
Alan Hay

XML構成を使用し、<annotation-driven />を使用する場合、 Jackson Githubアカウント で推奨されているように、<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>

`

5
blackwood

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>  
4
user2968675

次のソリューションは、パフォーマンス上の理由から、すべてのフェッチタイプをfetch=FetchType.LAZYの場合からfetch=FetchType.EAGERに移行したSpring 4.3、(非ブート)およびHibernate 5.1に対するものです。すぐに、遅延ロードの問題による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;
}

ノート:

  • Hibernate5Moduleを作成および構成して、このモジュールなしでJacksonに似た動作を取得する必要があります。デフォルトでは、互換性のない仮定が行われます。

  • WebMvcConfigurerAdapterには他にも多くの設定があり、別の設定クラスを避けたかったので、他の投稿で参照されていたWebMvcConfigurationSupport#addDefaultHttpMessageConverters関数を使用しませんでした。

  • WebMvcConfigurerAdapter#configureMessageConvertersは、Springのすべてのメッセージコンバーターの内部構成を無効にします。これに関する潜在的な問題を回避することをお勧めします。

  • extendMessageConvertersを使用すると、他のすべてのメッセージコンバーターの構成を失うことなく、自動的に構成されたすべてのJacksonクラスへのアクセスが可能になりました。

  • getObjectMapper#registerModuleを使用して、Hibernate5Moduleを既存のコンバーターに追加できました。

  • モジュールはJSONとXMLプロセッサの両方に追加されました

この追加により、Hibernateと遅延読み込みの問題は解決しましたが、生成されたJSON形式の残留問題が発生しました。 このgithubの問題 で報告されているように、hibernate-jacksonの遅延ロードモジュールは現在@JsonUnwrappedアノテーションを無視し、潜在的なデータエラーを引き起こします。これは、強制ロード機能の設定に関係なく発生します。この問題は2016年から続いています。


遅延ロードされるクラスに以下を追加することにより、組み込みObjectMapperはhibernate5モジュールを追加せずに動作するようです。

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

Spring Data Restプロジェクトから次のヘルパーを使用できます。

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
1
Przemek Nowak

この質問はこれとは少し異なりますが、 Hibernateオブジェクトのシリアル化中にストレンジジャクソンの例外がスローされます ですが、根本的な問題はこのコードで同じ方法で修正できます。

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

この問題を非常に簡単に解決しました。

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

私の場合、エラーを与えていました。ペンダントを返すたびに、良い習慣としてそれを変更できないリストに変換しましたが、インスタンスを取得するために使用した方法に応じてそれがどのように遅延する可能性があるか(フェッチの有無にかかわらず)、Hibernateによって初期化される前にテストを実行し、空のプロパティのシリアル化を妨げ、問題を解決する注釈を追加します。

0
Pedro Bacchini

@ rickの便利な答え を試しましたが、 jackson-datatype-jsr31 などの「よく知られたモジュール」が、クラスパス。 (この ブログ投稿 は自動登録について説明しています。)

@rickの答えを拡張すると、Springの Jackson2ObjectMapperBuilder を使用してObjectMapperを作成するバリエーションがあります。これにより、「既知のモジュール」が自動登録され、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);
    }
}
0
Markus Pscheidt

私はこれを試して働いた:

//遅延読み込みのカスタム構成

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);
0
ahll