游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

从线上环境摘取了四个代码优化记录分享给大家

  前言
  因为前段时间新项目已经完成目前趋于稳定,所以最近我被分配到了公司的运维组,负责维护另外一个项目,包含处理客户反馈的日常问题,以及对系统缺陷进行优化。
  经过了接近两周的维护,除了日常问题以外,代码层面我一共处理了一个BUG,优化了三个问题,我把这四个问题归纳成了四段编码小技巧分享给大家,希望能有所帮助,今后若遇到类似的问题可以到我这里翻出来看看,想必能节省许多时间。
  技巧1、stream分组
  很多人都知道java8的stream很好用,但很多人其实不会用,或者说搜了许多资料还是用不好,归根究底就是许多百度的资料没有合适的案例,让人似懂非懂。我这里就从线上项目中提取出了一段stream分组的代码片段,帮大家一看就懂。
  首先,我把表结构展示一下,当然为了做案例简化了,方便理解。医生信息表
  id
  doctorname
  phone
  photourl
  areacode
  1hr张三
  13612345678hrhttps:head。img。comabc。png
  EAST
  2hr李四
  15845678901hrhttps:head。img。comxyz。png
  WEST院区表
  id
  areacode
  areaname
  1hrEAST
  东院区
  2hrSOUTH
  南院区
  3hrWEST
  西院区
  4hrNORTH
  北院区
  需求:查询医生信息列表,要展示院区名称。
  在我做优化之前,上一位同事是这么写的:查询医生列表ListDoctorVOdoctorVOListdoctorService。findDoctorList();遍历医生列表,装入院区名称。doctorVOList。forEach((vo){院区编码StringareaCodevo。getAreaCode();根据院区编码查询院区信息HospitalAreaDTOhospitalAreaDTOareaService。findOneByAreaCode(areaCode);放入院区名称vo。setAreaName(hospitalAreaDTO。getAreaName());});返回returndoctorVOList;
  可以看到,他是遍历医生列表,然后分别去查询每个医生所在院区的名称并返回,等于说若有100个医生,那么就要查询100次院区表,虽然MySQL8。0以后的查询效率其实变高了,这种小表查询其实影响没那么大,但作为一个成熟的线上项目,这种代码就是新手水平,我敢打包票很多人都这么写过。
  优化后:查询医生列表ListDoctorVOdoctorVOListdoctorService。findDoctorList();以areaCode为key将院区列表分组放入内存中MapString,ListHospitalAreaDTOareaMapareaService。findAll()。stream()。collect(Collectors。groupingBy(ee。getAreaCode()));遍历医生列表,装入院区名称。ListDoctorVOdoctorVOListnewArrayList();doctorVOList。forEach((vo){院区编码StringareaCodevo。getAreaCode();根据院区编码从map中拿到院区名称StringareaNameareaMap。get(areaCode)。get(0)。getAreaName();放入院区名称vo。setAreaName(areaName);});返回returndoctorVOList;
  可以看到,这里直接使用stream分组将院区信息按照院区编码为key,院区信息为value放入内存中,然后遍历医生列表时,根据院区编码直接从内存中取到对应的院区名称即可,前后只查询了1次,极大提高了效率,节省了数据库资源。
  只要是类似这种遍历查询需要从其他小表查出某属性值的场景时,都可以使用这种方式。
  2、stream排序
  这个排序其实很简单,就是根据客户要求的多个规则给医生列表排序,这里的规则是:按照是否在线、是否排班降序,且按照医生职称、医生编号升序。
  项目中用到了mybatis,所以之前的写法是直接写sql语句,但sql语句复杂一点的话后期交给其他同事是不好维护的。
  其实,查出列表后,直接在内存中通过stream进行排序就很舒适,所以我把项目中这部分的sql语句写法优化成了直接在代码中进行查询并排序。
  stream多属性不同规则排序:查询列表ListHomePageDoctorsDTOrespDTOListfindHomePageDoctorList();排序ListHomePageDoctorsDTOsortListrespDTOList。stream()。sorted(Comparator。comparing(HomePageDoctorsDTO::getOnlineFlag,Comparator。reverseOrder())。thenComparing(HomePageDoctorsDTO::getScheduleStatus,Comparator。reverseOrder())。thenComparing(HomePageDoctorsDTO::getDoctorTitleSort)。thenComparing(HomePageDoctorsDTO::getDoctorNo))。collect(Collectors。toList());返回returnsortList;
  上面一段代码就OK了,十分简单,reverseOrder()表示降序,不写就表示默认的升序。
  这里需要注意一点,网上很多资料都有用到:
  Comparator。comparing(HomePageDoctorsRespDTO::getOnlineFlag)。reverse()
  这样的方式来进行降序,这是有误区的,可以专门查下或试下reverse()的用法,它只是反转不是降序排列,类似于从左到右变为从右到左这样的形式,降序一定要用上面代码的写法,这是一个要注意的坑。
  3、异步线程
  异步线程很多人都知道,直接使用Async注解即可,但很多人不知道使用这个注解的限制条件,往往以为自己用上了,实际上根本没有走异步线程。Async注解只能标注在void方法上;Async注解标注的方法必须是public修饰;Async注解标注的方法和调用方在同一个类中,不会生效。
  以上条件缺一不可,哪怕满足前两个也不行,还是不会走异步线程。
  我维护的这个项目就是满足了前两个,实际上没有生效,说明写这段代码的同事想法是好的,希望不占用主线程从而提高接口效率,但实际上自己也没有充分测试,以为是有效的,我相信很多人也这么干过。
  这里,我优化了下,给大家一个最科学的写法,保证有效,这里我以发短信通知为例。
  首先,定义一个专门写异步方法的类叫AsyncService。异步方法的服务,不影响主程序运行。ServicepublicclassAsyncService{privatefinalLoggerlogLoggerFactory。getLogger(AsyncService。class);AutowiredprivatePhoneServicephoneService;发短信通知患者检查时间paramdto患者信息paramconsult咨询信息AsyncpublicvoidsendMsgToPatient(PatientDTOpatientDTO,ConsultDTOconsultDTO){消息内容StringphonepatientDTO。getTelphone();Stringmsg您好,patientDTO。getName(),已成功为你预约consultDTO。getDeviceType()检查,时间是consultDTO。getCheckDate(),望您做好检查时间安排。就诊卡号:consultDTO。getPatientId(),检查项目:consultDTO。getTermName();发短信phoneService。sendPhoneMsg(phone,msg);}}
  这里注意,使用public修饰符,void方法,前面限制条件已经讲过。
  其次,我们要在配置类中声明EnableAsync注解开启异步线程。ConfigurationEnableAsyncpublicclassAsyncConfigurationimplementsAsyncConfigurer{具体实现。。。。}
  最后,我们在业务方法中调用即可。publicBusinessResultdoBusiness(PatientDTOpatientDTO,ConsultDTOconsultDTO){处理业务逻辑,此处省略。。。。。。。异步发短信通知患者检查时间asyncService。sendMsgToPatient(patientDTO,consultDTO);}
  这样,这个发短信的业务就会走异步线程,哪怕有其他类似业务需要异步调用,也都可以放到AsyncService中去统一处理。
  我们还要注意一点,以上方式的异步线程实际上走的是默认线程池,而默认线程池并不是推荐的,因为在大量使用过程中可能出现线程数不够导致堵塞的情况,所以我们还要进一步优化,使用自定义线程池。
  这里,我们使用阿里开发手册中推荐的ThreadPoolTaskExecutor。ConfigurationEnableAsyncpublicclassAsyncConfigurationimplementsAsyncConfigurer{privatefinalLoggerlogLoggerFactory。getLogger(AsyncConfiguration。class);OverrideBean(nametaskExecutor)publicExecutorgetAsyncExecutor(){log。debug(CreatingAsyncTaskExecutor);ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor。setCorePoolSize(8);executor。setMaxPoolSize(50);executor。setQueueCapacity(1000);executor。setThreadNamePrefix(asyncExecutor);returnexecutor;}OverridepublicAsyncUncaughtExceptionHandlergetAsyncUncaughtExceptionHandler(){returnnewSimpleAsyncUncaughtExceptionHandler();}}
  这里,我们分别设置了核心线程数8、最大线程数50、任务队列1000,线程名称以asyncExecutor开头。
  这些配置其实可以提取出来放到yml文件中,具体配置多少要结合项目使用异步线程的规模以及服务器自身的水平来判断,我们这个项目用到异步线程的地方不算太多,主要是发短信通知和订阅消息通知时,而且服务器本身是8核16G,所以这个设置是相对符合的。
  4、统一异常管理
  统一异常管理是我着重要讲的,这次我维护的项目中在这块写的简直是难以忍受,线上排查问题很多重要的信息啥也看不到,检查代码发现明明用到了统一异常管理,但写法简直是外行水准,气的我肚子疼。
  首先,我说一下规范:统一异常管理后,如非必要绝不能再try。。。catch,如果必须try。。。catch请一定要log。error(e)记录日志打印堆栈信息,并且throw异常,否则该代码块出问题线上什么也看不到;
  统一异常管理后,接口层面校验错误时不要直接使用通用响应对象返回,比如ResultUtil。error(500,查询xx失败),这样会导致统一异常管理失去效能,因为这就是正常返回了一个对象,不是出现异常,所以我们应该在校验错误时直接thrownewBusinessException(查询xx失败)主动抛出一个异常,这样才会被捕获到;
  统一异常管理后,全局异常管理类中最好使用Spring自带的ResponseEntity包装一层,保证异常时HTTP状态不是200,而是正确的异常状态,这样前端工程师才能根据HTTP状态判断接口连通性,然后再根据业务状态判断接口获取数据是否成功。
  这里,我把项目中优化后的全局异常统一处理代码贴上来分享给大家:
  首先,我们自定义三个常用异常。
  校验参数的异常,继承运行时异常RuntimeException。参数不正确异常publicclassBadArgumentExceptionextendsRuntimeException{publicBadArgumentException(){super();}publicBadArgumentException(StringerrMsg){super(errMsg);}}
  校验权限的异常,继承运行时异常RuntimeException。无访问权限异常publicclassNotAuthorityExceptionextendsRuntimeException{publicNotAuthorityException(){super(没有访问权限。);}publicNotAuthorityException(StringerrMsg){super(errMsg);}}
  业务逻辑异常,继承运行时异常RuntimeException。业务逻辑异常publicclassBusinessExceptionextendsRuntimeException{publicBusinessException(){super();}publicBusinessException(StringerrMsg){super(errMsg);}publicBusinessException(StringerrMsg,Throwablethrowable){super(errMsg,throwable);}}
  其次,我们声明一个全局异常处理类。统一异常处理RestControllerAdviceSlf4jpublicclassExceptoinTranslator{权限异常ExceptionHandler(value{AccessDeniedException。class,NotAuthorityException。class})publicResponseEntityhandleNoAuthorities(Exceptionex){returnResponseEntity。status(HttpCodeEnum。FORBIDDEN。getCode())。body(ResultUtil。forbidden(ex。getMessage()));}参数错误异常ExceptionHandler(valueBadArgumentException。class)publicResponseEntityhandleBadArgument(Exceptionex){returnResponseEntity。status(HttpStatus。BADREQUEST。value())。body(ResultUtil。custom(HttpStatus。BADREQUEST。value(),ex。getMessage()));}接口参数校验异常ExceptionHandler(valueMethodArgumentNotValidException。class)publicResponseEntityhandleArguNotValid(MethodArgumentNotValidExceptionex){FieldErrorfieldErrorex。getBindingResult()。getFieldErrors()。get(0);Stringmsg!StringUtils。isEmpty(fieldError。getDefaultMessage())?fieldError。getDefaultMessage():参数不合法;returnResponseEntity。status(HttpStatus。BADREQUEST。value())。body(ResultUtil。custom(HttpStatus。BADREQUEST。value(),msg));}参数不合法异常ExceptionHandler(valueConstraintViolationException。class)publicResponseEntityhandleConstraintViolation(ConstraintViolationExceptionex){Stringerrex。getMessage();SetConstraintViolationlt;?setex。getConstraintViolations();if(!set。isEmpty()){errset。iterator()。next()。getMessage();}StringmsgStringUtils。isEmpty(err)?参数不合法:err;returnResponseEntity。status(HttpStatus。BADREQUEST。value())。body(ResultUtil。custom(HttpStatus。BADREQUEST。value(),msg));}参数不合法异常ExceptionHandler(value{IllegalArgumentException。class})publicResponseEntityhandleIllegalArgu(Exceptionex){Stringerrex。getMessage();StringmsgStringUtils。isEmpty(err)?参数不合法:err;returnResponseEntity。status(HttpStatus。BADREQUEST。value())。body(ResultUtil。custom(HttpStatus。BADREQUEST。value(),msg));}业务逻辑处理异常,也是我们最常用的主动抛出的异常。ExceptionHandler(valueBusinessException。class)publicResponseEntityhandleBadBusiness(Exceptionex){returnResponseEntity。status(HttpStatus。INTERNALSERVERERROR。value())。body(ResultUtil。custom(HttpStatus。INTERNALSERVERERROR。value(),ex。getMessage()));}HTTP请求方法不支持异常ExceptionHandler(valueHttpRequestMethodNotSupportedException。class)publicResponseEntitymethodNotSupportException(Exceptionex){returnResponseEntity。status(HttpStatus。METHODNOTALLOWED。value())。body(ResultUtil。custom(HttpStatus。METHODNOTALLOWED。value(),请求方法不支持!));}除上面以外所有其他异常的处理会进入这里ExceptionHandler(valueException。class)publicResponseEntityhandleException(Exceptionex){log。error(〔ExceptoinTranslator〕全局异常:,ex);returnResponseEntity。status(HttpStatus。INTERNALSERVERERROR。value())。body(ResultUtil。custom(HttpStatus。INTERNALSERVERERROR。value(),发生内部错误!));}}
  上面这个全局异常处理,包含了项目最有可能出现的:几种参数异常、权限异常、HTTP方法不支持异常、自定义业务异常、其他异常,基本上够用了,如果还想更细致一点还可以自定义其他的异常放进来。
  这里要关注的两点是:
  1、我们统一使用Spring的ResponseEntity进行了外层包装,而不是直接使用自定义响应对象ResultUtil来返回,这样保证了我们接口返回的业务状态和接口本身的HTTP状态是一致的,前端就可以判断接口连通性了,如果不明白区别,使用一下Postman就可以看到右上角的HTTP状态了,你使用自定义响应对象返回时永远都是200;
  2、最后其他所有异常Exception。class的捕获,务必进行log。error(ex)日志记录,这样线上排查时才能看到具体的堆栈信息。
  总结合理利用stream分组提高查询效率;
  stream排序避免踩坑;
  异步线程最佳用法;
  统一异常管理最佳使用方式。
  本文作者:福隆苑居士
  本文链接:https:www。cnblogs。comfulongyuanjuship16153733。html

很恐怖的电影20部一人勿看恐怖片大家或多或少都有看过,它能激发人们内心深处的恐惧,一个小小的细节就能引发你无数次的遐想,本文推荐很恐怖的电影20部,恐怖电影不适合独自一人看的电影,可以结伴在观。1很恐怖……广州再现街头溜车党,浩浩荡荡的车队真是帅呆了在广州大学城附近,我们可以看到一群群骑行者穿梭在街道中,也可以看到一群群年轻人踩着滑板、轮滑飞驰而过这样的刷街活动已经成为了很多年轻人心中的一种潮流生活方式,路人平时走在大街上……SpringCloud的配置文件bootstrap和applSpringCloud的配置文件bootstrap和application的区别零散的知识:这种小知识可能会在面试中被问到,因此有意无意就整理一下。关于bootst……宝马6系gt车衣施工效果展示,专业的隐形车衣,用心保护你的爱改造车型:BMW宝马6系gt车身颜色:贝尔尼纳灰改造产品:XPELLUXPLUS隐形车衣车型介绍:GT是GranTurismo的缩写,指适合长距离高速行……骁龙888游戏翻车,全是三星5nm工艺惹的祸?高通的最新一代旗舰处理器骁龙888,自打推出以来,网上争议就没有断过。这两天随着知名UP主极客湾的详细评测出炉之后,再次引爆全网。先来看一下极客湾测评结论:骁龙888性能……小丑获最佳男主角小丑的扮演者是谁讲述了什么在近日举办的奥斯卡颁奖典礼上面,电影《小丑》的男主角华金middot;菲尼克斯凭《小丑》获最佳男主角,电影《小丑》中ldquo;小丑rdquo;的扮演者是谁呢?电影《小丑》讲述……张子健甘十九妹图片张子健饰演尹剑平剧照大盘点张子健甘十九妹图片:张子健饰演尹剑平剧照大盘点《甘十九妹》是山东省三冠电影电视实业公司联合山东省影视制作中心与香港艺联影视剧集团根据美籍华人武侠作家萧逸先生的同名作著作改……青春斗最后结局怎么样,青春斗大结局剧情介绍都市剧《青春斗》给观众带来效果比较圆满,并把五个性格迥异的女孩产生的事情变十分圆满,特别逐渐找回自我的情况展现,更能明白命运经过锻炼后,自然可以产生向好的地方发展了。1青春斗最……韩国惊悚片十大韩国惊悚电影排行剧情及评价韩国惊悚片:十大韩国惊悚电影排行剧情及评价韩国十大惊悚电影排行,分分钟毛骨悚然。韩国惊悚片不仅逻辑缜密,充满智慧,而且情感表达细腻,氛围营造到位,代入感、悬念感强,整体感……Mac上使用docker的笔记Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2。0协议开源。Docker可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布……西游记中最恐怖的一集最恐怖是哪一集,恐怖真相曝光西游记中最恐怖的一集:最恐怖是哪一集,恐怖真相曝光西游记最恐怖的一集,师徒四人遭遇4万只妖怪(被吓傻)。西游记是家喻户晓的电视剧,剧情中主要描写的就是唐僧四人一路向西,闯……迪丽热巴长歌行即将收官李长歌人设无限极圈粉由朱锐斌执导,迪丽热巴、吴磊领衔主演的古装历史传奇剧《长歌行》即将迎来大结局,剧情的发展也进入无限极高潮阶段。在过去的日子中,观众们跟随着由迪丽热巴饰演的李长歌一路颠沛、一路成……
Flyme9再次更新,解决痛点是主要!一文看懂提到魅族这个品牌想必大家都很熟悉了,凭借着只出精品这一产品理念收获了广大消费者的认可与青睐,而今年所发布的魅族18系列也可以说是给我们交出了一份满意的答卷,小编认为,在软硬件方……颜值品质千元强机真的很难低调市面上有成千上万种音响设备,每一种都有着属于自己的特征,消费者在选购时往往也会眼花缭乱,不知道该关注哪种音响设备,更不知道如何选择好的音响设备。很多人纠结是选择智能音箱还……为什么没有大排量的国产车?是技术不够吗?真相很简单昨天,小木和朋友一起吃饭,聊到发动机排量的话题上。你一言我一语扯得就比较远,说到国产车为什么很少有大排量发动机?还能有啥原因?技术不到家呗!其中一位朋友略带讽刺语气地说。……618战报亮眼,苏宁易购这次是什么打法?每年的购物狂欢,让消费者纠结的无非是谁家商品更低价、买定离手后是否会再跌价?但复杂的套路,始终抵不过简单的低价。随着苏宁易购开启J10省钱计划,优惠产品从家电、3C到商超……原创购书作者阳龙生购书作者:阳龙生编辑:天美五星(2021年10月15日)去年上半年唐兄唐老师问我要买一本书吧,是农民作家高启伟的书,因他已患绝症,希望得到社会人士的关爱……免费升级Window11正式版,只需十分钟Windows11正式版系统将在105起开始推送,也许过完国庆假期回来,你身边的小伙伴都在用新系统了Windows11具有简洁美观的全新外观、更高的游戏性能,更多的安全支……微信聊天记录恐收费,网友嘲讽说好的不保留呢?将抓码青年设置星标第一时间接收最新文章你愿意为存储自己的微信聊天记录而付费吗?9月4号,一条消息登顶微博热搜微信拟推出聊天记录付费云存储服务。这条消息引发了千……料理机真的好用吗?斥巨资买了四台多功能料理机亲身试验最近刷小红书,感觉我的首页推荐都快被一个长得挺特别的黑科技料理机刷屏了。就这个叫田螺云厨的多功能料理机,好多小伙伴都在晒,光机身上这块iphone12那么大的触屏,看起来……服务器储存这些信息的量级可能达到了天文数字?现实世界永远都不会归零,因为现实世界是真真实实存在的,不可能归零的,精神世界倒是有可能归零,因为我们每个人都有一个属于自己的精神世界,如果我们离开了这个世界,那我们的精神世界也……多亏了这几个工具,搞定了http接口偶发415的问题推荐阅读:基于SSMShiroLayuiEasyui权限管理系统SpringCloudSpringCloudAlibabaMyBatisPlus打造的B2C商城源码智能办公OA……为了少预缴企业所得税,竟然敢这么筹划我们知道,企业在一年中每个季度的利润总额可能是不均衡的。有时候一年前面利润高,后两个季度又亏,如果前面多交了税,一方面资金占用多,另一方面年底汇算清缴退税申请也麻烦。……最低只需2499起,首款支持60倍数码变焦的手机价格再创新低如果5G对于你来说不是刚需!如果你所在的区域5G覆盖还没有那么快!如果你预算买一台2500价位的中端机!那么下面这款手机绝对比现在市场上的5G中端机更值得购买!OPPOR……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网