1. 概述
在这篇文章中,我们将探讨Hibernate的PropertyValueException
。我们将特别关注"not-null property references a null or transient value"错误消息。
Hibernate主要在两种情况下抛出PropertyValueException
:
- 当尝试将
null
值保存到标记为nullable = false
的列时。 - 当保存一个关联引用未保存实例的实体对象时。
2. Hibernate的空值检查
首先,让我们讨论一下Hibernate的@Column(nullable = false)
注解。如果没有其他Bean Validation存在,我们可以依赖Hibernate的空值检查。
此外,我们可以通过设置hibernate.check_nullability = true
来强制执行这种验证。为了重现以下示例,我们需要启用空值检查。
3. 保存null
值到非空列
现在,让我们利用Hibernate的验证机制来重现一个简单场景中的错误。我们将尝试保存一个没有设置必填字段的@Entity
。在这个例子中,我们将使用简单的Book
类:
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private String title;
// getters and setters
}
title
列的nullable
标志设置为false
。现在,如果我们不设置书名就试图保存Book
对象,会抛出PropertyValueException
:
@Test
public void whenSavingEntityWithNullMandatoryField_thenThrowPropertyValueException() {
Book book = new Book();
assertThatThrownBy(() -> session.save(book))
.isInstanceOf(PropertyValueException.class)
.hasMessageContaining("not-null property references a null or transient value");
}
因此,为了解决问题,我们需要在保存实体之前设置必填字段:book.setTitle("Clean Code")
。
4. 保存关联引用未保存实例
在这一节,我们将探讨一个更复杂场景中的常见情况。在这个例子中,我们将使用Author
和Article
实体,它们之间有一个双向关系:
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
@OneToMany
@Cascade(CascadeType.ALL)
private List<Article> articles;
// constructor, getters and setters
}
articles
字段带有@Cascade(CascadeType.ALL)
注解。因此,当我们保存Author
实体时,操作会传播到所有Article
对象:
@Entity
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
@ManyToOne(optional = false)
private Author author;
// constructor, getters and setters
}
现在,让我们尝试保存一个Author
和一些Articles
,看看会发生什么:
@Test
public void whenSavingBidirectionalEntityiesithCorrectParent_thenDoNotThrowException() {
Author author = new Author("John Doe");
author.setArticles(asList(new Article("Java tutorial"), new Article("What's new in JUnit5")));
assertThatThrownBy(() -> session.save(author))
.isInstanceOf(PropertyValueException.class)
.hasMessageContaining("not-null property references a null or transient value");
}
在处理双向关系时,我们可能会犯忘记从两个方向更新关联对象的常见错误。如果我们在Author
类的setter
方法中更新所有子articles
,就可以避免这种情况。
为了展示文章中提到的所有用例,我们将创建一个不同的方法。然而,从父实体的setter
方法中设置这些字段是一个好习惯:
public void addArticles(List<Article> articles) {
this.articles = articles;
articles.forEach(article -> article.setAuthor(this));
}
现在,我们可以使用新的设置方法,并期望不会出现错误:
@Test
public void whenSavingBidirectionalEntitesWithCorrectParent_thenDoNotThrowException() {
Author author = new Author("John Doe");
author.addArticles(asList(new Article("Java tutorial"), new Article("What's new in JUnit5")));
session.save(author);
}
5. 总结
在这篇文章中,我们了解了Hibernate验证机制的工作原理。首先,我们学习了如何在项目中启用nullability checking
。然后,我们举例说明了导致PropertyValueException
的主要原因,并学习了如何修复这些问题。
如往常一样,源代码可以在GitHub上找到。