Mam prosty kontroler, który zwraca obiekt użytkownika, ten użytkownik ma współrzędne atrybutu, które mają właściwość hibernacji FetchType.LAZY.
Kiedy próbuję uzyskać tego użytkownika, zawsze muszę załadować wszystkie współrzędne, aby uzyskać obiekt użytkownika, w przeciwnym razie, gdy Jackson próbuje serializować, użytkownik zgłasza wyjątek:
com.fasterxml.jackson.databind.JsonMappingException: nie można zainicjować serwera proxy - brak sesji
Wynika to z tego, że Jackson próbuje pobrać ten nietknięty obiekt. Oto obiekty:
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;
}
A kontroler:
@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {
User user = userService.getUser(username);
return user;
}
Czy jest sposób, aby powiedzieć Jacksonowi, aby nie serializował nieobrobionych obiektów? Szukałem innych odpowiedzi opublikowanych 3 lata temu wdrażających moduł jackson-hibernate-module. Ale prawdopodobnie można to osiągnąć dzięki nowej funkcji jackson.
Moje wersje to:
- Wiosna 3.2.5
- Hibernacja 4.1.7
- Jackson 2.2
Z góry dziękuję.
Odpowiedzi:
W końcu znalazłem rozwiązanie! dzięki indybee za danie mi wskazówki.
Samouczek Spring 3.1, Hibernate 4 i Jackson-Module-Hibernate mają dobre rozwiązanie dla Spring 3.1 i wcześniejszych wersji. Ale od wersji 3.1.2 Spring ma swój własny MappingJackson2HttpMessageConverter z prawie taką samą funkcjonalnością jak ta w samouczku, więc nie musimy tworzyć tego niestandardowego HTTPMessageConverter.
Dzięki javaconfig nie musimy również tworzyć HibernateAwareObjectMapper , wystarczy dodać Hibernate4Module do domyślnego MappingJackson2HttpMessageConverter, który ma już Spring, i dodać go do HttpMessageConverters aplikacji, więc musimy:
Rozszerz naszą sprężynową klasę konfiguracyjną z WebMvcConfigurerAdapter i zastąp metodę configureMessageConverters .
W tej metodzie dodaj MappingJackson2HttpMessageConverter z Hibernate4Module zarejestrowanym w metodzie previus .
Nasza klasa konfiguracyjna powinna wyglądać następująco:
@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.... }
Jeśli masz konfigurację XML, nie musisz też tworzyć własnego MappingJackson2HttpMessageConverter, ale musisz utworzyć spersonalizowany program mapujący, który pojawi się w samouczku (HibernateAwareObjectMapper), więc Twoja konfiguracja xml powinna wyglądać następująco:
<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>
Mam nadzieję, że ta odpowiedź będzie zrozumiała i pomoże komuś znaleźć rozwiązanie tego problemu. Wszelkie pytania zachęcamy do zadawania!
źródło
Począwszy od Spring 4.2 i używając Spring Boot i javaconfig, zarejestrowanie Hibernate4Module jest teraz tak proste, jak dodanie tego do konfiguracji:
@Bean public Module datatypeHibernateModule() { return new Hibernate4Module(); }
ref: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
źródło
Hibernate5Module
. Dodatkowo potrzebuje zależności od<groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-hibernate5</artifactId>
Jest to podobne do przyjętego rozwiązania autorstwa @rick.
Jeśli nie chcesz zmieniać istniejącej konfiguracji konwerterów wiadomości, możesz po prostu zadeklarować
Jackson2ObjectMapperBuilder
bean, taki jak:@Bean public Jackson2ObjectMapperBuilder configureObjectMapper() { return new Jackson2ObjectMapperBuilder() .modulesToInstall(Hibernate4Module.class); }
Nie zapomnij dodać następującej zależności do swojego pliku Gradle (lub Mavena):
compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'
Przydatne, jeśli masz buty sprężynowei chcesz zachować możliwość modyfikowania funkcji Jacksona z
application.properties
pliku.źródło
application.properties
pliku?Hibernate4Module
jest dziedzictwemW przypadku Spring Data Rest, podczas gdy rozwiązanie opublikowane przez @ r1ckr działa, wystarczy dodać jedną z następujących zależności w zależności od wersji Hibernate:
lub
W ramach Spring Data Rest istnieje klasa:
org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper
który automatycznie wykryje i zarejestruje Moduł podczas uruchamiania aplikacji.
Jest jednak problem:
Problem serializacji Lazy @ManyToOne
źródło
@Bean hibernate5Module()
Jeśli używasz konfiguracji XML i używasz
<annotation-driven />
, odkryłem, że musisz zagnieździć się w<message-converters>
środku<annotation-driven>
zgodnie z zaleceniami na koncie Jackson Github .Tak jak to:
<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>
`
źródło
Dla tych, którzy przybyli tutaj, szukając rozwiązania dla usługi RESTful opartej na Apache CXF, konfiguracja, która to naprawia, znajduje się poniżej:
<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"/>
Gdzie HibernateAwareObjectMapper definiuje się jako:
public class HibernateAwareObjectMapper extends ObjectMapper { public HibernateAwareObjectMapper() { registerModule(new Hibernate5Module()); } }
Następująca zależność jest wymagana od czerwca 2016 r. (Pod warunkiem, że używasz Hibernate5):
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-hibernate5</artifactId> <version>2.7.4</version> </dependency>
źródło
Następujące rozwiązanie jest przeznaczone dla Springa 4.3, (nie bootowalnego) i Hibernate 5.1, w których
fetch=FetchType.LAZY
ze względufetch=FetchType.EAGER
na wydajność przenieśliśmy wszystkie typy pobierania z przypadków na. Natychmiast zauważyliśmycom.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy
wyjątek z powodu problemu z leniwym ładowaniem.Najpierw dodajemy następującą zależność maven:
Następnie do naszego pliku konfiguracyjnego Java MVC dodawany jest następujący plik:
@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; }
Uwagi:
Musisz utworzyć i skonfigurować Hibernate5Module, aby uzyskać zachowanie podobne do Jacksona bez tego modułu. Domyślnie przyjmuje niezgodne założenia.
Nasz
WebMvcConfigurerAdapter
zawiera wiele innych konfiguracji i chcieliśmy uniknąć kolejnej klasy konfiguracji, dlatego nie używaliśmyWebMvcConfigurationSupport#addDefaultHttpMessageConverters
funkcji, do której odwoływaliśmy się w innych postach.WebMvcConfigurerAdapter#configureMessageConverters
wyłącza całą wewnętrzną konfigurację Springa konwerterów wiadomości. Woleliśmy uniknąć potencjalnych problemów z tym związanych.Korzystanie z
extendMessageConverters
włączonego dostępu do wszystkich automatycznie skonfigurowanych klas Jacksona bez utraty konfiguracji wszystkich innych konwerterów wiadomości.Za pomocą
getObjectMapper#registerModule
mogliśmy dodaćHibernate5Module
do istniejących konwerterów.Ten dodatek rozwiązał problem z hibernacją i leniwym ładowaniem, ale spowodował szczątkowy problem z wygenerowanym formatem JSON . Jak opisano w tym problemie na githubie, moduł leniwego ładowania hibernate-jackson obecnie ignoruje adnotację @JsonUnwrapped, co prowadzi do potencjalnych błędów danych. Dzieje się tak niezależnie od ustawienia funkcji ładowania wymuszonego. Problem istnieje od 2016 roku.
Uwaga
Wygląda na to, że dodając następujące elementy do klas, które są leniwie ładowane, wbudowany ObjectMapper działa bez dodawania modułu hibernate5:
@JsonIgnoreProperties( {"handler","hibernateLazyInitializer"} ) public class Anyclass {
źródło
kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
Możesz skorzystać z następującego pomocnika z projektu Spring Data Rest:
źródło
Zrobiłem bardzo proste rozwiązanie tego problemu.
@JsonInclude(JsonInclude.Include.NON_EMPTY) public Set<Pendency> getPendencies() { return Hibernate.isInitialized(this.pendencies) ? Collections.unmodifiableSet(this.pendencies) : new HashSet<>(); }
W moim przypadku podawałem błąd, ponieważ za każdym razem, gdy zwracałem pendencje, jako dobrą praktykę przekonwertowałem go na listę, której nie można modyfikować, ale jak może lub nie może być leniwy w zależności od metody, której użyłem do uzyskania instancji (z pobieraniem lub bez), wykonuję test przed zainicjowaniem go przez Hibernate i dodaję adnotację, która zapobiega serializacji pustej właściwości, co rozwiązało mój problem.
źródło
Spędziłem cały dzień próbując rozwiązać ten sam problem. Możesz to zrobić bez zmiany istniejącej konfiguracji konwerterów wiadomości.
Moim zdaniem najłatwiejszy sposób rozwiązania tego problemu tylko w 2 krokach z pomocą jackson-datatype-hibernate :
build.gradle.kts
:implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
@Bean
@Bean fun hibernate5Module(): Module = Hibernate5Module()
Zauważ, że
Module
tocom.fasterxml.jackson.databind.Module
niejava.util.Module
Dobrą praktyką jest również dodawanie
@JsonBackReference
&@JsonManagedReference
do@OneToMany
&@ManyToOne
relacji.@JsonBackReference
może być tylko 1 w klasie.źródło
Chociaż to pytanie różni się nieco od tego: wyjątek Dziwnego Jacksona jest generowany podczas serializacji obiektu Hibernate , podstawowy problem można naprawić w ten sam sposób za pomocą tego kodu:
@Provider public class MyJacksonJsonProvider extends JacksonJsonProvider { public MyJacksonJsonProvider() { ObjectMapper mapper = new ObjectMapper(); mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); setMapper(mapper); } }
źródło
Próbowałem tego i działałem:
// niestandardowa konfiguracja dla leniwego ładowania
public static class HibernateLazyInitializerSerializer extends JsonSerializer<JavassistLazyInitializer> { @Override public void serialize(JavassistLazyInitializer initializer, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException { jsonGenerator.writeNull(); } }
i skonfiguruj mappera:
mapper = new JacksonMapper(); SimpleModule simpleModule = new SimpleModule( "SimpleModule", new Version(1,0,0,null) ); simpleModule.addSerializer( JavassistLazyInitializer.class, new HibernateLazyInitializerSerializer() ); mapper.registerModule(simpleModule);
źródło
Inne rozwiązanie dla butów sprężynowych: konfiguracja:
spring.jpa.open-in-view=true
źródło
Wypróbowałem użyteczną odpowiedź @ Ricka , ale napotkałem problem polegający na tym, że „dobrze znane moduły”, takie jak jackson-datatype-jsr310 , nie były automatycznie rejestrowane, mimo że znajdowały się na ścieżce klas. (Ten post na blogu opisano automatyczną rejestrację).
Rozwijając odpowiedź @ Ricka, oto odmiana wykorzystująca Jackson2ObjectMapperBuilder Springa do utworzenia pliku
ObjectMapper
. To automatycznie rejestruje „dobrze znane moduły” i ustawia pewne funkcje oprócz zainstalowaniaHibernate4Module
.@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); } }
źródło
hibernate5Module
(ponieważ używam hibernacji 5), jackson ignoruje proxy niezainicjalizowanych obiektów, ale wygląda na to, że „dobrze znane moduły” nie są rejestrowane, mimo że używam twojego podejścia. Wiem o tym, ponieważ niektóre części mojej aplikacji są zepsute, a kiedy usuwam Module, znów działają.DelegatingWebMvcConfiguration
klasy (zamiastWebMvcConfigurerAdapter
) zgodnie z sugestią Jackson-Antpath-Filter :@Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
Hibernate4Module
jest dziedzictwem. 2SerializationFeature.WRITE_DATES_AS_TIMESTAMPS
- nie o pytaniu