SpringMVC
、Hibernate
、およびJSON
に取り組んでいますが、このエラーが発生しています。
HTTP Status 500 - Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.SerializationFeature.FAIL_ON_EMPTY_BEANS) )
以下のエンティティを確認してください
@Entity
@Table(name="USERS")
public class User {
@Id
@GeneratedValue
@Column(name="USER_ID")
private Integer userId;
@Column(name="USER_FIRST_NAME")
private String firstName;
@Column(name="USER_LAST_NAME")
private String lastName;
@Column(name="USER_MIDDLE_NAME")
private String middleName;
@Column(name="USER_EMAIL_ID")
private String emailId;
@Column(name="USER_PHONE_NO")
private Integer phoneNo;
@Column(name="USER_PASSWORD")
private String password;
@Column(name="USER_CONF_PASSWORD")
private String confPassword;
@Transient
private String token;
@Column(name="USER_CREATED_ON")
private Date createdOn;
@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
@Fetch(value = FetchMode.SUBSELECT)
@JoinTable(name = "USER_ROLES", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "ROLE_ID") })
private List<ActifioRoles> userRole = new ArrayList<ActifioRoles>();
@OneToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL,mappedBy="userDetails")
@Fetch(value = FetchMode.SUBSELECT)
private List<com.actifio.domain.Address> userAddress = new ArrayList<com.actifio.domain.Address>();
@OneToOne(cascade=CascadeType.ALL)
private Tenant tenantDetails;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getConfPassword() {
return confPassword;
}
public void setConfPassword(String confPassword) {
this.confPassword = confPassword;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
public List<ActifioRoles> getUserRole() {
return userRole;
}
public void setUserRole(List<ActifioRoles> userRole) {
this.userRole = userRole;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public Integer getPhoneNo() {
return phoneNo;
}
public void setPhoneNo(Integer phoneNo) {
this.phoneNo = phoneNo;
}
public List<com.actifio.domain.Address> getUserAddress() {
return userAddress;
}
public void setUserAddress(List<com.actifio.domain.Address> userAddress) {
this.userAddress = userAddress;
}
public Tenant getTenantDetails() {
return tenantDetails;
}
public void setTenantDetails(Tenant tenantDetails) {
this.tenantDetails = tenantDetails;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
どうすれば解決できますか?
Hibernateプロキシオブジェクトを介した遅延読み込みでも同様の問題が発生しました。遅延ロードされたプライベートプロパティを持つクラスに注釈を付けることで回避しました:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
JSONシリアル化をそのアノテーションに壊すプロキシオブジェクトにプロパティを追加できると思います。
問題は、エンティティが遅延してロードされ、完全にロードされる前にシリアル化が行われることです。
Hibernate.initialize(<your getter method>);
これを追加するだけで、同じ問題にぶつかりましたが、提供された回答は機能しませんでした。例外の提案を受け取り、application.propertiesファイルに追加して修正しました...
spring.jackson.serialization.fail-on-empty-beans=false
Hibernate 4.3でSpring Boot v1.3を使用しています
オブジェクト全体とネストされたオブジェクトをシリアル化するようになりました。
編集:2018
これはまだコメントを受け取っているので、ここで明確にします。このabsolutelyはエラーのみを隠します。パフォーマンスへの影響があります。当時、私は後でそれを配信して作業するために何かが必要でした(これはもうスプリングを使用しないことで行いました)。そのため、本当に問題を解決したい場合は、他の人の話を聞いてください。とりあえず使いたい場合は、この回答を使用してください。それはひどい考えですが、一体、あなたのために働くかもしれません。記録のために、この後再びクラッシュや問題が発生することはありませんでした。しかし、おそらくSQLパフォーマンスの悪夢になった原因です。
前の回答で正しく提案されているように、遅延読み込みとは、データベースからオブジェクトをフェッチするときに、ネストされたオブジェクトがフェッチされないことを意味します(必要に応じて後でフェッチされることもあります)。
ジャクソンはネストされたオブジェクトをシリアル化しようとします(== JSONを作成します)が、通常のオブジェクトではなくJavassistLazyInitializerを見つけるために失敗します。これが表示されるエラーです。今、それを解決する方法は?
前にCP510で提案されたように、1つのオプションはこの設定行によってエラーを抑制することです:
spring.jackson.serialization.fail-on-empty-beans=false
ただし、これは原因ではなく、症状に対処することです。エレガントに解決するには、このオブジェクトがJSONで必要かどうかを決める必要がありますか?
JSONのオブジェクトが必要な場合は、それを引き起こすフィールドからFetchType.LAZY
オプションを削除します(取得するルートエンティティだけでなく、ネストされたオブジェクトのフィールドである場合もあります)。
JSONのオブジェクトが不要な場合は、このフィールドのゲッター(または着信値を受け入れる必要がない場合はフィールド自体)に@JsonIgnore
の注釈を付けます。次に例を示します。
// this field will not be serialized to/from JSON @JsonIgnore private NestedType secret;
より複雑なニーズがある場合(たとえば、同じエンティティを使用する異なるRESTコントローラーの異なるルール)、jackson views または filtering を使用できます。単純なユースケースでは、ネストされたオブジェクトを個別にフェッチします。
Hibernateの遅延読み込みを処理するJackson用のアドオンモジュールを使用できます。
https://github.com/FasterXML/jackson-datatype-hibernate の詳細は、hibernate 3と4を個別にサポートしています。
問題は、エンティティを取得する方法だと思います。
たぶんあなたはこのようなことをしている:
Person p = (Person) session.load(Person.class, new Integer(id));
get
の代わりにload
メソッドを使用してみてください
Person p = (Person) session.get(Person.class, new Integer(id));
問題は、loadメソッドでは、実際のオブジェクトではなくプロキシのみを取得することです。プロキシオブジェクトにはプロパティが既にロードされていないため、シリアル化が発生したときに、シリアル化するプロパティはありません。 getメソッドを使用すると、実際のオブジェクトを実際に取得できますが、実際にはこのオブジェクトをシリアル化できます。
わたしにはできる
@JsonIgnoreProperties({"hibernateLazyInitializer","handler"})
例えば.
@Entity
@Table(name = "user")
@Data
@NoArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer","handler"})
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Date created;
}
この注釈をエンティティクラス(モデル)に追加すると、動作します。これにより、休止状態のプロキシオブジェクトを介した遅延読み込みが発生します。
@JsonIgnoreProperties({"hibernateLazyInitializer"、 "handler"})
この例外
org.springframework.http.converter.HttpMessageNotWritableException
なぜなら、あなたは応答出力をSerializableオブジェクトとして送信しているからです。
これは春に発生する問題です。この問題を克服するには、POJOオブジェクトを応答出力として送信します。
例:
@Entity
@Table(name="user_details")
public class User implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="id")
private Integer id;
@Column(name="user_name")
private String userName;
@Column(name="email_id")
private String emailId;
@Column(name="phone_no")
private String phone;
//setter and getters
POJOクラス:
public class UserVO {
private int Id;
private String userName;
private String emailId;
private String phone;
private Integer active;
//setter and getters
コントローラで、セリ化可能なオブジェクトフィールドをPOJOクラスフィールドに変換し、pojoクラスを出力として返します。
User u= userService.getdetials(); // get data from database
UserVO userVo= new UserVO(); // created pojo class object
userVo.setId(u.getId());
userVo.setEmailId(u.getEmailId());
userVo.setActive(u.getActive());
userVo.setPhone(u.getPhone());
userVo.setUserName(u.getUserName());
retunr userVo; //finally send pojo object as output.
Hibernate 5.2以降では、以下のようにhibernateプロキシを削除できます。これにより、実際のオブジェクトが提供されるため、適切にシリアル化できます。
Object unproxiedEntity = Hibernate.unproxy( proxy );
このソリューションは、@ marcoによる以下のソリューションから着想を得ています。また、これらのデータで彼の回答を更新しました。
ここでの問題は、サブオブジェクトの遅延読み込みに関するもので、ジャクソンは完全なオブジェクトではなく、休止状態のプロキシのみを検出します。
したがって、2つのオプションがあります。上記のほとんどの回答で行われたように、例外を抑制するか、LazyLoadオブジェクトがロードされていることを確認してください。
後者のオプションを選択する場合、解決策は jackson-datatype libraryを使用し、シリアル化の前に遅延読み込み依存関係を初期化するようにライブラリを構成することです。
そのために新しい構成クラスを追加しました。
@Configuration
public class JacksonConfig extends WebMvcConfigurerAdapter {
@Bean
@Primary
public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
Hibernate5Module module = new Hibernate5Module();
module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
mapper.registerModule(module);
messageConverter.setObjectMapper(mapper);
return messageConverter;
}
}
@Primary
は、他のBeanの初期化に他のJackson構成が使用されないようにします。 @Bean
は通常どおりです。 module.enable(Hibernate5Module.Feature.FORCE_LAZY_LOADING);
は、依存関係の遅延読み込みを有効にします。
注意-パフォーマンスへの影響に注意してください。 EAGERフェッチが役立つ場合もありますが、熱心にしたとしても、@OneToOne
を除く他のすべてのマッピングにプロキシオブジェクトが存在するため、このコードが必要になります。
PS:一般的なコメントとして、Json応答でデータオブジェクト全体を送り返すことは推奨しません。この通信にはDtoを使用し、mapstructなどのマッパーを使用してそれらをマップする必要があります。これにより、上記の例外と同様に、偶発的なセキュリティの抜け穴からあなたを救います。
Hibernateの場合、 jackson-datatype-hibernate プロジェクトを使用して、遅延ロードされたオブジェクトでのJSONシリアル化/非シリアル化に対応できます。
例えば、
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonDatatypeHibernate5Configuration {
// Register Jackson Hibernate5 Module to handle JSON serialization of lazy-loaded entities
// Any beans of type com.fasterxml.jackson.databind.Module are automatically
// registered with the auto-configured Jackson2ObjectMapperBuilder
// https://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper
@Bean
public Module hibernate5Module() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.enable( Hibernate5Module.Feature.FORCE_LAZY_LOADING );
hibernate5Module.disable( Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION );
return hibernate5Module;
}
}
変更しました(アノテーションモデルクラス)
fetch = FetchType.LAZY
に
fetch = FetchType.EAGER
そして、かなりの方法で働いた...
大好きです。
または、マッパーを次のように構成できます。
//遅延読み込みのカスタム構成
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);
Hibernateエンティティの関係が問題の原因である可能性があります...その関連エンティティの遅延読み込みを単に停止するだけです...たとえば... customerTypeにlazy = "false"を設定することで、以下で解決しました。
<class name="Customer" table="CUSTOMER">
<id name="custId" type="long">
<column name="CUSTID" />
<generator class="assigned" />
</id>
<property name="name" type="Java.lang.String">
<column name="NAME" />
</property>
<property name="phone" type="Java.lang.String">
<column name="PHONE" />
</property>
<property name="pan" type="Java.lang.String">
<column name="PAN" />
</property>
<many-to-one name="customerType" not-null="true" lazy="false"></many-to-one>
</class>
</hibernate-mapping>
これはジャクソンの問題です。これを防ぐには、ネストされた関係またはネストされたクラスをシリアル化しないようにジャクソンに指示してください。
次の例を見てください。 アドレスクラスのマッピング先市、州、および国クラスおよび州自体が指す国および国地域へ。 Spring boot REST APIを使用してアドレス値を取得すると、上記のエラーが発生します。これを防ぐには、マップされたクラス(レベル1のJSONを反映)をシリアル化し、@JsonIgnoreProperties(value = {"state"})
、@JsonIgnoreProperties(value = {"country"})
および@JsonIgnoreProperties(value = {"region"})
とのネストされた関係を無視するだけです。
これにより、上記のエラーとともにLazyload例外が防止されます。以下のコードを例として使用し、モデルクラスを変更します。
Address.Java
@Entity
public class Address extends AbstractAuditingEntity
{
private static final long serialVersionUID = 4203344613880544060L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "street_name")
private String streetName;
@Column(name = "apartment")
private String apartment;
@ManyToOne
@JoinColumn(name = "city_id")
@JsonIgnoreProperties(value = {"state"})
private City city;
@ManyToOne
@JoinColumn(name = "state_id")
@JsonIgnoreProperties(value = {"country"})
private State state;
@ManyToOne
@JoinColumn(name = "country_id")
@JsonIgnoreProperties(value = {"region"})
private Country country;
@ManyToOne
@JoinColumn(name = "region_id")
private Region region;
@Column(name = "Zip_code")
private String zipCode;
@ManyToOne
@JoinColumn(name = "address_type_id", referencedColumnName = "id")
private AddressType addressType;
}
City.Java
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = "city")
@Cache(region = "cityCache",usage = CacheConcurrencyStrategy.READ_WRITE)
@Data
public class City extends AbstractAuditingEntity
{
private static final long serialVersionUID = -8825045541258851493L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
//@Length(max = 100,min = 2)
private String name;
@ManyToOne
@JoinColumn(name = "state_id")
private State state;
}
State.Java
@Entity
@Table(name = "state")
@Data
@EqualsAndHashCode(callSuper = true)
public class State extends AbstractAuditingEntity
{
private static final long serialVersionUID = 5553856435782266275L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "code")
private String code;
@Column(name = "name")
@Length(max = 200, min = 2)
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "country_id")
private Country country;
}
Country.Java
@Entity
@Table(name = "country")
@Data
@EqualsAndHashCode(callSuper = true)
public class Country extends AbstractAuditingEntity
{
private static final long serialVersionUID = 6396100319470393108L;
@Id
@Column(name = "id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
@Length(max = 200, min = 2)
private String name;
@Column(name = "code")
@Length(max = 3, min = 2)
private String code;
@Column(name = "iso_code")
@Length(max = 3, min = 2)
private String isoCode;
@ManyToOne
@JoinColumn(name = "region_id")
private Region region;
}