从零开始 Spring Boot 51:JPA 中的默认列值
图源:简书 (jianshu.com)
JPA 是一个 ORM 框架,因此,通常我们需要在实体类中定义表结构,这其中就包含可能的字段默认值。
本文介绍如何在 Hibernate(JPA)中设置默认列值(Default Column Value)。
默认属性值
最简单的方式是对实体类指定一个默认的属性值,比如:
@Data
@Table(name = "USER_TREE")
@Entity
public class Tree {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(nullable = false)
private Integer age = 5;
}
测试用例:
@Test
void testAddTreeWithDefaultValue(){
Tree tree = new Tree();
treeRepository.save(tree);
Assertions.assertEquals(5, tree.getAge());
}
这样做的缺点是由 Hibernate 自动生成的表结构中并不会体现字段的默认值:
CREATE TABLE `user_tree` (
`id` bigint NOT NULL,
`age` int NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
一般来说是不会产生什么影响的,但如果我们直接在数据库中执行 INSERT SQL,并且期望对拥有默认值的字段使用缺省,就无法实现。
columnDefinition
通过@Column
的columnDefinition
属性,我们可以指定表结构的字段定义,可以利用这一点指定 DDL 语句中的字段默认值:
@Data
@Table(name = "USER_TEACHER")
@Entity
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(columnDefinition = "varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'icexmoon'")
private String name;
}
Hibernate 生成的表结构中的确会出现字段的默认值:
CREATE TABLE `user_teacher` (
`id` bigint NOT NULL,
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'icexmoon',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
但遗憾的是,Hibernate 并不能识别columnDefinition
属性中的表字段默认值,并像我们期待的那样在添加实体实例时自动使用:
@Test
void testAddTeacherWithDefaultValue() {
Teacher teacher = new Teacher();
teacherRepository.save(teacher);
var findTeacher = teacherRepository.findAll().stream().findFirst().get();
Assertions.assertNull(findTeacher.getName());
}
这个示例中添加到数据库中的Teacher
实际上其name
是null
。
@DynamicInsert
可以使用@@DynamicInsert
解决上述问题,这个注解可以让实体的 INSERT SQL 排除值为null
的字段:
@DynamicInsert
// ...
public class Teacher {
// ...
}
测试用例:
@Test
void testAddTeacherWithDefaultValue() {
Teacher teacher = new Teacher();
teacherRepository.save(teacher);
var findTeacher = teacherRepository.findAll().stream().findFirst().get();
Assertions.assertNotNull(findTeacher.getName());
Assertions.assertEquals("icexmoon", findTeacher.getName());
}
可以看到此时的 Hibernate SQL 日志:
Hibernate: insert into user_teacher (id) values (?)
所以自然而然的,插入的数据中name
字段使用了 DDL name 字段的默认值。
但这样做依然有一个问题——不会体现在我们用于插入的实体上,需要重新从数据库加载实体才行:
@Test
void testAddTeacherWithDefaultValue2() {
Teacher teacher = new Teacher();
teacherRepository.save(teacher);
Assertions.assertNull(teacher.getName());
// ...
}
可以看到,Hibernate 并不会在这种情况下帮助我们自动重新加载实体(以获取通过数据库指定的字段默认值)。
默认属性+columnDefinition
更合理的做法是结合默认属性以及用columnDefinition
指定 DDL 字段默认值:
@Getter
@Setter
@Entity
@ToString
@EqualsAndHashCode
@Table(name = "USER_STUDENT")
@Accessors(chain = true)
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@EqualsAndHashCode.Exclude
private Long id;
public static final String DEFAULT_NAME = "icexmoon";
@Column(name = "NAME", columnDefinition = "varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT 'icexmoon'")
private String name = DEFAULT_NAME;
public static final LocalDate DEFAULT_BIRTH_DAY = LocalDate.of(2022, 1, 1);
@Setter(AccessLevel.NONE)
@Column(name = "BIRTH_DAY", columnDefinition = "date DEFAULT '2002-01-01'")
private LocalDate birthDay = DEFAULT_BIRTH_DAY;
@Transient
@Setter(AccessLevel.NONE)
@Getter(AccessLevel.NONE)
@EqualsAndHashCode.Exclude
@Nullable
private Integer age;
public static final Gender DEFAULT_GENDER = Gender.MALE;
@Enumerated(EnumType.ORDINAL)
@Column(name = "gender", columnDefinition = "tinyint DEFAULT '0'")
private Gender gender = DEFAULT_GENDER;
// ...
}
这样做其实依然有个缺陷,如果要修改属性默认值,最好同时修改
columnDefinition
中的default
值,否则就会出现语义上的不一致。我有尝试过在指定columnDefinition
值时用表达式指定默认值,但只能使用常量表达式,无法实现复杂语义。
测试用例:
@Test
void testNewStudentWithDefaultValue() {
Student student = new Student();
studentRepository.save(student);
Assertions.assertEquals(Student.DEFAULT_NAME, student.getName());
Assertions.assertEquals(Student.DEFAULT_BIRTH_DAY, student.getBirthDay());
Assertions.assertEquals(Student.DEFAULT_GENDER, student.getGender());
var findStudent = studentRepository.findOne(Example.of(new Student().setName(Student.DEFAULT_NAME))).get();
Assertions.assertNotNull(findStudent);
Assertions.assertEquals(Student.DEFAULT_NAME, findStudent.getName());
Assertions.assertEquals(Student.DEFAULT_BIRTH_DAY, findStudent.getBirthDay());
Assertions.assertEquals(Student.DEFAULT_GENDER, findStudent.getGender());
}
The End,谢谢阅读。
可以从这里获取本文的完整测试用例。
参考资料
- Default Column Values in JPA | Baeldung
- hibernate设置默认值