我正在使用 Kotlin + Spring Boot + Hibernate 编写简单的 CRUD 并提出了这个问题:
我在
User
类中有一个名为 age
的字段,并输入 Int
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Size
@Entity
@Table(name = "users")
data class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long,
@NotBlank
@Column(unique = true)
@Size(min = 1, max = 100)
val username: String,
@NotBlank
@Size(max = 50)
val firstName: String,
@Size(max = 50)
val lastName: String?,
@Size(min = 1, max = 100)
val age: Int
)
然后我有创建用户的发布请求
@PostMapping
fun post(@RequestBody user: User): User {
log.debug("POST: {}", user)
return userRepository.save(user)
}
问题是,当我错过 JSON 中的
age
字段时,它不会响应 400 错误,而是默认为 0
。
如何将此字段设为必填?
我认为一个更大的问题是您将同一个类(您的实体)用于多种目的。这使得此类非常不灵活,因为您无法使其适应 REST 端点的需求以及 Hibernate 的需求。
我会做的是在两者之间划定界限(建议像Clean Architecture那样做):
data class UserDTO(val id: Long?,
var username: String?,
var firstName: String?,
var lastName: String?,
var age: Int?) {
fun toUser() = User(
id = id ?: signalError(),
username = username ?: signalError(),
firstName = firstName ?: signalError(),
lastName = lastName ?: signalError(),
age = age ?: signalError())
private fun signalError() : Nothing = throw IllegalArgumentException(
"Missing parameter in UserDTO.")
}
由于这篇post,我解决了这个问题, 还要感谢 @Adam Arold 展示最佳实践。
我创建了新的
UserDTO
类:
import ai.leavingstone.flywayspringdemo.domain.User
import javax.validation.constraints.*
data class UserDTO(
private val id: Long = -1,
@field:NotBlank
@field:Size(min = 1, max = 100)
private val username: String,
@field:NotBlank
@field:Size(max = 50)
private val firstName: String,
@field:Size(max = 50)
private val lastName: String?,
@field:NotNull
private val age: Int?
) {
fun toUser(): User = User(
id,
username,
firstName,
lastName,
age!!
)
}
在所有数据验证注释都不起作用之前,添加
@field
前缀后,它可以正常工作。 @NotNull
上的 Int
也可以正常工作,并且也是 id
字段的默认值。
您应该使用
@Valid @RequestBody user: User
作为方法参数,以便 spring 将验证给定的主体。这应该是默认完成的,但以防万一,把它写下来,这对新工人等来说更好。
此外@
Size
注释不支持基元或数字变量。它只是验证 String
、Array
、Map
、List
...的长度...所以只需用 @Min
和 @Max
进行注释即可。所以默认值 0 将不再有效。
另外,对于可空对象进行验证,您可以使用
Integer
而不是 Int
,这样如果您希望负数或 0 也有效,则可以仅检查 @NotNull
,而无需获取默认值 0。
您遇到的问题的发生是因为 Kotlin 中的
Int
被编译为 Java 中的原始类型 int
。反序列化时,像int
这样的原始类型不能被赋值为null
。相反,它们会自动使用默认值进行初始化,在 int
的情况下为 0
。此行为与 Java 处理基本类型的方式一致。
Java中其他基本类型的默认值如下:
int = 0
byte = 0
char = ('\u0000')
short = 0
long = 0
float = 0.0
double = 0.0
boolean = false
与 JSR 303(Bean Validation)或 JSR 380(Bean Validation 2.0)等验证框架交互时,这可能会导致意外行为。例如,像
Int
这样的字段在反序列化期间永远不会是 null
,因此 @NotNull
验证可能无法按预期工作,因为该字段将被分配默认值 0
而不是 null
。
现在我们了解了根本原因,有两种简单的方法可以解决此问题:
将字段更改为可空类型: 将
Int
等字段转换为可空类型,例如 Int?
。这样,Kotlin 会将它们编译成相应的包装类型(Java 中的 Integer
),并且 @NotNull
将按预期工作。然而,这种方法引入了不必要的字段可空性检查,理想情况下这些字段永远不应该是 null
。
data class Student(
@field:NotNull
val id: Int?, // Nullable type
...
)
这将确保该字段在反序列化期间确实可以是
null
,并且如果输入中缺少该字段,@NotNull
将正确触发验证。
使用
@Min
或 @Max
进行验证:
另一种选择是使用 @Min
或 @Max
等注释来验证字段,尽管这种方法可能感觉违反直觉。您可以将 0
值视为无效或“空等效”输入并相应地对其进行验证。
例如,如果
0
不是您字段的有效值,您可以添加验证约束来拒绝它:
data class Student(
@field:Min(1, message = "Value must be greater than 0")
val id: Int, // Non-nullable type
...
)
这样,即使该字段自动初始化为
0
,您也可以将 0
视为无效值并像处理 null
一样处理它。