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

本文介绍了在Spring Boot JPA中如何设置默认列值,包括使用默认属性值、@ColumnDefinition以及@DynamicInsert。通过示例展示了它们在Hibernate中的表现和可能的问题,强调了在使用时需要注意的语义一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

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

通过@ColumncolumnDefinition属性,我们可以指定表结构的字段定义,可以利用这一点指定 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实际上其namenull

@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,谢谢阅读。

可以从这里获取本文的完整测试用例。

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值