使用 SpringDataJPA(Hibernate)动态更新数据的正确姿势
一直以来我都是用 Mybatis 作为数据持久层框架,后面尝试了一下 SpringDataJPA,发现挺好用的,特别是其基于方法名的派生查询,对于简单的 sql 查询,不再需要编写 sql 了,只需要按照一定的格式定义好方法名,SpringDataJPA 会根据方法名,自动生成对应的 sql。
假设我们有一张用户表,只有 userid,username,sex,age 四个字段,对应的实体类(Entity)如下:
如果我们想根据 username 查询用户,只需要定义这样一个方法即可:
是不是很方便,对于简单的单表查询,再也不用手写 sql 了。
使用过 tk-mybatis 的朋友,你肯定熟悉它提供的 updateByXXXSelective 方法,动态更新非 null 的属性。Hibernate(SpringDataJPA 底层使用的是 Hibernate 框架)也支持类似的操作,在实体类上加上 @DynamicUpdate 注解,Hibernate 就会动态更新表数据(实际上更新地是实体类中发生变化的部分字段)。如果你是第一次使用 @DynamicUpdate,而且你习惯了 updateByXXXSelective 的使用方式,可能会形成思维定式,认为 Hibernate 的动态更新也是更新非 null 的字段。实际上并非如此,Hibernate 更新的是那些字段值发生变化的列,我们来看一个例子,你就明白了。
首先,我们往 user 表中插入一条数据:
接下来我想把 age 字段值改为 100,在不了解 Hibernate 的动态更新机制前,我是这样做的:
更新之后,我发现数据变成这样了:
我只更新了 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 方法,完成更新。
查看 user 表,结果跟预期的一致,只有 age 值变成了 100,其他字段值数据保持不变:
细心的同学可能会发现,那这样不是多了一步查询操作吗,实际上并非如此。即使我们不主动查询 user 1 的数据,Hibernate 本身也会先去查询 user 1 的数据,不然它怎么确定哪些字段值发生了变化呢,这一点我们通过追踪 sql 日志也能看到:
总结一下,使用 @DynamicUpdate 的动态更新的正确做法应该是:先通过查询得到完整的实体类,然后更新你需要改变的属性值,最后再执行 save 方法,这样 Hibernate 只会更新 entity 中发生变化的属性值对应的 column。
版权声明: 本文为 InfoQ 作者【redcoder54】的原创文章。
原文链接:【http://xie.infoq.cn/article/0373b6a7a6dd1de76eb9884e1】。文章转载请联系作者。
评论