1. 概述
初学者常误以为 @NotNull
和 @Column(nullable = false)
是一回事,可以随意替换使用。✅
但实际上,两者虽然最终都能防止字段存入 null
值,但作用机制和生效时机完全不同。❌
@NotNull
是 运行时的数据校验,由 Bean Validation 规范驱动@Column(nullable = false)
是 数据库 schema 的元数据定义,影响 DDL 生成
本文将从实战角度对比这两个注解的行为差异,帮你避开常见“坑”。
2. 依赖环境
本文示例基于 Spring Boot 项目,核心依赖如下:
<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 示例实体类
定义一个简单的 Item
实体用于测试:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
private BigDecimal price;
}
3. @NotNull 注解详解
@NotNull
来自 Bean Validation 规范(如 Hibernate Validator),属于通用校验注解,不仅限于 JPA 实体,任何 POJO 都可用。✅
3.1 使用示例
给 price
字段加上 @NotNull
:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@NotNull
private BigDecimal price;
}
尝试保存 price
为 null
的对象:
@SpringBootTest
public class ItemIntegrationTest {
@Autowired
private ItemRepository itemRepository;
@Test
public void shouldNotAllowToPersistNullItemsPrice() {
itemRepository.save(new Item());
}
}
执行结果抛出异常:
Caused by: javax.validation.ConstraintViolationException:
Validation failed for classes [com.example.demo.Item] during persist time for groups [javax.validation.groups.Default,]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, ...}
]
关键点分析 ✅
- ❌ SQL 语句未执行:Hibernate 在
pre-persist
阶段触发 Bean Validation,校验失败直接中断流程 - 异常类型为
ConstraintViolationException
,属于应用层校验错误 - 数据根本不会到达数据库,更安全、更早拦截
3.1 Schema 自动生成行为
若启用 Hibernate 自动建表(ddl-auto=create-drop
):
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
生成的 SQL 为:
create table item (
id bigint not null,
price decimal(19,2) not null,
primary key (id)
)
⚠️ 注意:price
列自动带上了 NOT NULL
约束!
原因揭秘
Hibernate 默认会将实体上的 Bean Validation 注解(如 @NotNull
)自动映射为数据库约束。这个行为由以下配置控制:
# 默认为 true,开启自动映射
spring.jpa.properties.hibernate.validator.apply_to_ddl=true
若想关闭此功能(比如希望 DB 层更宽松),可设置为 false
:
spring.jpa.properties.hibernate.validator.apply_to_ddl=false
此时生成的表结构中 price
将不再有 NOT NULL
。
4. @Column(nullable = false) 注解详解
@Column
是 JPA 规范的一部分,主要用于 定义数据库列的元数据,比如列名、长度、是否允许为空等。
4.1 使用示例
修改实体:
@Entity
public class Item {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false)
private BigDecimal price;
}
再次执行保存 null
的测试:
@Test
public void shouldNotAllowToPersistNullItemsPrice() {
itemRepository.save(new Item());
}
输出日志片段:
Hibernate:
insert into item (price, id) values (?, ?)
WARN SqlExceptionHelper: SQL Error: 23502, SQLState: 23502
ERROR SqlExceptionHelper: NULL not allowed for column "PRICE"
关键点分析 ✅
- ✅ SQL
INSERT
语句被成功生成并发送到了数据库 - ❌ 最终由 数据库层报错拒绝插入
- 异常为
DataIntegrityViolationException
,属于数据库约束冲突
⚠️ 这意味着:应用已经完成了持久化流程的大部分工作,直到执行 SQL 才失败,资源浪费且响应延迟。
4.1 启用 Hibernate 层空值检查
Hibernate 实际上支持在 session flush 阶段对 @Column(nullable = false)
字段做空值检查,但默认不开启。
需手动启用:
spring.jpa.properties.hibernate.check_nullability=true
开启后,再执行测试,抛出异常:
org.hibernate.PropertyValueException:
not-null property references a null or transient value : com.example.demo.Item.price
此时,Hibernate 在生成 SQL 前就拦截了 null 值,不会发送到数据库。
但注意:这个功能是 Hibernate 特有的,不是 JPA 标准行为,可移植性差。
5. 总结与建议
对比维度 | @NotNull |
@Column(nullable = false) |
---|---|---|
规范来源 | Bean Validation | JPA |
校验时机 | 持久化前(Pre-persist) | 可配置,但默认不校验 |
是否生成 DDL 约束 | ✅ 默认生成(需 apply_to_ddl=true ) |
✅ 生成 |
异常类型 | ConstraintViolationException |
PropertyValueException 或数据库异常 |
是否发送 SQL | ❌ 不发送(校验失败时) | ✅ 默认会发送 |
跨平台兼容性 | ✅ 高(标准 JSR-380) | ⚠️ 依赖 Hibernate 特性 |
✅ 推荐做法
优先使用
@NotNull
靠前拦截,减少无效数据库交互,符合“fail-fast”原则。配合
validation
依赖使用
确保spring-boot-starter-validation
在 classpath 中。保持
hibernate.validator.apply_to_ddl=true
让应用层校验自动同步到数据库 schema,避免不一致。@Column(nullable = false)
仅用于纯 DDL 控制场景
比如你不用 Bean Validation,或者只想控制建表。
⚠️ 踩坑提醒
- 单独使用
@Column(nullable = false)
而不开启check_nullability
,等于把校验甩锅给数据库,性能差、体验差 - 不引入
validation
依赖时,@NotNull
不会生效(只是个普通注解) - 多层校验时,注意
@Valid
和@NotNull
的组合使用,别漏掉级联校验
示例代码已托管至 GitHub:https://github.com/spring-examples/hibernate-notnull-vs-column