hibernate @notnull vs @column(nullable = false)
1. 简介
乍一看,*@NotNull和@Column(nullable = false)*注解似乎都有相同的用途,并且可以互换使用。**然而,我们很快就会看到,这并不完全正确。
尽管在 JPA 实体上使用时,它们都基本上防止在底层数据库中存储null值,但这两种方法之间存在显着差异。
在本快速教程中,我们将比较*@NotNull和@Column(nullable = false)*约束。
2. 依赖
对于所有提供的示例,我们将使用一个简单的Spring Boot 应用程序。
这是pom.xml文件的相关部分,显示了所需的依赖项:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
2.1.样本实体
让我们还定义一个非常简单的实体,我们将在本教程中使用它:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
private BigDecimal price;
}
3. @NotNull注解
@NotNull注解在Bean Validation 规范中定义。这意味着它的使用不仅限于实体。相反,我们也可以在任何其他 bean 上使用*@NotNull*。
让我们继续使用我们的用例,并将*@NotNull注解添加到Item的price*字段:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@NotNull
private BigDecimal price;
}
现在,让我们尝试使用null price来持久化一个项目:
@SpringBootTest
public class ItemIntegrationTest {
@Autowired
private ItemRepository itemRepository;
@Test
public void shouldNotAllowToPersistNullItemsPrice() {
itemRepository.save(new Item());
}
}
让我们看看 Hibernate 的输出:
2019-11-14 12:31:15.070 ERROR 10980 --- [ main] o.h.i.ExceptionMapperStandardImpl :
HHH000346: Error during managed flush [Validation failed for classes
[com.blogdemo.h2db.springboot.models.Item] during persist time for groups
[javax.validation.groups.Default,] List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class
com.blogdemo.h2db.springboot.models.Item,
messageTemplate='{javax.validation.constraints.NotNull.message}'}]]
(...)
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes
[com.blogdemo.h2db.springboot.models.Item] during persist time for groups
[javax.validation.groups.Default,] List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class
com.blogdemo.h2db.springboot.models.Item,
messageTemplate='{javax.validation.constraints.NotNull.message}'}]
如我们所见,在这种情况下,我们的系统抛出了javax.validation.ConstraintViolationException。
重要的是要注意 Hibernate 没有触发 SQL 插入语句。因此,无效数据不会保存到数据库中。
这是因为 pre-persist 实体生命周期事件在将查询发送到数据库之前触发了 bean 验证。
3.1. 模式生成
在上一节中,我们介绍了*@NotNull*验证的工作原理。
现在让我们看看如果让 Hibernate 为我们生成数据库模式会发生什么。
出于这个原因,我们将在application.properties文件中设置几个属性:
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
如果我们现在启动我们的应用程序,我们将看到 DDL 语句:
create table item (
id bigint not null,
price decimal(19,2) not null,
primary key (id)
)
令人惊讶的是,Hibernate 自动将非空约束添加到price列定义中。
这怎么可能?
事实证明**,Hibernate 开箱即用地将应用于实体的 bean 验证注解转换为 DDL 模式元数据。**
这很方便,也很有意义。如果我们将*@NotNull*应用于实体,我们很可能希望相应的数据库列也不为空。
但是,如果出于任何原因,我们想要禁用这个 Hibernate 功能,我们需要做的就是将hibernate.validator.apply_to_ddl属性设置为 false。
为了测试这一点,让我们更新我们的application.properties:
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
让我们运行应用程序并查看 DDL 语句:
create table item (
id bigint not null,
price decimal(19,2),
primary key (id)
)
正如预期的那样,这次 Hibernate 没有在price列中添加not null约束。
4. *@Column(nullable = false)*注解
@Column注解被定义为Java Persistence API 规范的一部分。
它主要用于 DDL 模式元数据生成。这意味着如果我们让 Hibernate 自动生成数据库模式,它会将非空约束应用于特定的数据库列。
让我们用*@Column(nullable = false)更新我们的Item*实体,看看它是如何工作的:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private BigDecimal price;
}
我们现在可以尝试保持一个空价格值:
@SpringBootTest
public class ItemIntegrationTest {
@Autowired
private ItemRepository itemRepository;
@Test
public void shouldNotAllowToPersistNullItemsPrice() {
itemRepository.save(new Item());
}
}
这是 Hibernate 输出的片段:
Hibernate:
create table item (
id bigint not null,
price decimal(19,2) not null,
primary key (id)
)
(...)
Hibernate:
insert
into
item
(price, id)
values
(?, ?)
2019-11-14 13:23:03.000 WARN 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper :
SQL Error: 23502, SQLState: 23502
2019-11-14 13:23:03.000 ERROR 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper :
NULL not allowed for column "PRICE"
首先,我们可以注意到Hibernate 生成的价格列具有我们预期的非空约束。
此外,它还能够创建 SQL 插入查询并将其传递。因此**,触发错误的是底层数据库。**
4.1. 验证
几乎所有来源都强调*@Column(nullable = false)*仅用于模式 DDL 生成。
然而,Hibernate 能够针对可能的null值执行实体验证,即使相应的字段仅使用@Column(nullable = false)进行注解。
为了激活这个 Hibernate 特性,我们需要显式地将hibernate.check_nullability属性设置为true:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.check_nullability=true
现在让我们再次执行我们的测试用例并检查输出:
org.springframework.dao.DataIntegrityViolationException:
not-null property references a null or transient value : com.blogdemo.h2db.springboot.models.Item.price;
nested exception is org.hibernate.PropertyValueException:
not-null property references a null or transient value : com.blogdemo.h2db.springboot.models.Item.price
这一次,我们的测试用例抛出了org.hibernate.PropertyValueException。
重要的是要注意,在这种情况下,Hibernate 没有将插入 SQL 查询发送到数据库。