红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

从零开始 Spring Boot 51:JPA 中的默认列值

2023年6月29日 924点热度 0人点赞 0条评论

spring boot

图源:简书 (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设置默认值

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: column hibernate spring
最后更新:2023年6月29日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号