Hibernate的非空错误
1. 概述
在本文中,我们将了解 Hibernate 的 PropertyValueException。特别是,我们将考虑 *“非空属性引用空值或瞬态值”*错误消息。
Hibernate主要会在两种情况下抛出PropertyValueException :
- 为标有nullable = false的列保存null值时
- 保存具有引用未保存实例的关联的实体时
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
}
标题列的可为null标志设置为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
}
文章字段具有*@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和一些Article,看看会发生什么:
@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以更新所有子Article,我们可以避免这种情况。
为了说明本文中介绍的所有用例,我们将为此创建一个不同的方法。但是,从父实体的设置器中设置这些字段是一个很好的做法:
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);
}