Tránh tuần tự hóa Jackson trên các đối tượng lười biếng không được tìm nạp


83

Tôi có một bộ điều khiển đơn giản trả về đối tượng Người dùng, người dùng này có tọa độ thuộc tính có thuộc tính ngủ đông FetchType.LAZY.

Khi tôi cố gắng có được người dùng này, tôi luôn phải tải tất cả các tọa độ để có được đối tượng người dùng, nếu không khi Jackson cố gắng tuần tự hóa Người dùng sẽ ném ngoại lệ:

com.fasterxml.jackson.databind.JsonMappingException: không thể khởi tạo proxy - không có phiên

Điều này là do Jackson đang cố gắng lấy vật thể chưa tìm được này. Đây là các đối tượng:

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

Và bộ điều khiển:

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

    User user = userService.getUser(username);

    return user;

}

Có một cách để nói với Jackson để không sắp xếp các đối tượng chưa được sắp xếp? Tôi đã tìm kiếm các câu trả lời khác được đăng 3 năm trước khi triển khai jackson-hibernate-module. Nhưng có lẽ nó có thể đạt được với một tính năng jackson mới.

Các phiên bản của tôi là:

  • Mùa xuân 3.2.5
  • Ngủ đông 4.1.7
  • Jackson 2.2

Cảm ơn trước.


3
phương pháp mô tả trong các 2 liên kết làm việc cho tôi, mùa xuân 3.1, Hibernate 4 và Jackson-Module-HibernateFasterXML / jackson-datatype-ngủ đông
indybee

Cảm ơn bạn indybee, tôi đã xem hướng dẫn này, mùa xuân 3.2.5 đã có MappingJackson2HttpMessageConverter, nhưng tôi không thể sử dụng nó để tránh đối tượng lười biếng không tìm nạp được, tôi cũng đã cố gắng triển khai đối tượng trong hướng dẫn, nhưng không có gì. ..
r1ckr

1
bạn có gặp phải lỗi tương tự "không thể khởi tạo proxy" hoặc một cái gì đó khác không? (tôi đã sử dụng HibernateAwareObjectMapper bổ sung được mô tả trong các bài viết với Spring 3.2.6 và Hibernate 4.1.7 và jackson 2.3)
indybee

1
Hiểu rồi!! Kể từ phiên bản 3.1.2 Spring đã có MappingJackson2HttpMessageConverter của riêng mình và có hành vi gần như tương tự như phiên bản trong hướng dẫn, nhưng vấn đề là tôi đang sử dụng cấu hình java Spring và tôi đang bắt đầu với javaconfigs. Tôi đã cố gắng để làm một @Bean MappingJackson2HttpMessageConverter , thay vì thêm nó vào HttpMessageConverters của mùa xuân, có thể nó là rõ ràng trong câu trả lời :)
r1ckr

rất vui vì nó đang hoạt động :)
indybee

Câu trả lời:


93

Cuối cùng tôi đã tìm ra giải pháp! cảm ơn indybee đã cho tôi một manh mối.

Hướng dẫn Spring 3.1, Hibernate 4 và Jackson-Module-Hibernate có một giải pháp tốt cho Spring 3.1 và các phiên bản trước đó. Nhưng vì phiên bản 3.1.2 Spring đã có MappingJackson2HttpMessageConverter của riêng mình với chức năng gần như tương tự như chức năng trong hướng dẫn, vì vậy chúng ta không cần tạo HTTPMessageConverter tùy chỉnh này.

Với javaconfig chúng ta không cần tạo HibernateAwareObjectMapper nữa, chúng ta chỉ cần thêm Hibernate4Module vào MappingJackson2HttpMessageConverter mặc định mà Spring đã có và thêm nó vào HttpMessageConverters của ứng dụng, vì vậy chúng ta cần:

  1. Mở rộng lớp cấu hình mùa xuân của chúng tôi từ WebMvcConfigurerAdapter và ghi đè lên các phương pháp configureMessageConverters .

  2. Trên phương thức đó, hãy thêm MappingJackson2HttpMessageConverter với Hibernate4Module được đăng ký trong một phương thức previus.

Lớp cấu hình của chúng ta sẽ trông như thế này:

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

Nếu bạn có cấu hình xml, bạn cũng không cần phải tạo MappingJackson2HttpMessageConverter của riêng mình, nhưng bạn cần tạo trình ánh xạ được cá nhân hóa xuất hiện trong hướng dẫn (HibernateAwareObjectMapper), vì vậy cấu hình xml của bạn sẽ giống như sau:

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

Hy vọng câu trả lời này có thể hiểu được và giúp ai đó tìm ra giải pháp cho vấn đề này, bất kỳ câu hỏi nào hãy hỏi!


3
Cảm ơn đã chia sẻ giải pháp. Tuy nhiên, sau một vài lần thử, tôi thấy rằng jackson mới nhất tại thời điểm này ( 2.4.2 ) KHÔNG hoạt động với spring 4.0.6. Tôi vẫn gặp lỗi "không có phiên". Chỉ khi tôi hạ cấp phiên bản jackson trở lại 2.3.4 (hoặc 2.4.2), nó mới bắt đầu hoạt động.
Robin

1
Cảm ơn bạn đã trỏ MappingJackson2HttpMessageConverter ra ngoài! Tôi đã tìm kiếm một lớp học như thế này trong một thời gian.
LanceP

1
Cảm ơn rât nhiều! Tôi vừa thử nghiệm nó và nó hoạt động với Spring 4.2.4, Jackson 2.7.1-1 và JacksonDatatype 2.7.1 :)
Isthar 17/02/16

1
Điều này không giải quyết được sự cố trong Spring Data REST.
Alessandro C

1
Làm thế nào để giải quyết vấn đề tương tự, nếu Springboot được sử dụng thay cho Spring ?. Tôi đã thử đăng ký mô-đun Hibernate4 (là phiên bản tôi đang có), nhưng điều đó không giải quyết được sự cố của tôi.
ranjith Gampa

34

Kể từ phiên bản Spring 4.2 và sử dụng Spring Boot và javaconfig, việc đăng ký Hibernate4Module giờ đây đơn giản như thêm điều này vào cấu hình của bạn:

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

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


19
Đối với Hibernate 5 (như trong phiên bản Spring Boot hiện tại), nó là như vậy Hibernate5Module. Nó cũng cần một sự phụ thuộc vào<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
Zeemee

1
Làm việc cho tôi cũng như trên Spring Boot 2.1. Bắt đầu khiến tôi phát điên.
Van Dame

Không rõ làm thế nào để thực hiện điều này. Tôi gặp lỗi loại không tương thích.
EarthMind

2
Tuyệt vời! Làm việc cho tôi với Spring Boot 2.0.6 và Hibernate 5.
GarouDan

Điều này rất hữu ích. Cần chỉ ra rằng việc đăng ký cá thể lớp Hibernate5Module dưới dạng bean là không đủ. Nếu bạn muốn áp dụng nó cho một cá thể ObjectMapper, bạn có thể tự động chuyển mô-đun vào lớp do Spring quản lý của bạn và sau đó đăng ký nó với cá thể ObjectMapper. @Autowire Mô-đun jacksonHibernateModule; ObjectMapper mapper = new ObjectMapper (); mapper.registerModule (this.jacksonHibernateModule).
gburgalum01

12

Điều này tương tự với giải pháp được chấp nhận bởi @rick.

Nếu bạn không muốn chạm vào cấu hình trình chuyển đổi tin nhắn hiện có, bạn chỉ có thể khai báo một Jackson2ObjectMapperBuilderbean như:

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

Đừng quên thêm phần phụ thuộc sau vào tệp Gradle (hoặc Maven) của bạn:

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

Hữu ích nếu bạn có ứng dụng và bạn muốn giữ khả năng sửa đổi các tính năng của Jackson từ application.propertiestệp.


Làm thế nào chúng ta có thể đạt được điều này (Cấu hình Jackson để tránh đối tượng được tải chậm) bằng cách sử dụng application.propertiestệp?
hemu

1
Đó không phải là những gì đang được nói ở đây. Giải pháp của @Luis Belloch gợi ý rằng bạn nên tạo Jackson2ObjectMapperBuilder để bạn không ghi đè trình chuyển đổi thông báo mặc định của spring boots và tiếp tục sử dụng các giá trị application.properties để kiểm soát các thuộc tính jackson.
kapad

xin lỗi ... tôi là người mới, nơi bạn sẽ thực hiện phương pháp này? Với khởi động mùa xuân Nó sử dụng mùa xuân 5 tương đương WebMvcConfigurerAdapter bị phản đối
Eric Huang

Hibernate4Modulelà di sản
Dmitry Kaltovich

8

Trong trường hợp Spring Data Rest sau đó, trong khi giải pháp do @ r1ckr đăng hoạt động, tất cả những gì cần thiết là thêm một trong các phần phụ thuộc sau tùy thuộc vào phiên bản Hibernate của bạn:

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

hoặc là

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

Trong Spring Data Rest có một lớp:

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

sẽ tự động phát hiện và đăng ký Mô-đun khi khởi động ứng dụng.

Tuy nhiên, có một vấn đề:

Sự cố Serializing Lazy @ManyToOne


Làm thế nào để bạn cấu hình điều này với khởi động mùa xuân khi bạn không sử dụng Spring Data Rest?
Eric Huang

Không đủ. Bạn cũng cần@Bean hibernate5Module()
Dmitry Kaltovich

4

Nếu bạn sử dụng cấu hình XML và sử dụng <annotation-driven />, tôi thấy rằng bạn phải lồng <message-converters>vào bên trong <annotation-driven>như được khuyến nghị trên tài khoản Jackson Github .

Như vậy:

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

`


Cấu hình xml là kế thừa
Dmitry Kaltovich

3

Đối với những người đến đây tìm kiếm giải pháp cho dịch vụ RESTful dựa trên Apache CXF, cấu hình khắc phục sự cố này là bên dưới:

<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"/>

Trường hợp HibernateAwareObjectMapper được định nghĩa là:

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

Phần phụ thuộc sau là bắt buộc kể từ tháng 6 năm 2016 (với điều kiện bạn đang sử dụng Hibernate5):

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

1

Giải pháp sau đây dành cho Spring 4.3, (không khởi động) & Hibernate 5.1 trong đó chúng tôi đã chuyển đổi tất cả các kiểu tìm nạp fetch=FetchType.LAZYtừ các trường hợp fetch=FetchType.EAGERvì lý do hiệu suất. Ngay lập tức chúng tôi thấy com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxyngoại lệ do vấn đề tải chậm.

Đầu tiên, chúng tôi thêm phần phụ thuộc maven sau:

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

Sau đó, phần sau được thêm vào tệp cấu hình Java MVC của chúng tôi:

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

Ghi chú:

  • Bạn cần tạo và cấu hình Hibernate5Module để có hành vi tương tự như Jackson khi không có mô-đun này. Mặc định đưa ra các giả định không tương thích.

  • Chúng tôi WebMvcConfigurerAdaptercó rất nhiều cấu hình khác trong đó và chúng tôi muốn tránh một lớp cấu hình khác, đó là lý do tại sao chúng tôi không sử dụng WebMvcConfigurationSupport#addDefaultHttpMessageConverterschức năng đã được đề cập trên các bài viết khác.

  • WebMvcConfigurerAdapter#configureMessageConvertersvô hiệu hóa tất cả cấu hình bên trong của trình chuyển đổi thông báo của Spring. Chúng tôi muốn tránh những vấn đề tiềm ẩn xung quanh điều này.

  • Sử dụng extendMessageConvertersquyền truy cập đã kích hoạt vào tất cả các lớp Jackson được cấu hình tự động mà không làm mất cấu hình của tất cả các bộ chuyển đổi thông báo khác.

  • Bằng cách sử dụng, getObjectMapper#registerModulechúng tôi có thể thêm Hibernate5Modulevào các trình chuyển đổi hiện có.

  • Mô-đun đã được thêm vào cả bộ xử lý JSON và XML

Việc bổ sung này đã giải quyết được sự cố với Hibernate và lazy loading nhưng lại gây ra sự cố còn lại với định dạng JSON đã tạo . Như đã báo cáo trong vấn đề github này , mô-đun tải lười biếng hibernate-jackson hiện bỏ qua chú thích @JsonUnwrapped, dẫn đến lỗi dữ liệu tiềm ẩn. Điều này xảy ra bất kể cài đặt tính năng tải lực. Vấn đề đã có từ năm 2016.


Ghi chú

Có vẻ như bằng cách thêm phần sau vào các lớp được tải chậm, ObjectMapper tích hợp sẽ hoạt động mà không cần thêm mô-đun hibernate5:

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

Giải pháp có thể dễ dàng hơn nhiều: kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
Dmitry Kaltovich

0

Bạn có thể sử dụng trình trợ giúp sau từ dự án Spring Data Rest:

Jackson2DatatypeHelper.configureObjectMapper(objectMapper);

Không phải về câu hỏi
Dmitry Kaltovich

0

Tôi đã thực hiện một giải pháp rất đơn giản cho vấn đề này.

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

Trong trường hợp của tôi, tôi đã đưa ra lỗi vì bất cứ khi nào tôi trả về một mặt dây chuyền, như một phương pháp hay, tôi đã chuyển đổi nó thành một danh sách không thể sửa đổi, nhưng làm thế nào nó có thể có hoặc có thể không tùy thuộc vào phương pháp tôi sử dụng để lấy ví dụ. (có hoặc không tìm nạp), tôi thực hiện kiểm tra trước khi nó được khởi tạo bởi Hibernate và thêm chú thích ngăn việc tuần tự hóa thuộc tính trống và điều đó đã giải quyết được vấn đề của tôi.


0

Tôi đã dành cả ngày để giải quyết cùng một vấn đề. Bạn có thể làm điều đó mà không cần thay đổi cấu hình trình chuyển đổi tin nhắn hiện có.

Theo tôi cách dễ nhất để giải quyết vấn đề này chỉ với 2 bước với sự trợ giúp của jackson-datatype-hibernate :

ví dụ kotlin (giống như java):

  1. Thêm vào build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Tạo nên @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • Chú ý rằng Modulecom.fasterxml.jackson.databind.Module, khôngjava.util.Module

  • Ngoài ra, thực hành tốt là thêm @JsonBackReference& @JsonManagedReferencevào @OneToMany& các @ManyToOnemối quan hệ. @JsonBackReferencechỉ có thể là 1 trong lớp.


-1

Mặc dù câu hỏi này hơi khác với câu hỏi này: Ngoại lệ Jackson kỳ lạ được ném ra khi tuần tự hóa đối tượng Hibernate , vấn đề cơ bản có thể được khắc phục theo cách tương tự với mã này:

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

-1

Tôi đã thử điều này và đã làm việc:

// cấu hình tùy chỉnh để tải chậm

public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> {

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

và định cấu hình trình liên kết:

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

-1

Một giải pháp khác cho khởi động mùa xuân: cấu hình: spring.jpa.open-in-view=true


Điều này sẽ không giúp ích gì
Dmitry Kaltovich

-1

Tôi đã thử câu trả lời hữu ích của @ rick , nhưng gặp phải vấn đề là "các mô-đun nổi tiếng" như jackson-datatype-jsr310 không được tự động đăng ký mặc dù chúng nằm trên classpath. (Bài đăng trên blog này giải thích về việc đăng ký tự động.)

Mở rộng câu trả lời của @ rick, đây là một biến thể sử dụng Jackson2ObjectMapperBuilder của Spring để tạo ObjectMapper. Điều này tự động đăng ký "mô-đun nổi tiếng" và đặt một số tính năng ngoài việc cài đặt 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()
                            .modulesToInstall(hibernate4Module());
        return new MappingJackson2HttpMessageConverter(builder.build());
  }

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

Bạn có cập nhật về chủ đề này không? Tôi đang gặp sự cố hiện tại. Tất nhiên khi tôi thêm hibernate5Module(vì tôi đang sử dụng hibernate 5), jackson bỏ qua proxy của các đối tượng không được khởi tạo, nhưng có vẻ như "các mô-đun nổi tiếng" vẫn chưa được đăng ký mặc dù tôi sử dụng cách tiếp cận của bạn. Tôi biết điều này, vì một số phần trong ứng dụng của tôi bị hỏng và khi tôi xóa Mô-đun, chúng hoạt động trở lại.
Daniel Henao

@DanielHenao Bạn đã học spring.io/blog/2014/12/02/… chưa? Tôi chuyển sang Hibernate5, bằng cách sử dụng DelegatingWebMvcConfigurationlớp (thay vì WebMvcConfigurerAdapter) theo đề nghị của các Jackson-Antpath-Filter :@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
Markus Pscheidt

1. Hibernate4Modulelà di sản. 2 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS- không phải về câu hỏi
Dmitry Kaltovich

Quảng cáo @DmitryKaltovich 1) nó không phải là di sản vào thời điểm viết bài; quảng cáo 2) ok, đã gỡ bỏ.
Markus Pscheidt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.