关于Spring Data JPA更新部分字段的问题
1、问题背景
个人比较喜欢Spring data JPA,这次的问题是在实体类中使用List类型作为字段,JPA也提供了操作的方法,即使用@ElementCollection注解,网上对于JPA的知识比较零散,毕竟是不如Mybatis使用起来那么简单。
下面进入正题,来看下我的实体类中的字段:
@ElementCollection(fetch = FetchType.LAZY)//定义基本类型或可嵌入类的实例集合
@OrderColumn(name="position")//如果使用的是List,你需要多定义一个字段维护集合顺序
private List<String> part;
其实JPA内部对于集合类型的字段是使用另一张表进行维护。简单来说,省去了你自己去定义另一张表来进行一对多关系的维护。
所以,我想更新这张表的这条字段就不能像更新普通字段(String,int)一样了,那不一样在哪里呢?
JPA更新字段的手段有两种,一种是通过设置主键进行save()保存,一种是通过@Query注解。
如果使用第二种,在前面已经说了其实是对另一张表的操作,所以我们只需要对另一张表增删改查就好了。
但这样个人觉得有点没发挥JPA的个性,所以我想使用第一种。
但是使用第一种方法有两个问题:
- 实体类的部分字段LAZY懒加载会不会导致更新后这部分字段为NULL?
- JPA为什么能够知道我们调用save()方法是updata更新而不是insert新增?
请看下面的测试用例。
2、测试用例
下面看下定义的实体类,主要由主键id,字段name,以及集合part组成,集合为Lazy懒加载。
@Entity
@Table(name = "name_href")
public class NameHref {
@Id
@GeneratedValue()
private int id;
@Column(name = "name")
private String name;
private String href;
@ElementCollection(fetch = FetchType.LAZY)
@OrderColumn(name="position")
private List<String> part;
......
当我们定义集合part为Lazy懒加载,正常来说使用JPA获取实体后是取不到part的值的,(执行get方法会报错)所以更新字段后part的值到底是不是为NULL,我们来看单元测试:
@Autowired
NameHrefRepository nameHrefRepository;
@Test
public void getHref() {
NameHref nameHref = new NameHref();
nameHref.setId(-1);
String name = "博客园";
nameHref.setName(name);
nameHref.setHref("http://www.cnblogs.com");
ArrayList<String> objects = new ArrayList<>();
objects.add("安卓");
objects.add("苹果");
nameHref.setPart(objects);
nameHrefRepository.save(nameHref);
NameHref byName = nameHrefRepository.findAllByName(name);
byName.setHref("http://www.baidu.com");
nameHrefRepository.save(byName);
}
这段代码是先新建一个实体保存到数据库然后再获取该实体,修改部分字段,使用save()方法保存。执行完后我们查看数据库字段:href属性已经被成功修改,而且声明为LAZY的集合part也还在。说明save()方法正确执行了updata操作。那JPA究竟如何执行的,看下SQL记录:
Hibernate: select namehref0_.id as id1_20_, namehref0_.href as href2_20_, namehref0_.name as name3_20_ from name_href namehref0_ where namehref0_.name=?
Hibernate: select namehref0_.id as id1_20_0_, namehref0_.href as href2_20_0_, namehref0_.name as name3_20_0_ from name_href namehref0_ where namehref0_.id=?
Hibernate: update name_href set href=?, name=? where id=?
一共执行了三句SQL,第一句是由find方法执行的查询操作,第二句第三句是由save()方法进行的操作。由此可知,JPA更新字段的原理大概是先执行select语句判断是否数据已存在,若存在则执行updata语句进行更新操作。那JPA是如何对save()操作进行分辨的呢?答案是主键是否被赋值。来看下面的测试方法:
NameHref nameHref = new NameHref();
// nameHref.setId(-1);
String name = "博客园";
nameHref.setName(name);
nameHref.setHref("http://www.cnblogs.com");
nameHrefRepository.save(nameHref);
上面的代码把setId()方法注释掉后,执行程序,查看SQL打印:
Hibernate: insert into name_href (href, name) values (?, ?)
只执行了insert语句,由此可知,JPA对程序调用的save()方法判断是updata或者insert操作的依据是看实体对象的主键是否被赋值。
到这里还没有结束,如果你不是通过Repository获取的实体对象,而是自己定义实体对象并对主键赋值,想达到更新部分字段的目的,那么你通过save()方法更新字段后会出现未定义的字段为NULL的情况。来看下面的测试用例:
NameHref nameHref = new NameHref();
nameHref.setId(1);
String name = "博客园2号";
nameHref.setName(name);
nameHrefRepository.save(nameHref);
在上面的代码中,我们想要达到更新数据表中ID为1的name字段的值,并保持其它字段的值不变。执行程序后,我们查看数据表:
除了字段id,name(name被成功修改),其它字段都变成了NULL,这是为什么呢?来看下SQL执行记录:
Hibernate: select namehref0_.id as id1_20_0_, namehref0_.href as href2_20_0_, namehref0_.name as name3_20_0_ from name_href namehref0_ where namehref0_.id=?
Hibernate: update name_href set href=?, name=? where id=?
Hibernate: delete from name_href_part where name_href_id=?
查看SQL打印,我们可知JPA首先进行了更新前的判断,查看是否已存在。然后进行更新操作,不过此时的JPA似乎没有那么聪明,它不知道我们只想更新部分字段,而是当成了更新全部字段,导致没有被我们进行赋值的其它字段都成了NULL,并且主动删除了关联表的字段part。由此说明,手动新建实体对象并进行更新操作是不可取的。
3、总结一波
JPA更新字段的手段有两种,一种是通过设置主键进行save()保存,一种是通过@Query注解。
使用save()方法更新字段一定要通过Repository获取实体对象,在此对象上进行更新操作。
推荐我的另一篇文章:两行代码玩转Spring Data排序与分页
关于Spring Data JPA更新部分字段的问题的更多相关文章
- spring data jpa查询部分字段、多余附加字段
spring data jpa查询部分字段 第一种方法:使用 model 查询时转化 首先建立一个 model ,写上自己想要查询的字段,然后写上构造函数,这步很重要,因为spring jpa 转化时 ...
- Spring Date JPA 更新部分字段
在Spring Data JPA 中,新增和更新操作都是用save()的方式进行,JPA是通过什么方法来知道我们是要进行insert还是update呢? 经过测试,JPA对程序调用的save()方法判 ...
- 如何使用Sping Data JPA更新局部字段
问题描述 在更新数据时,有时候我们只需要更新一部分字段,其他字段保持不变.Spring Data JPA并未提供现成的接口,直接使用save()更新会导致其他字段被Null覆盖掉. 解决办法 通常有两 ...
- spring data jpa自定义bean字段映射
当遇到复杂多表查询时,并且同时还需要确保查询性能,此时则需要使用自定义sql查询,然而spring data jpa对于自定义sql则需使用查询需要在对应的bean中做太多的配置映射,我尝试了一下,最 ...
- spring data jpa 查询部分字段列名无效问题
spring data jpa原生sql查询问题,我只要表其中的几个字段的值,本以为写个原生sql,拿实体类对象去接没问题 结果列名无效,测试了一下,把返回值类型改成List<Object> ...
- Spring Data Jpa 更新操作
第一步,通过Repository对象把实体根据ID查询出来 第二部,往查出来的实体对象进行set各个字段 第三步,通过Repository接口的save方法进行保存 保存和更新方式(已知两种) 第一种 ...
- spring data jpa 查询自定义字段,转换为自定义实体
目标:查询数据库中的字段,然后转换成 JSON 格式的数据,返回前台. 环境:idea 2016.3.4, jdk 1.8, mysql 5.6, spring-boot 1.5.2 背景:首先建立 ...
- spring data jpa 查询部分字段
@Query("select new map(ah as ah,salq as sqlq,yg as yg, bg as bg,ay as ay) FROM Aj where ahdm=?1 ...
- spring data jpa开启批量插入、批量更新
spring data jpa开启批量插入.批量更新 原文链接:https://www.cnblogs.com/blog5277/p/10661096.html 原文作者:博客园--曲高终和寡 *** ...
随机推荐
- Android 基本控件的常用属性
TextView //normal 默认 bold 粗体 italic斜体 可用| 多选 android:textStyle //设置限定可以输入哪些字符 android:digits //设置文本可 ...
- python语言学习--1
第一天 item: 当字符串中出现 反斜杠'\' 时,输出时会把它当做转义字符处理,所以结果中不会出现它,若要正常输出则需要在字符串前面加上r, 我想r的意思是religion即“原始”的意思: it ...
- 关于商米D1S,USB默认权限在关机后丢失的FAQ
1.机器型号:商米D1S 2.机器系统版本:7.1.2 3.情况描述:双屏的机器不管是银盒子收银还是银盒子智能收银,勾选默认后重启机器还是会提示, 4.解决方案:商米厂商大约会在1月份进行系统更新,到 ...
- PHP 生成器语法
一般你在迭代一组数据的时候,需要创建一个数据,假设数组很大,则会消耗很大性能,甚至造成内存不足. //Fatal error: Allowed memory size of 1073741824 by ...
- python高级(2)—— 基础回顾2
回顾知识 一 操作系统的作用: 隐藏丑陋复杂的硬件接口,提供良好的抽象接口 管理.调度进程,并且将多个进程对硬件的竞争变得有序 关于操作系统的发展史,可以参考我之前的一篇博文:传送门 二 多道技术: ...
- python第一百零八天---Django 3 session 操作
上节内容回顾: 1.请求周期 url> 路由 > 函数或类 > 返回字符串或者模板语言? Form表单提交: 提交 -> url > 函数或类中的方法 - .... Ht ...
- Android开发--Service和Activity通过广播传递消息
Android的Service也运行在主线程,但是在服务里面是没法直接调用更改UI,如果需要服务传递消息给Activity,通过广播是其中的一种方法: 一.在服务里面发送广播 通过intent传送数据 ...
- Lua中的#
Lua中的 对字符串来说,#取字符串的长度,但对于table需要注意. lua的table可以用数字或字符串等作为key, #号得到的是用整数作为索引的最开始连续部分的大小, 如果t[1] == ni ...
- js对WebApi请求的基本操作
在WebAPI对外提供的,大概有4种接口,get,post,delete,put,现在,我就简单的来说一下js请求webApi的方式和大概的作用: get:在webApi中,get方法通常是用来获取数 ...
- MySQL注入与防御
1.简介 1.1.含义 在一个应用中,数据的安全无疑是最重要的.数据的最终归宿都是数据库,因此如何保证数据库不被恶意攻击者入侵是一项重要且严肃的问题! SQL注入作为一种很流行的攻击手段,一直以来都受 ...