詳解Spring Data Jpa當(dāng)屬性為Null也更新的完美解決方案
開場(chǎng)白
我本來(lái)是一名android開發(fā)者,突然就對(duì)java后端產(chǎn)生了濃烈的興趣。所以,立馬就轉(zhuǎn)到了后端。第一個(gè)項(xiàng)目使用的使用Spring Data Jpa來(lái)操作數(shù)據(jù)庫(kù)的,可是在更新數(shù)據(jù)的時(shí)候發(fā)現(xiàn)一個(gè)問(wèn)題,屬性值為Null竟然也更新,這就會(huì)導(dǎo)致本來(lái)沒(méi)有更新的屬性值,全部就成了Null。
原因
經(jīng)過(guò)一番度娘操作,原來(lái)Jpa,不知道你是想把屬性設(shè)置為Null,還是不想。
解決方法
找到一個(gè)方法,就是在數(shù)據(jù)模型上加上注解@DynamicUpdate,可是發(fā)現(xiàn)并不好使。而后經(jīng)過(guò)整理,找到以下解決方案
我們有如下實(shí)體
@Entity
public class User{
public User(){
}
@Id
@GeneratedValue
public Long id;
private String name;
private String mobileNo;
private String email;
private String password;
private Integer type;
private Date registerTime;
private String region;
private Integer validity;
setter...
getter...
}
需求:我們只更新用戶的名字,其他屬性值不變。
controller代碼如下
@RestController
public class UserController {
@Autowired
private UserDao userDao;
@PostMapping(value = "/save")
public String save(@RequestBody User u) {
userDao.save(u)
return "更新成功";
}
}
注意:如果我們只是更新用戶的名字,我們會(huì)這樣操作,如下只是提交需要更新的用戶的id和需要更新屬性的值,但是這樣的結(jié)果就是,其他沒(méi)有提交更改的屬性值,會(huì)被當(dāng)成Null,將數(shù)據(jù)庫(kù)中對(duì)應(yīng)值全部設(shè)為Null,為了解決這個(gè)問(wèn)題提出以下方案。
{
"id" : "1",
"name" : "張三"
}
方案如下:
說(shuō)明:
- 目標(biāo)源:請(qǐng)求更新的實(shí)體數(shù)據(jù)。
- 數(shù)據(jù)源:通過(guò)目標(biāo)源傳上來(lái)的id,去數(shù)據(jù)庫(kù)中查出的實(shí)體數(shù)據(jù)
我們可以將目標(biāo)源中需要改變的屬性值過(guò)濾掉以后,將數(shù)據(jù)源中的數(shù)據(jù)復(fù)制到目標(biāo)源中,這樣就達(dá)到了,只是更新需要改變的屬性值,不需要更新的保持不變。
工具類如下
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import java.beans.PropertyDescriptor;
import java.util.HashSet;
import java.util.Set;
/**
* There is no royal road to learning.
* Description:提交實(shí)體對(duì)象中的null賦值
* Created by 賢領(lǐng)·周 on 2018年04月10日 15:26
*/
public class UpdateTool {
/**
* 將目標(biāo)源中不為空的字段過(guò)濾,將數(shù)據(jù)庫(kù)中查出的數(shù)據(jù)源復(fù)制到提交的目標(biāo)源中
*
* @param source 用id從數(shù)據(jù)庫(kù)中查出來(lái)的數(shù)據(jù)源
* @param target 提交的實(shí)體,目標(biāo)源
*/
public static void copyNullProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, getNoNullProperties(target));
}
/**
* @param target 目標(biāo)源數(shù)據(jù)
* @return 將目標(biāo)源中不為空的字段取出
*/
private static String[] getNoNullProperties(Object target) {
BeanWrapper srcBean = new BeanWrapperImpl(target);
PropertyDescriptor[] pds = srcBean.getPropertyDescriptors();
Set<String> noEmptyName = new HashSet<>();
for (PropertyDescriptor p : pds) {
Object value = srcBean.getPropertyValue(p.getName());
if (value != null) noEmptyName.add(p.getName());
}
String[] result = new String[noEmptyName.size()];
return noEmptyName.toArray(result);
}
}
這里重點(diǎn)說(shuō)明一下, BeanUtils.copyProperties這個(gè)方法,網(wǎng)上很多教程都是存在誤區(qū)的,源碼如下:
/**
* 通過(guò)源碼不難看出,該方法就是將source的屬性值復(fù)制到target中
*
* @param source 數(shù)據(jù)源(也就是我們通過(guò)id去數(shù)據(jù)庫(kù)查詢出來(lái)的數(shù)據(jù))
* @param target 目標(biāo)源(也就是我們請(qǐng)求更新的數(shù)據(jù))
* @param ignoreProperties (需要過(guò)濾的字段)
*/
public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException {
copyProperties(source, target, (Class)null, ignoreProperties);
}
private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null;
PropertyDescriptor[] var7 = targetPds;
int var8 = targetPds.length;
for(int var9 = 0; var9 < var8; ++var9) {
PropertyDescriptor targetPd = var7[var9];
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
} catch (Throwable var15) {
throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15);
}
}
}
}
}
}
有了上面的工具類以后,我們的controller如下寫:
@RestController
public class UserController {
@Autowired
private UserDao userDao;
@PostMapping(value = "/save")
public String save(@RequestBody User u) {
if(u.getId != 0){
User source= userDao.findOne(u.getId);
UpdateTool.copyNullProperties(source, u);
}
userDao.save(u)
return "更新成功";
}
}
結(jié)果
這樣我們更新部分屬性值得時(shí)候,其他不更新的屬性值就不會(huì)設(shè)置為Null
{
"id" : "1",
"name" : "張三"
}
性能上肯定是有影響,但是目前整理可行的方案,如果有更好的解決方案歡迎留言。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java基礎(chǔ)之SpringBoot整合knife4j
Swagger現(xiàn)在已經(jīng)成了最流行的接口文檔生成與管理工具,但是你是否在用的時(shí)候也在吐槽,它是真的不好看,接口測(cè)試的json數(shù)據(jù)沒(méi)法格式化,測(cè)試地址如果更改了還要去改配置,接口測(cè)試時(shí)增加token驗(yàn)證是真的麻煩…針對(duì)Swagger的種種缺點(diǎn),Knife4j就呼之欲出了.需要的朋友可以參考下2021-05-05
淺談java7增強(qiáng)的try語(yǔ)句關(guān)閉資源
下面小編就為大家?guī)?lái)一篇淺談java7增強(qiáng)的try語(yǔ)句關(guān)閉資源。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
SpringBoot實(shí)現(xiàn)Word轉(zhuǎn)PDF和TXT的實(shí)踐分享
研發(fā)工作中難免會(huì)遇到一些奇奇怪怪的需求,就比如最近,客戶提了個(gè)新需求:上傳一個(gè)WORD文檔,要求通過(guò)系統(tǒng)把該文檔轉(zhuǎn)換成PDF和TXT,所以本文給大家分享了SpringBoot實(shí)現(xiàn)Word轉(zhuǎn)PDF和TXT的實(shí)踐,感興趣的朋友可以參考下2024-08-08
Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析
Spring的BeanFactoryPostProcessor是容器初始化的擴(kuò)展接口,允許在Bean實(shí)例化前修改或擴(kuò)展Bean的配置元數(shù)據(jù),本文給大家介紹Spring 中 BeanFactoryPostProcessor 的作用和示例源碼分析,感興趣的朋友一起看看吧2025-03-03
Mybatis-plus如何提前獲取實(shí)體類用雪花算法生成的ID
本文主要介紹了Mybatis-plus如何提前獲取實(shí)體類用雪花算法生成的ID,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
maven導(dǎo)入本地倉(cāng)庫(kù)jar包,報(bào):Could?not?find?artifact的解決
這篇文章主要介紹了maven導(dǎo)入本地倉(cāng)庫(kù)jar包,報(bào):Could?not?find?artifact的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
java實(shí)現(xiàn)flappy Bird小游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)flappy Bird小游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12

