在CRUD操作中與業(yè)務(wù)無(wú)關(guān)的SQL字段賦值的方法
提高效率一直是個(gè)永恒的話題,編程中有一項(xiàng)也是可以提到效率的,那就是專注做一件事情,讓其它沒(méi)有強(qiáng)緊密聯(lián)系的與之分開(kāi)。這里分享下我們做CRUD時(shí)遇到的常見(jiàn)數(shù)據(jù)處理場(chǎng)景:
•數(shù)據(jù)庫(kù)表字段全部設(shè)計(jì)為非空,即使這個(gè)字段在業(yè)務(wù)上是可以為空的,之所以將數(shù)據(jù)庫(kù)表字段全部設(shè)計(jì)為非空,這里有優(yōu)點(diǎn)也有缺點(diǎn),我們認(rèn)為優(yōu)點(diǎn)大于缺點(diǎn),所以選擇了它
優(yōu)點(diǎn):
1.獲取值時(shí),不用判斷這個(gè)字段是否為null,直接可用于邏輯運(yùn)算。
2.mysql DBA推薦此方案,可能是有利于性能,這里我并非求證過(guò)。
缺點(diǎn):
1.業(yè)務(wù)含義沒(méi)有null清楚,比如int字段默認(rèn)值設(shè)置成0,0就沒(méi)有null語(yǔ)義清晰。
2.在使用ORM插入數(shù)據(jù)時(shí),需要處理非空字段值為null的問(wèn)題。
• 系統(tǒng)字段的賦值,比如創(chuàng)建人,創(chuàng)建人id,創(chuàng)建時(shí)間,編輯人,編輯人id,編輯時(shí)間等,這些都需要在實(shí)際插入數(shù)據(jù)庫(kù)前賦值給Model。這些系統(tǒng)字段與具體的業(yè)務(wù)一般沒(méi)有太大的關(guān)聯(lián)關(guān)系,只是起到標(biāo)注數(shù)據(jù)被什么人在什么時(shí)間處理的,當(dāng)這些非業(yè)務(wù)相關(guān)的代碼充斥在代碼中時(shí),就顯得有些多余,而且這類代碼多了也會(huì)顯示冗余,最后帶來(lái)的結(jié)果就是非關(guān)鍵代碼比例大。
上面關(guān)于默認(rèn)值與null語(yǔ)義問(wèn)題不需要解決,因?yàn)槲覀冋J(rèn)為具有默認(rèn)值帶來(lái)的優(yōu)點(diǎn)遠(yuǎn)大于可空字段帶來(lái)的煩惱,我們來(lái)看默認(rèn)值與系統(tǒng)字段一般情況下如何處理:
•在操作ORM時(shí),將模型所有可空的字段都手動(dòng)賦值成默認(rèn)值,int的賦值為0等。
•在設(shè)計(jì)數(shù)據(jù)庫(kù)時(shí),將非空字段加上默認(rèn)值,讓數(shù)據(jù)庫(kù)來(lái)處理這些未插入值的字段,如果使用mybatis的話,mapper中提到的插入操作有兩個(gè):insert,insertSelective,后面這個(gè)insertSelective就是處理非空字段的,即插入的模型對(duì)于不需要賦值的字段就保持null值,數(shù)據(jù)庫(kù)在插入時(shí)生成的sql語(yǔ)句也不會(huì)包含這些字段,這樣就可以利用上數(shù)據(jù)庫(kù)的默認(rèn)值了。如果正巧數(shù)據(jù)庫(kù)的結(jié)構(gòu)當(dāng)初設(shè)計(jì)時(shí)沒(méi)有設(shè)計(jì)默認(rèn)值,又不能改的情況就比較糟糕了,情況回到上面手動(dòng)賦值,可能會(huì)出現(xiàn)類似如下的代碼:編寫(xiě)一個(gè)函數(shù)通過(guò)反射來(lái)解析每個(gè)字段,如果為null就修改為默認(rèn)值:
public static <T> void emptyNullValue(final T model) { Class<?> tClass = model.getClass(); List<Field> fields = Arrays.asList(tClass.getDeclaredFields()); for (Field field : fields) { Type t = field.getType(); field.setAccessible(true); try { if (t == String.class && field.get(model) == null) { field.set(model, ""); } else if (t == BigDecimal.class && field.get(model) == null) { field.set(model, new BigDecimal(0)); } else if (t == Long.class && field.get(model) == null) { field.set(model, new Long(0)); } else if (t == Integer.class && field.get(model) == null) { field.set(model, new Integer(0)); } else if (t == Date.class && field.get(model) == null) { field.set(model, TimeHelper.LocalDateTimeToDate(java.time.LocalDateTime.of(1990, 1, 1, 0, 0, 0, 0))); } } catch (IllegalAccessException e) { e.printStackTrace(); } } }
然后在代碼調(diào)用insert前調(diào)用函數(shù)來(lái)解決:
ModelHelper.emptyNullValue(request);
如何處理系統(tǒng)字段呢,在創(chuàng)建編輯數(shù)據(jù)時(shí),需要獲取當(dāng)前用戶,然后根據(jù)邏輯分別更新創(chuàng)建人信息以及編輯人信息,我們專門編寫(xiě)一個(gè)反射機(jī)制的函數(shù)來(lái)處理系統(tǒng)字段:
注:下面的系統(tǒng)字段的識(shí)別,是靠系統(tǒng)約定實(shí)現(xiàn)的,比如creator約定為創(chuàng)建人等,可根據(jù)不同的情況做數(shù)據(jù)兼容,如果系統(tǒng)設(shè)計(jì)的好,一般在一個(gè)系統(tǒng)下所有表的風(fēng)格應(yīng)該是相同的。
public static <T> void buildCreateAndModify(T model,ModifyModel modifyModel,boolean isCreate){ Class<?> tClass = model.getClass(); List<Field> fields = Arrays.asList(tClass.getDeclaredFields()); for (Field field : fields) { Type t = field.getType(); field.setAccessible(true); try { if(isCreate){ if (field.getName().equals(modifyModel.getcId())) { field.set(model, modifyModel.getUserId()); } if (field.getName().equals(modifyModel.getcName())) { field.set(model, modifyModel.getUserName()); } if (field.getName().equals(modifyModel.getcTime())) { field.set(model, new Date()); } } if (field.getName().equals(modifyModel.getmId())) { field.set(model, modifyModel.getUserId()); } if (field.getName().equals(modifyModel.getmName())) { field.set(model, modifyModel.getUserName()); } if (field.getName().equals(modifyModel.getmTime())) { field.set(model, new Date()); } } catch (IllegalAccessException e) { e.printStackTrace(); } } }
最后在數(shù)據(jù)處理前,根據(jù)創(chuàng)建或者編輯去調(diào)用函數(shù)來(lái)給系統(tǒng)字段賦值,這類代碼都混雜在業(yè)務(wù)代碼中。
ModifyModel modifyModel = new ModifyModel(); modifyModel.setUserId(getCurrentEmployee().getId()); modifyModel.setUserName(getCurrentEmployee().getName()); if (request.getId() == 0) { ModelHelper.buildCreateAndModify(request, modifyModel, true); deptService.insert(request); } else { ModelHelper.buildCreateAndModify(request, modifyModel, false); deptService.updateByPrimaryKey(request); }
我們可以利用參數(shù)注入來(lái)解決。參數(shù)注入的理念就是在spring mvc接收到前臺(tái)請(qǐng)求的參數(shù)后,進(jìn)一步對(duì)接收到的參數(shù)做處理以達(dá)到預(yù)期的效果。我們來(lái)創(chuàng)建
ManageModelConfigMethodArgumentResolver,它需要實(shí)現(xiàn)HandlerMethodArgumentResolver,這個(gè)接口看起來(lái)比較簡(jiǎn)單,包含兩個(gè)核心方法:
• 判斷是否是需要注入的參數(shù),一般通過(guò)判斷參數(shù)上是否有特殊的注解來(lái)實(shí)現(xiàn),也可以增加一個(gè)其它的參數(shù)判斷,可根據(jù)具體的業(yè)務(wù)做調(diào)整,我這里只以是否有特殊注釋來(lái)判定是否需要參數(shù)注入。
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(ManageModelConfig.class); }
• 參數(shù)注入,它提供了一個(gè)擴(kuò)展入口,讓我們有機(jī)會(huì)對(duì)接收到的參數(shù)做進(jìn)一步的處理。
@Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { Object manageModel =getRequestResponseBodyMethodProcessor().resolveArgument(parameter, mavContainer, webRequest, binderFactory); ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class); Employee currentUser = (Employee) servletRequest.getAttribute(DEFAULT_ATTRIBUTE_GET_USER_FROM_REQUEST); if (null == currentUser) { return manageModel; } ManageModelConfig parameterAnnotation = parameter.getParameterAnnotation(ManageModelConfig.class); ModelHelper.setDefaultAndSystemFieldsValue(manageModel, currentUser,parameterAnnotation.isSetDefaultFieldsValue()); return manageModel; }
這段函數(shù)有幾處核心邏輯:
•取得參數(shù)對(duì)象,因?yàn)槲覀兲幚淼氖莂jax請(qǐng)求的參數(shù),最簡(jiǎn)單的注入方法就是得到實(shí)際參數(shù)通過(guò)反射去處理默認(rèn)字段以及系統(tǒng)的值。ajax請(qǐng)求與form表單post提交的數(shù)據(jù)綁定略有不同,可參考之前文章分享的列表頁(yè)動(dòng)態(tài)搜索的參數(shù)注入(列表頁(yè)的動(dòng)態(tài)條件搜索)。獲取當(dāng)前請(qǐng)求參數(shù)對(duì)象,我們可以借助如下兩個(gè)對(duì)象配合來(lái)完成:
•RequestMappingHandlerAdapter
•RequestResponseBodyMethodProcessor
private RequestMappingHandlerAdapter requestMappingHandlerAdapter=null; private RequestResponseBodyMethodProcessor requestResponseBodyMethodProcessor = null; private RequestResponseBodyMethodProcessor getRequestResponseBodyMethodProcessor() { if(null==requestMappingHandlerAdapter) { requestMappingHandlerAdapter=new RequestMappingHandlerAdapter(); } if (null==requestResponseBodyMethodProcessor) { List<HttpMessageConverter<?>> messageConverters = requestMappingHandlerAdapter.getMessageConverters(); messageConverters.add(new MappingJackson2HttpMessageConverter()); requestResponseBodyMethodProcessor = new RequestResponseBodyMethodProcessor(messageConverters); } return requestResponseBodyMethodProcessor; }
通過(guò)如下代碼就可以取到參數(shù)對(duì)象了,其實(shí)就是讓spring mvc重新解析了一遍參數(shù)。
Object manageModel =getRequestResponseBodyMethodProcessor().resolveArgument(parameter, mavContainer, webRequest, binderFactory);
•如何獲取當(dāng)前用戶,我們?cè)诔晒Φ卿浵到y(tǒng)后,將當(dāng)前用戶的信息存儲(chǔ)在request中,然后就可以在函數(shù)中獲取當(dāng)前用戶,也可以采用其它方案,比如ThreadLocal,緩存等等。
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class); Employee currentUser = (Employee) servletRequest.getAttribute(DEFAULT_ATTRIBUTE_GET_USER_FROM_REQUEST);
•調(diào)用處理函數(shù)解決默認(rèn)字段以及系統(tǒng)的賦值,可以根據(jù)配置來(lái)決定是否處理字段默認(rèn)值。
ManageModelConfig parameterAnnotation = parameter.getParameterAnnotation(ManageModelConfig.class); ModelHelper.setDefaultAndSystemFieldsValue(manageModel, currentUser,parameterAnnotation.isSetDefaultFieldsValue());
最后將我們的參數(shù)注入邏輯啟動(dòng)起來(lái),這里選擇在xml中配置:
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"> <mvc:argument-resolvers> <bean class="cn.wanmei.party.management.common.mvc.method.annotation.ManageModelConfigMethodArgumentResolver"/> </mvc:argument-resolvers> </mvc:annotation-driven>
再看action中的調(diào)用:只需要在參數(shù)前面增加注解@ManageModelConfig,如果需要處理默認(rèn)值,則將啟用默認(rèn)值的選項(xiàng)設(shè)置成true即可,下面的實(shí)現(xiàn)部分完全看不到任何與業(yè)務(wù)無(wú)關(guān)的代碼。
@RequestMapping(value = "/addOrUpdateUser") @ResponseBody public Map<String, Object> addOrUpdateUser(@ManageModelConfig(isSetDefaultFieldsValue=true) EmployeeDto request) { Map<String, Object> ret = new HashMap<>(); ValidateUtil.ValidateResult result= new ValidateUtil().ValidateModel(request); boolean isCreate=request.getId() == 0; try { if (isCreate) { employeeService.insert(request); } else { employeeService.updateByPrimaryKey(request); } ret.put("data", "ok"); }catch (Exception e){ ret.put("err", e.getMessage()); } return ret; }
通過(guò)自定義實(shí)現(xiàn)HandlerMethodArgumentResolver,來(lái)捕獲ajax請(qǐng)求的參數(shù),利用反射機(jī)制動(dòng)態(tài)的將系統(tǒng)字段以及需要處理默認(rèn)值的字段自動(dòng)賦值,避免人工干預(yù),起到了代碼精簡(jiǎn),邏輯干凈,問(wèn)題統(tǒng)一處理的目的。需要注意的是這些實(shí)現(xiàn)都是結(jié)合當(dāng)前系統(tǒng)設(shè)計(jì)的,比如我們認(rèn)為id字段>0就代表是更新操作,為空或者等于小于0就代表是創(chuàng)建,系統(tǒng)字段也是約定名稱的等等。
相關(guān)文章
Navicat保存查詢和查詢文件放在哪個(gè)位置最佳方法推薦
這篇文章主要介紹了Navicat保存查詢和查詢文件放在哪個(gè)位置,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-08-08問(wèn)個(gè)高難度的復(fù)雜查詢(在一個(gè)時(shí)間段內(nèi)的間隔查詢)
問(wèn)個(gè)高難度的復(fù)雜查詢(在一個(gè)時(shí)間段內(nèi)的間隔查詢)...2007-04-04datagrip如何找到數(shù)據(jù)庫(kù)和表
這篇文章主要介紹了datagrip入坑指南(如何找到數(shù)據(jù)庫(kù)和表)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09NoSQL 數(shù)據(jù)庫(kù)你應(yīng)該了解的 10 件事
NoSQL 數(shù)據(jù)庫(kù)通常使用廉價(jià)服務(wù)器集群去管理迅猛發(fā)展的數(shù)據(jù)和交易容量,而 RDBMS 傾向依賴昂貴的專業(yè)服務(wù)器和存儲(chǔ)系統(tǒng)。結(jié)果就是,NoSQL 系統(tǒng)的每千兆字節(jié)成本或者每秒的交易成本要很多倍的低于 RDBMS 系統(tǒng),這使得你可以非常低的成本去存儲(chǔ)和處理更多的數(shù)據(jù)。2016-04-04在PostgreSQL中使用日期類型時(shí)一些需要注意的地方
這篇文章主要介紹了在PostgreSQL中使用日期類型時(shí)一些需要注意的地方,包括時(shí)間戳和日期轉(zhuǎn)換等方面,需要的朋友可以參考下2015-04-04