Джексон - не сериализуй ленивые объекты

У меня есть сущность:

@Entity
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private List<Genre> genre;

}

Затем у меня есть контроллер, целью которого является получение книг, моя проблема в том, что поле жанра включается в ответ json моего контроллера. В любом случае я могу исключить те поля, которые лениво загружаются, когда Джексон сериализует объект?

Это конфигурация моего ObjectMapper:

Hibernate4Module hm = new Hibernate4Module();
hm.configure(Hibernate4Module.Feature.FORCE_LAZY_LOADING, false);
registerModule(hm);
configure(SerializationFeature.INDENT_OUTPUT, true);

Спасибо!

Я не могу пометить его как JsonIgnore, так как он навсегда останется в поле сериализации. Будут времена, когда мне нужно будет искать жанры вместе с книгой, и к тому времени я буду использовать "fetch join" в своем запросе, чтобы он не был нулевым.

5 ответов

Решение

Вы можете сделать это с Джексоном @JsonInclude аннотаций.

В соответствии с последней версией javadoc (2.4 прямо сейчас) вы можете указать с помощью простой аннотации, включать или нет аннотированное свойство, если значение поля равно нулю или пусто.

По умолчанию это JsonInclude.Include.ALWAYS, и это означает, что даже если ваши лениво не загруженные значения равны нулю, Джексон действительно включает это свойство.

Указание не включать пустые или нулевые значения может значительно уменьшить размер ответа JSON, включая все преимущества.

Если вы хотите изменить это поведение, вы можете добавить аннотацию на уровне класса или отдельного свойства / уровня getterMethod.

Попробуйте добавить следующие аннотации в поля, которые вы не хотите включать, если они пусты:

@JsonInclude(JsonInclude.Include.NON_EMPTY)
@OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
private List<Genre> genre;

Вы можете использовать конфигурацию пружины, чтобы отключить принудительную отложенную загрузку по умолчанию!

      @Configuration
public class JacksonConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        Hibernate5Module hibernate5Module = new Hibernate5Module();
        hibernate5Module.configure(Feature.FORCE_LAZY_LOADING, false);
        // Enable below line to switch lazy loaded json from null to a blank object!
        //hibernate5Module.configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true);
        mapper.registerModule(hibernate5Module);
        return mapper;
    }
}

Вы можете использовать JSON Filter особенность Джексона:

@Entity
@JsonFilter("Book") 
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private List<Genre> genre;
} 

@Entity
@JsonFilter("Genre")
public class Genre {
   ...
}

Затем в контроллере вы указываете, что фильтровать:

@Controller
public class BookController {
      @Autowired
      private ObjectMapper objectMapper;

      @Autowird
      private BookRepository bookRepository;

       @RequestMapping(value = "/book", method = RequestMethod.GET, produces =  "application/json")
       @ResponseBody
       public ResponseEntity<String> getBooks() {

          final List<Book> books = booksRepository.findAll();
          final SimpleFilterProvider filter = new SimpleFilterProvider();
          filter.addFilter("Book", SimpleFilterProvider.serializeAllExcept("Genre");
          return new ResponseEntity<>(objectMapper.writer(filter).writeValueAsString(books), HttpStatus.OK)
       }

}

Таким образом, вы можете контролировать, когда вы хотите фильтровать ленивые отношения во время выполнения

Возможно, это связано с известной проблемой отложенной загрузки.

Я не использую jackson-datatype-hibernate, но то, что я сделал, чтобы решить ту же проблему, - это извлечь постоянную коллекцию из картинки, используя DTO вместо прямой сериализации объекта Hibernate. Такие инструменты, как Dozer, могут помочь вам с этим. В качестве альтернативы есть небольшая утилита, которую я написал, чтобы делать такие сопоставления.

Если вы просто хотите поэкспериментировать с тем, как может выглядеть DTO, вы можете заменить выгруженную постоянную коллекцию обычной пустой коллекцией, например books.setGenre(new ArrayList<>()); К сожалению, я не знаю, как определить, был ли загружен лениво загруженный объект или нет, поэтому вы не можете выполнить это переназначение автоматически. Места, в которых вы заменяете постоянные коллекции, должны определяться вами в каждом конкретном случае.

Вы можете использовать Gson вместо ObjectMapper и при определении объекта пометить поле как "временное".

public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column
    private String title;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = ("movie"),cascade = CascadeType.ALL)
    private **transient** List<Genre> genre;

}

при десериализации с использованием gson.toJson(book), Gson не будет десериализовать этот элемент.

Другие вопросы по тегам