Hibernate @NotNull vs @Column(nullable = false) – Hibernate @NotNull vs @Column(nullable = false)

最后修改: 2019年 11月 30日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

At first glance, it may seem like both the @NotNull and @Column(nullable = false) annotations serve the same purpose and can be used interchangeably. However, as we’ll soon see, this isn’t entirely true.

乍一看,似乎@NotNull@Column(nullable = false)注解的目的相同,可以互换使用。然而,正如我们即将看到的,这并不完全正确。

Even though, when used on the JPA entity, both of them essentially prevent storing null values in the underlying database, there are significant differences between these two approaches.

即使在JPA实体上使用时,这两种方法基本上都能防止在底层数据库中存储null值,但这两种方法之间还是有很大的区别。

In this quick tutorial, we’ll compare the @NotNull and @Column(nullable = false) constraints.

在这个快速教程中,我们将比较@NotNull@Column(nullable = false)约束。

2. Dependencies

2.依赖性

For all the presented examples, we’ll use a simple Spring Boot application.

在所有介绍的例子中,我们将使用一个简单的Spring Boot应用程序。

Here’s a relevant section of the pom.xml file that shows needed dependencies:

这里是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. Sample Entity

2.1.实体样本

Let’s also define a very simple entity that we’ll be using throughout this tutorial:

让我们也定义一个非常简单的实体,我们将在本教程中使用它。

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    private BigDecimal price;
}

3. The @NotNull Annotation

3.@NotNull注释

The @NotNull annotation is defined in the Bean Validation specification. This means its usage isn’t limited only to the entities. On the contrary, we can use @NotNull on any other bean as well.

@NotNull注解在Bean Validation规范中定义。这意味着它的用法并不只限于实体。相反,我们也可以在任何其他Bean上使用@NotNull

Let’s stick with our use case though and add the @NotNull annotation to the Item‘s price field:

让我们坚持我们的用例,并将@NotNull注解添加到Itemprice字段。

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    @NotNull
    private BigDecimal price;
}

Now, let’s try to persist an item with a null price:

现在,让我们试着坚持一个带有null price的项目。

@SpringBootTest
public class ItemIntegrationTest {

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void shouldNotAllowToPersistNullItemsPrice() {
        itemRepository.save(new Item());
    }
}

And let’s see Hibernate’s output:

让我们看看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.baeldung.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.baeldung.h2db.springboot.models.Item, 
messageTemplate='{javax.validation.constraints.NotNull.message}'}]]
 
(...)
 
Caused by: javax.validation.ConstraintViolationException: Validation failed for classes 
[com.baeldung.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.baeldung.h2db.springboot.models.Item, 
messageTemplate='{javax.validation.constraints.NotNull.message}'}]

As we can see, in this case, our system threw javax.validation.ConstraintViolationException.

我们可以看到,在这种情况下,我们的系统抛出了javax.validation.ConstraintViolationException

It’s important to notice that Hibernate didn’t trigger the SQL insert statement. Consequently, invalid data wasn’t saved to the database.

值得注意的是,Hibernate并没有触发SQL插入语句。因此,无效的数据并没有保存到数据库中。

This is because the pre-persist entity lifecycle event triggered the bean validation just before sending the query to the database.

这是因为pre-persist实体生命周期事件在向数据库发送查询之前就触发了Bean验证。

3.1. Schema Generation

3.1 方案生成

In the previous section, we’ve presented how the @NotNull validation works.

在上一节中,我们已经介绍了@NotNull验证的工作原理。

Let’s now find out what happens if we let Hibernate generate the database schema for us.

现在让我们来看看,如果我们让Hibernate为我们生成数据库模式会发生什么

For that reason, we’ll set a couple of properties in our application.properties file:

为此,我们将在我们的application.properties文件中设置几个属性。

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

If we now start our application, we’ll see the DDL statement:

如果我们现在启动我们的应用程序,我们会看到DDL语句。

create table item (
   id bigint not null,
    price decimal(19,2) not null,
    primary key (id)
)

Surprisingly, Hibernate automatically adds the not null constraint to the price column definition.

令人惊讶的是,Hibernate自动将not null约束添加到price列定义中。

How’s that possible?

这怎么可能呢?

As it turns out, out of the box, Hibernate translates the bean validation annotations applied to the entities into the DDL schema metadata.

事实证明,开箱即用,Hibernate将应用于实体的bean验证注释翻译成DDL模式元数据。

This is pretty convenient and makes a lot of sense. If we apply @NotNull to the entity, we most probably want to make the corresponding database column not null as well.

这是很方便的,而且很有意义。如果我们对实体应用@NotNull,我们很可能想让相应的数据库列也变得not null

However, if, for any reason, we want to disable this Hibernate feature, all we need to do is to set hibernate.validator.apply_to_ddl property to false.

然而,如果出于任何原因,我们想禁用这个Hibernate功能,我们需要做的就是将hibernate.validator.apply_to_ddl属性设置为false。

In order to test this let’s update our application.properties:

为了测试这一点,让我们更新我们的application.properties

spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.validator.apply_to_ddl=false

Let’s run the application and see the DDL statement:

让我们运行该应用程序,看看DDL语句。

create table item (
   id bigint not null,
    price decimal(19,2),
    primary key (id)
)

As expected, this time Hibernate didn’t add the not null constraint to the price column.

正如预期的那样,这次Hibernate没有给not null列添加price约束

4. The @Column(nullable = false) Annotation

4.@Column(nullable = false)注释

The @Column annotation is defined as a part of the Java Persistence API specification.

@Column注释被定义为Java Persistence API规范的一部分

It’s used mainly in the DDL schema metadata generation. This means that if we let Hibernate generate the database schema automatically, it applies the not null constraint to the particular database column.

它主要用于DDL模式元数据的生成。这意味着如果我们让Hibernate自动生成数据库模式,它就会对特定的数据库列应用not null约束条件

Let’s update our Item entity with the @Column(nullable = false) and see how this works in action:

让我们用@Column(nullable = false)来更新我们的Item 实体,看看它是如何工作的。

@Entity
public class Item {

    @Id
    @GeneratedValue
    private Long id;

    @Column(nullable = false)
    private BigDecimal price;
}

We can now try to persist a null price value:

我们现在可以尝试坚持一个空的价格值。

@SpringBootTest
public class ItemIntegrationTest {

    @Autowired
    private ItemRepository itemRepository;

    @Test
    public void shouldNotAllowToPersistNullItemsPrice() {
        itemRepository.save(new Item());
    }
}

Here’s the snippet of Hibernate’s output:

下面是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"

First of all, we can notice that Hibernate generated the price column with the not null constraint as we anticipated.

首先,我们可以注意到,Hibernate按照我们的预期,生成了带有not null约束的价格列

Additionally, it was able to create the SQL insert query and pass it through. As a result, it’s the underlying database that triggered the error.

此外,它还能够创建SQL插入查询并将其通过。因此,是底层数据库触发了这个错误。

4.1. Validation

4.1.审定

Almost all the sources emphasize that @Column(nullable = false) is used only for schema DDL generation.

几乎所有的资料都强调,@Column(nullable = false)只用于生成模式DDL。

Hibernate, however, is able to perform the validation of the entity against the possible null values, even if the corresponding field is annotated only with @Column(nullable = false).

然而,Hibernate能够针对可能的null值执行实体验证,即使相应的字段只用@Column(nullable = false)来注解。

In order to activate this Hibernate feature, we need to explicitly set the hibernate.check_nullability property to true:

为了激活这个Hibernate功能,我们需要明确地将hibernate.check_nullability属性设置为true

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.check_nullability=true

Let’s now execute our test case again and examine the output:

现在让我们再次执行我们的测试案例并检查输出。

org.springframework.dao.DataIntegrityViolationException: 
not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price; 
nested exception is org.hibernate.PropertyValueException: 
not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price

This time, our test case threw the org.hibernate.PropertyValueException.

这一次,我们的测试案例抛出了org.hibernate.PropertyValueException

It’s crucial to notice that, in this case, Hibernate didn’t send the insert SQL query to the database.

关键是要注意,在这种情况下,Hibernate并没有向数据库发送插入的SQL查询

5. Summary

5.摘要

In this article, we’ve described how the @NotNull and @Column(nullable – false) annotations work.

在这篇文章中,我们已经描述了@NotNull@Column(nullable – false)注解是如何工作的。

Even though both of them prevent us from storing null values in the database, they take different approaches.

尽管它们都阻止我们在数据库中存储null值,但它们采取的方法不同。

As a rule of thumb, we should prefer the @NotNull annotation over the @Column(nullable = false) annotation. This way, we make sure the validation takes place before Hibernate sends any insert or update SQL queries to the database.

作为一个经验法则我们应该倾向于使用@NotNull注解而不是@Column(nullable = false)注解。这样,我们就能确保在Hibernate向数据库发送任何插入或更新的SQL查询之前进行验证。

Also, it’s usually better to rely on the standard rules defined in the Bean Validation, rather than letting the database handle the validation logic.

另外,通常依靠Bean Validation中定义的标准规则更好,而不是让数据库处理验证逻辑。

But, even if we let Hibernate generate the database schema, it’ll translate the @NotNull annotation into the database constraints. We must then only make sure that hibernate.validator.apply_to_ddl property is set to true.

但是,即使我们让Hibernate生成数据库模式,它也会将@NotNull注释翻译成数据库约束。然后,我们必须只确保hibernate.validator.apply_to_ddl属性被设置为true./strong>。

As usual, all the code examples are available over on GitHub.

像往常一样,所有的代码实例都可以在GitHub上找到