詳解Spring Data Jpa當(dāng)屬性為Null也更新的完美解決方案
開場(chǎng)白
我本來是一名android開發(fā)者,突然就對(duì)java后端產(chǎn)生了濃烈的興趣。所以,立馬就轉(zhuǎn)到了后端。第一個(gè)項(xiàng)目使用的使用Spring Data Jpa來操作數(shù)據(jù)庫的,可是在更新數(shù)據(jù)的時(shí)候發(fā)現(xiàn)一個(gè)問題,屬性值為Null竟然也更新,這就會(huì)導(dǎo)致本來沒有更新的屬性值,全部就成了Null。
原因
經(jīng)過一番度娘操作,原來Jpa,不知道你是想把屬性設(shè)置為Null,還是不想。
解決方法
找到一個(gè)方法,就是在數(shù)據(jù)模型上加上注解@DynamicUpdate,可是發(fā)現(xiàn)并不好使。而后經(jīng)過整理,找到以下解決方案
我們有如下實(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é)果就是,其他沒有提交更改的屬性值,會(huì)被當(dāng)成Null,將數(shù)據(jù)庫中對(duì)應(yīng)值全部設(shè)為Null,為了解決這個(gè)問題提出以下方案。
{ "id" : "1", "name" : "張三" }
方案如下:
說明:
- 目標(biāo)源:請(qǐng)求更新的實(shí)體數(shù)據(jù)。
- 數(shù)據(jù)源:通過目標(biāo)源傳上來的id,去數(shù)據(jù)庫中查出的實(shí)體數(shù)據(jù)
我們可以將目標(biāo)源中需要改變的屬性值過濾掉以后,將數(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)源中不為空的字段過濾,將數(shù)據(jù)庫中查出的數(shù)據(jù)源復(fù)制到提交的目標(biāo)源中 * * @param source 用id從數(shù)據(jù)庫中查出來的數(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)說明一下, BeanUtils.copyProperties這個(gè)方法,網(wǎng)上很多教程都是存在誤區(qū)的,源碼如下:
/** * 通過源碼不難看出,該方法就是將source的屬性值復(fù)制到target中 * * @param source 數(shù)據(jù)源(也就是我們通過id去數(shù)據(jù)庫查詢出來的數(shù)據(jù)) * @param target 目標(biāo)源(也就是我們請(qǐng)求更新的數(shù)據(jù)) * @param ignoreProperties (需要過濾的字段) */ 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)文章
Springboot?內(nèi)部服務(wù)調(diào)用方式
這篇文章主要介紹了Springboot?內(nèi)部服務(wù)調(diào)用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03JDBC簡(jiǎn)介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
什么是JDBC?這篇文章就為大家詳細(xì)介紹了Java語言中用來規(guī)范客戶端程序如何來訪問數(shù)據(jù)庫的應(yīng)用程序接口,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法
線程池(thread pool)是一種線程使用模式。線程過多或者頻繁創(chuàng)建和銷毀線程會(huì)帶來調(diào)度開銷,進(jìn)而影響緩存局部性和整體性能。這篇文章主要介紹了一種類似JAVA線程池的C++線程池實(shí)現(xiàn)方法,需要的朋友可以參考下2019-07-07springboot項(xiàng)目部署到k8s上的方法步驟
本文主要介紹了springboot項(xiàng)目部署到k8s上的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05