在CRUD操作中与业务无关的SQL字段赋值的方法

作者:无名 - 数据库其它 -

提高效率一直是个永恒的话题,编程中有一项也是可以提到效率的,那就是专注做一件事情,让其它没有强紧密联系的与之分开。这里分享下我们做CRUD时遇到的常见数据处理场景:

•数据库表字段全部设计为非空,即使这个字段在业务上是可以为空的,之所以将数据库表字段全部设计为非空,这里有优点也有缺点,我们认为优点大于缺点,所以选择了它

优点:

1.获取值时,不用判断这个字段是否为null,直接可用于逻辑运算。

2.mysql DBA推荐此方案,可能是有利于性能,这里我并非求证过。

缺点:

1.业务含义没有null清楚,比如int字段默认值设置成0,0就没有null语义清晰。

2.在使用ORM插入数据时,需要处理非空字段值为null的问题。

• 系统字段的赋值,比如创建人,创建人id,创建时间,编辑人,编辑人id,编辑时间等,这些都需要在实际插入数据库前赋值给Model。这些系统字段与具体的业务一般没有太大的关联关系,只是起到标注数据被什么人在什么时间处理的,当这些非业务相关的代码充斥在代码中时,就显得有些多余,而且这类代码多了也会显示冗余,最后带来的结果就是非关键代码比例大。

上面关于默认值与null语义问题不需要解决,因为我们认为具有默认值带来的优点远大于可空字段带来的烦恼,我们来看默认值与系统字段一般情况下如何处理:

•在操作ORM时,将模型所有可空的字段都手动赋值成默认值,int的赋值为0等。

•在设计数据库时,将非空字段加上默认值,让数据库来处理这些未插入值的字段,如果使用mybatis的话,mapper中提到的插入操作有两个:insert,insertSelective,后面这个insertSelective就是处理非空字段的,即插入的模型对于不需要赋值的字段就保持null值,数据库在插入时生成的sql语句也不会包含这些字段,这样就可以利用上数据库的默认值了。如果正巧数据库的结构当初设计时没有设计默认值,又不能改的情况就比较糟糕了,情况回到上面手动赋值,可能会出现类似如下的代码:编写一个函数通过反射来解析每个字段,如果为null就修改为默认值:

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();
}
}
} 

然后在代码调用insert前调用函数来解决:

ModelHelper.emptyNullValue(request); 

如何处理系统字段呢,在创建编辑数据时,需要获取当前用户,然后根据逻辑分别更新创建人信息以及编辑人信息,我们专门编写一个反射机制的函数来处理系统字段:

注:下面的系统字段的识别,是靠系统约定实现的,比如creator约定为创建人等,可根据不同的情况做数据兼容,如果系统设计的好,一般在一个系统下所有表的风格应该是相同的。

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();
}
}
}

最后在数据处理前,根据创建或者编辑去调用函数来给系统字段赋值,这类代码都混杂在业务代码中。

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);
}

我们可以利用参数注入来解决。参数注入的理念就是在spring mvc接收到前台请求的参数后,进一步对接收到的参数做处理以达到预期的效果。我们来创建

ManageModelConfigMethodArgumentResolver,它需要实现HandlerMethodArgumentResolver,这个接口看起来比较简单,包含两个核心方法:

• 判断是否是需要注入的参数,一般通过判断参数上是否有特殊的注解来实现,也可以增加一个其它的参数判断,可根据具体的业务做调整,我这里只以是否有特殊注释来判定是否需要参数注入。

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(ManageModelConfig.class);
}

• 参数注入,它提供了一个扩展入口,让我们有机会对接收到的参数做进一步的处理。

@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;
}

这段函数有几处核心逻辑:

•取得参数对象,因为我们处理的是ajax请求的参数,最简单的注入方法就是得到实际参数通过反射去处理默认字段以及系统的值。ajax请求与form表单post提交的数据绑定略有不同,可参考之前文章分享的列表页动态搜索的参数注入(列表页的动态条件搜索)。获取当前请求参数对象,我们可以借助如下两个对象配合来完成:

•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;
} 

通过如下代码就可以取到参数对象了,其实就是让spring mvc重新解析了一遍参数。

Object manageModel =getRequestResponseBodyMethodProcessor().resolveArgument(parameter, mavContainer, webRequest, binderFactory); 

•如何获取当前用户,我们在成功登录系统后,将当前用户的信息存储在request中,然后就可以在函数中获取当前用户,也可以采用其它方案,比如ThreadLocal,缓存等等。

ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class);
Employee currentUser = (Employee) servletRequest.getAttribute(DEFAULT_ATTRIBUTE_GET_USER_FROM_REQUEST); 

•调用处理函数解决默认字段以及系统的赋值,可以根据配置来决定是否处理字段默认值。

ManageModelConfig parameterAnnotation = parameter.getParameterAnnotation(ManageModelConfig.class);
ModelHelper.setDefaultAndSystemFieldsValue(manageModel, currentUser,parameterAnnotation.isSetDefaultFieldsValue());

最后将我们的参数注入逻辑启动起来,这里选择在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中的调用:只需要在参数前面增加注解@ManageModelConfig,如果需要处理默认值,则将启用默认值的选项设置成true即可,下面的实现部分完全看不到任何与业务无关的代码。

@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;
}

通过自定义实现HandlerMethodArgumentResolver,来捕获ajax请求的参数,利用反射机制动态的将系统字段以及需要处理默认值的字段自动赋值,避免人工干预,起到了代码精简,逻辑干净,问题统一处理的目的。需要注意的是这些实现都是结合当前系统设计的,比如我们认为id字段>0就代表是更新操作,为空或者等于小于0就代表是创建,系统字段也是约定名称的等等。

IT人知识库 原文链接:http://www.itpeo.net/12848/459833.html





8 种常用的 NoSQL 数据库系统对比分析

Kristóf Kovács 是一位软件架构师和咨询顾问,他最近发布了一片对比各种类型NoSQL数据库的文章。 虽然SQ... ...

NoSQL开篇之为什么要使用NoSQL

NoSQL在2010年风生水起,大大小小的Web站点在追求高性能高可靠性方面,不由自主都选择了NoSQL技术作为优先考虑... ...

SQL中Having与Where的区别

含义 where 在分组之前就进行选择操作 having having在分组操作之后才进行选择操作,所以havin... ...

YII2数据库查询实践

初探yii2框架,对增删改查,关联查询等数据库基本操作的简单实践。 数据库配置。 /config/db.php 进行数据... ...

rfedfre

深入解析NoSQL数据库的分布式算法(图文详解)

尽管NoSQL运动并没有给分布式数据处理带来根本性的技术变革,但是依然引发了铺天盖地的关于各种协议和算法的研究以及实践。... ...

rfedfre

SQL select distinct的使用方法

在表中,可能会包含重复值。这并不成问题,不过,有时您也许希望仅仅列出不同(distinct)的值。关键词 distinc... ...

在SQL SERVER中查询数据库中第几条至第几条之间的数据SQL语句写法

今天在写程序的时候,需要生成从开始id到结束id的sql语句。原来不需要这个功能现在就需要了。 在SQL SERVER中... ...

数据库同步优化技巧分享

场景1: 需要将某张表的某些字段全部同步到另一张表的某些字段. INSERT INTO table1(a,b,c,d... ...

rfedfre

什么是数据库索引 有哪些类型和特点

有效优化VPS性能,提高VPS服务器运行速度,除了合理配置WEB服务器外,更多的是需要我们能够很好的优化网站程序及网站数... ...

SQL like子句的另一种实现方法(速度比like快)

一般来说使用模糊查询,大家都会想到LIKE  select * from table where a like... ...

NoSQL 数据库你应该了解的 10 件事

四分之一个世纪以来,关系型数据库(RDBMS)一直是主流数据库模型。但是现在非关系型数据库,“云”或者“NoSQL”数据... ...

Hadoop 2.x伪分布式环境搭建详细步骤

本文以图文结合的方式详细介绍了Hadoop 2.x伪分布式环境搭建的全过程,供大家参考,具体内容如下 1、修改hadoo... ...

rfedfre

Spring集成MyBatis完整实例(分享)

为了梳理前面学习的《Spring整合MyBatis(Maven+MySQL)一》与《Spring整合MyBatis(M... ...

rfedfre

Maven nexus 安装nexus私服出现的问题和解决办法

1. 在win10中安装nexus时提示:wrapper | OpenSCManager failed - 拒绝访问。 ... ...

rfedfre

详解数据库中跨库数据表的运算

1. 简单合并(FROM) 所谓跨库数据表,是指逻辑上同一张数据表被分别存储在不同数据库中。其原因有可能是因为数据量太大... ...

rfedfre

怎么让Birt报表脚本数据源变得既简单又强大

概述:运行在 JVM 上的 SQL 函数和存储过程 总所周知,有些数据库没有强大的分析函数(eg. Mysql), 有些... ...

rfedfre

数据计算中间件技术综述

传统企业大数据架构的问题 上图是大家都很熟悉的基于 Hadoop 体系的开源大数据架构图。在这个架构中,大致可以分成三... ...

一条慢SQL导致购物车服务无法使用的解决方案

概述 之前处理过一个购物车故障,觉得还挺经典的,在这里跟大家分享一下。这个故障直接导致前端添加购物车、获取用户购物车列表... ...

sql注入之必备的基础知识

什么是SQL注入(SQL Injection) 所谓SQL注入式攻击,就是攻击者把SQL命令插入到Web表单的输入域或页... ...

sql注入之新手入门示例详解

前言 在学习这篇文章之前,至于要学习了SQL注入的前提知识,可以参考之前写的一篇sql注入之必备的基础知识。 认识SQ... ...