写点什么

使用 SpringDataJPA(Hibernate)动态更新数据的正确姿势

作者:redcoder54
  • 2023-11-24
    上海
  • 本文字数:1549 字

    阅读完需:约 5 分钟

一直以来我都是用 Mybatis 作为数据持久层框架,后面尝试了一下 SpringDataJPA,发现挺好用的,特别是其基于方法名的派生查询,对于简单的 sql 查询,不再需要编写 sql 了,只需要按照一定的格式定义好方法名,SpringDataJPA 会根据方法名,自动生成对应的 sql。

假设我们有一张用户表,只有 userid,username,sex,age 四个字段,对应的实体类(Entity)如下:


@Data@Entity(name = "User")@Table(name = "user")@DynamicUpdatepublic class User {
@Id @Column(name = "userid", nullable = false) private String userid;
@Column(name = "username", nullable = false) private String username;
@Column(name = "sex", nullable = false) private Integer sex;
@Column(name = "age", nullable = false) private Integer age;
}
复制代码


如果我们想根据 username 查询用户,只需要定义这样一个方法即可:


// 查询返回一条数据User findByUsername(String username);
// 查询返回多条数据List<User> findByUsername(String username);
复制代码


是不是很方便,对于简单的单表查询,再也不用手写 sql 了。


使用过 tk-mybatis 的朋友,你肯定熟悉它提供的 updateByXXXSelective 方法,动态更新非 null 的属性。Hibernate(SpringDataJPA 底层使用的是 Hibernate 框架)也支持类似的操作,在实体类上加上 @DynamicUpdate 注解,Hibernate 就会动态更新表数据(实际上更新地是实体类中发生变化的部分字段)。如果你是第一次使用 @DynamicUpdate,而且你习惯了 updateByXXXSelective 的使用方式,可能会形成思维定式,认为 Hibernate 的动态更新也是更新非 null 的字段。实际上并非如此,Hibernate 更新的是那些字段值发生变化的列,我们来看一个例子,你就明白了。


首先,我们往 user 表中插入一条数据:


insert into user(userid, username, sex, age) value (1,'david',1,30);
复制代码


接下来我想把 age 字段值改为 100,在不了解 Hibernate 的动态更新机制前,我是这样做的:


  @Resource  private UserRepository repository;      @Test  void updateAge() {      User user = new User();      user.setUserid(1);      user.setAge(100);      repository.save(user);  }
复制代码


更新之后,我发现数据变成这样了:



我只更新了 age 字段,但是 username 和 sex 的值却变成了 null,好奇怪。通过追踪 sql 日志,我发现 Hibernate 竟然把 username 和 sex 的值更新为 null 了。



这是为什么,跟我预想的不一样。通过一番搜索,我搞清楚了的 Hibernate 的动态更新机制:当你给一个实体类加上 @DynamicUpdate 后,Hibernate 会比较 entity 实体类中发生变化的字段值,只更新那些字段值发生变化的列。虽然我仅改变了 age 字段的值,但是 username 和 sex 字段值都变成了 null,于是 Hibernate 发现 username、sex 和 age 字段值都发生了变化,就把它们全部更新了。


搞清楚了 @DynamicUpdate 动态更新原理后,自然明白正确的做法应该是:先查询 user 1 的数据,然后在改变 age 属性的值,最后执行 save 方法,完成更新。


  @Test  void updateAge() {      User user = repository.findById(1).get();      user.setUserid(1);      user.setAge(100);      repository.save(user);  }
复制代码


查看 user 表,结果跟预期的一致,只有 age 值变成了 100,其他字段值数据保持不变:



细心的同学可能会发现,那这样不是多了一步查询操作吗,实际上并非如此。即使我们不主动查询 user 1 的数据,Hibernate 本身也会先去查询 user 1 的数据,不然它怎么确定哪些字段值发生了变化呢,这一点我们通过追踪 sql 日志也能看到:



总结一下,使用 @DynamicUpdate 的动态更新的正确做法应该是:先通过查询得到完整的实体类,然后更新你需要改变的属性值,最后再执行 save 方法,这样 Hibernate 只会更新 entity 中发生变化的属性值对应的 column。


发布于: 刚刚阅读数: 9
用户头像

redcoder54

关注

还未添加个人签名 2018-07-19 加入

还未添加个人简介

评论

发布
暂无评论
使用SpringDataJPA(Hibernate)动态更新数据的正确姿势_Java_redcoder54_InfoQ写作社区