写在前面
现在是Java培训机构林立的时代,更有360行,行行转java的谣传。但是,我们很多人都一味地追求知识面的广度,孜孜不倦地学习各种新技术。
网上大量地博客都打着xxx管理系统的亮眼标题,哇,免费的可以白嫖,赶紧下载,编译,部署。搭出来一个博客页面,有类型,文章的增删改查,各种新技术,boot,cloud,vue,感觉自己碉堡了。
但是,我们往往却疏忽了业务逻辑的锻炼,那些项目再好,页面再华丽,也无非就是最最简单的增删改查,为一大群crud boy提供了肥沃的土壤。
于是,又有人说,我要跳出舒适圈,去卷算法。
算法对应高阶的程序来说是有意义的,但是我们更多时候,工作还是写业务代码为主。
不要求你的代码有多么牛逼,但是要求你的代码质量过硬,不要频繁出现各种BUG,引起生产事故。
试金石
不要觉得这是一件很简单的事情哦,往往一个很小的功能,就会埋藏了数不清的坑。
甚至你都会觉得愕然,我TM一个妥妥的crud小能手,怎么遇到这么简单的需求,一不小心就跳进了坑里。
这是一个思维的问题,业务思维无法用无数的crud来升级。
要我说,看一个初入java行业的小白来说,最好的试金石就是这个修改唯一数据的问题。
准备下环境
先把环境搞一搞,为了方便起见,我直接使用了之前写的 《清新版 Springboot日记本系统》的代码,添加好test依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId></dependency>
直接搞起。
先来一个Customer类:
@Data@Builderpublic class Customer { @TableId(type = IdType.AUTO) private Integer id; private String name; private String idno; private String isDel;}
对应表结构:
测试添加数据
@Slf4j@SpringBootTest(classes = {DiaryApplication.class})@RunWith(SpringJUnit4ClassRunner.class)public class MyTest { @Resource CustomerMapper customerMapper; @Test public void tt(){ customerMapper.insert(Customer.builder().name("小火龙").idno("1").build()); customerMapper.insert(Customer.builder().name("杰尼龟").idno("2").build()); customerMapper.insert(Customer.builder().name("妙蛙种子").idno("3").build()); }}
得到
至此,环境准备完毕。
身份证号唯一
需求很简单,要把身份证号设置唯一。
我们知道,id是自动递增的,这个没问题。身份证号唯一,无非就算新增和修改的时候判断一下呗。
新增判断唯一
这个简单,无非就是新增的时候判断一下idno有没有呗。 你很快就写出了代码,并且为自己封装了checkIdno方法而沾沾自喜:
/** * 测试新增 */@Testpublic void insert(){ //模拟前台传过来的Req Customer customerDto = Customer.builder().name("火球鼠").idno("1").build(); //先检查身份证号是否存在于数据库中 checkIdno(customerDto.getIdno()); //正常入库 customerMapper.insert(customerDto);}private void checkIdno(String idno) { LambdaQueryWrapper<Customer> lambdaQueryWrapper = new LambdaQueryWrapper<>(); Customer one = customerMapper.selectOne(lambdaQueryWrapper.eq(Customer::getIdno, idno)); //不是空,说明有这个idno的数据 if(one != null){ throw new WrongArgumentException("身份证重复!"); }}
测试,报错了,业务正确。
编辑判断唯一
新增已经开了一个好头,接下来是编辑,作为一个刚毕业后进入公司的萌新,此时正兴致勃勃地敲起了键盘。
嗯,修改的时候正好可以复用刚刚封装的checkIdno方法。
/** * 测试修改 */@Testpublic void edit(){ //模拟前台传过来的Req Customer customerReq = Customer.builder().id(1).idno("2").build(); //先检查身份证号是否存在于数据库中 checkIdno(customerReq.getIdno()); //根据id查询数据库的数据 Customer customerDto = getById(customerReq.getId()); //更新idno customerDto.setIdno(customerReq.getIdno()); //入库 customerMapper.updateById(customerDto);}private Customer getById(Integer id) { return customerMapper.selectById(id);}
封装性,封装性,这是培训班老师教的,能封装的就封装。 嗯,我又封装了一个getById方法,这是荣誉的勋章,也是我技术实力的证明!
上面代码测试修改小火龙,把身份证从1改成2,与杰尼龟冲突。
来吧,测试。
( •̀ ω •́ )yeah!
打完收工,注释完美,代码完整,我还封装了呢!
接下来就是升职加薪,迎娶白富美,从此走上人生巅峰了吧。
假如没改idno呢?
人生总是起起落落,酸甜苦辣都有,不可能只有甜。
直到有一天,测试小姐姐找到你,为什么我修改个姓名,也报错了呢?
你直接懵逼了,马上打开postman自测,结果发现还真有问题。
@Testpublic void edit(){ //模拟前台传过来的Req Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("1").build(); //先检查身份证号是否存在于数据库中 checkIdno(customerReq.getIdno()); //根据id查询数据库的数据 Customer customerDto = getById(customerReq.getId()); //更新name customerDto.setName(customerReq.getName()); //入库 customerMapper.updateById(customerDto);}
idno还是1,但是送过来了,小火龙本来的idno就是1,但是因为有了验重逻辑,导致报错了。
你顿时慌张起来,怎么就这么简单的一个小需求,还有这么多弯弯绕呢?
解决方案
一个最简单的思路就是,你编辑的时候,是不是肯定有个id传过来,毕竟我要根据这个id去更新嘛。
那么,我也可以通过这个id,去查询对应的idno,和这次送进来的idno是不是一样的。
如果跟原来的idno不一致,说明这次你改了idno,我就要进行校验,看你修改后的idno有没有和别的数据对应的idno重复。
方法就是拿着修改后的idno去查,查出来就说明有重复,否则就是唯一的,允许修改。
为了测试方便,给edit方法添加参数。
public void edit(Customer customerReq){ //根据id查询数据库是否有这条数据 Customer customer = customerMapper.selectById(customerReq.getId()); //如果不存在或者跟原来的idno不一致,才进行校验 Boolean isNeedCheck = true; if(customer != null && customer.getIdno().equals(customerReq.getIdno())){ isNeedCheck = false; } //检查身份证号是否存在于数据库中 if(isNeedCheck) { checkIdno(customerReq.getIdno()); } //根据id查询数据库的数据 Customer customerDto = getById(customerReq.getId()); //更新name customerDto.setName(customerReq.getName()); //更新idno customerDto.setIdno(customerReq.getIdno()); //入库 customerMapper.updateById(customerDto);}
然后是测试用例:
//测试编辑功能,修改其他字段但idno不变@Testpublic void testEdit01(){ //模拟前台传过来的Req => 只改姓名不改身份证 Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("1").build(); edit(customerReq);}//测试编辑功能,修改idno但是和其他数据的idno一样@Testpublic void testEdit02(){ //模拟前台传过来的Req => 只改姓名不改身份证 Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("2").build(); edit(customerReq);}//测试编辑功能,修改idno但是和其他数据的idno都不一样@Testpublic void testEdit03(){ Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("8").build(); edit(customerReq);}
第一个测试是正常的,因为没有改idno。第二个测试报错了,也符合预期。第三个是正例,结果依然没问题。
方案2
上面的逻辑是没问题的,代码可读性也很强,但是不够简洁。在实际开发中,我也看到有人是这么处理的。
直接上代码,注释很详细了。
public void edit2(Customer customerReq){ /** * 直接根据idno查询,无非有两种情况 * 1.查出来为null,说明没有这个idno,安全 * 2.查出来有一条数据,又分两种情况 * 2.1 这条数据就是要编辑的原数据:说明我们修改这条记录并且没改idno,安全 * 2.2 这条数据不是要编辑的原数据:说明我们修改这条记录并且改了idno,还和别的记录的idno重复了,不安全 * 总结:如果查出来有数据并且id和要编辑数据的id不同,才不安全 */ LambdaQueryWrapper<Customer> lambdaQueryWrapper = new LambdaQueryWrapper<>(); Customer one = customerMapper.selectOne(lambdaQueryWrapper.eq(Customer::getIdno, customerReq.getIdno())); if(Objects.nonNull(one) && !Objects.equals(one.getId(),customerReq.getId())){ throw new WrongArgumentException("身份证重复!"); } //根据id查询数据库的数据 Customer customerDto = getById(customerReq.getId()); //更新name customerDto.setName(customerReq.getName()); //更新idno customerDto.setIdno(customerReq.getIdno()); //入库 customerMapper.updateById(customerDto);}
思路就是直接通过idno去查,无非这四种情况。
直接根据idno查询,无非有两种情况:
1.查出来为null,说明没有这个idno,安全。
2.查出来有一条数据,又分两种情况。
2.1 这条数据就是要编辑的原数据:说明我们修改这条记录并且没改idno,安全。
2.2 这条数据不是要编辑的原数据:说明我们修改这条记录并且改了idno,还和别的记录的idno重复了,不安全。
总结:如果查出来有数据并且id和要编辑数据的id不同,才不安全。
乍一看似乎不好理解,但是只要我们把所有的情况都理清楚了,还是很清晰的。
这个方案确实很简洁,巧妙就巧妙在,不知不觉间它把新增的情况也包含了。
如果是新增,那么customerReq是没有id的,那么getId就是null,必然与one的getId不符合。也能走进这个逻辑:
LambdaQueryWrapper<Customer> lambdaQueryWrapper = new LambdaQueryWrapper<>();Customer one = customerMapper.selectOne(lambdaQueryWrapper.eq(Customer::getIdno, customerReq.getIdno()));if(Objects.nonNull(one) && !Objects.equals(one.getId(),customerReq.getId())){ throw new WrongArgumentException("身份证重复!");}
我们不妨把这个逻辑抽取出来封装成方法。
private void checkIdnoNew(Customer customerReq) { /** * 直接根据idno查询,无非有两种情况 * 1.查出来为null,说明没有这个idno,安全 * 2.查出来有一条数据,又分两种情况 * 2.1 这条数据就是要编辑的原数据:说明我们修改这条记录并且没改idno,安全 * 2.2 这条数据不是要编辑的元数据:说明我们修改这条记录并且改了idno,还和别的记录的idno重复了,不安全 * 总结:如果查出来有数据并且id和要编辑数据的id不同,才不安全 */ LambdaQueryWrapper<Customer> lambdaQueryWrapper = new LambdaQueryWrapper<>(); Customer one = customerMapper.selectOne(lambdaQueryWrapper.eq(Customer::getIdno, customerReq.getIdno())); if(Objects.nonNull(one) && !Objects.equals(one.getId(),customerReq.getId())){ throw new WrongArgumentException("身份证重复!"); }}
编辑方法就变成了这样:
public void edit2(Customer customerReq){ checkIdnoNew(customerReq); //根据id查询数据库的数据 Customer customerDto = getById(customerReq.getId()); //更新name customerDto.setName(customerReq.getName()); //更新idno customerDto.setIdno(customerReq.getIdno()); //入库 customerMapper.updateById(customerDto);}
新增方法也修改下:
@Testpublic void insert(){ //模拟前台传过来的Req Customer customerReq = Customer.builder().name("火球鼠").idno("6").build(); //先检查身份证号是否存在于数据库中 checkIdnoNew(customerReq); //正常入库 customerMapper.insert(customerReq);}
对比之前的方法:
//根据id查询数据库是否有这条数据Customer customer = customerMapper.selectById(customerReq.getId());//如果不存在或者跟原来的idno不一致,才进行校验Boolean isNeedCheck = true;if(customer != null && customer.getIdno().equals(customerReq.getIdno())){ isNeedCheck = false;}//检查身份证号是否存在于数据库中if(isNeedCheck) { checkIdno(customerReq.getIdno());}
是不是要简洁好多呢,这就是业务思维的魅力。
本文写成真的挺不容易,应该是现在网上少有的手把手教人理业务逻辑的博客了。看似一个小功能,却又这么多弯弯绕在里面,哈哈,没想到吧。
现在很多教程,包括很多视频课,都是一大堆技术给你安排上,动不动就商城啊,什么redis啊,ES啊,都给你整上。学了半天,看似学了很多,可往往都不精,成就了全栈HelloWolrd小能手。
很多人都跟我抱怨,为什么我学了那么多,乱七八糟的课程报了一大堆,商城系统也做了一堆,怎么去面试还是要被面试官diao呢,进了公司熬不过试用期就被开了?
那是因为,你还是没有养成真正的业务思维,思维还停留在学生时代,要别人把嚼碎了喂给你,缺少主动去钻研的精神。说白了,项目经验太少,不知道厂里到底要我干些什么?不要觉得会用MybatisPlus生成个Crud代码,会用redis简单的get/set,就能说精通了,现在行业内卷的时代,哪有这么简单。
可是你也许会说,不让我入职,我怎么积累项目经验?
看吧,又是死锁问题。
这些问题暴露出来,可能有网络上众多贩卖焦虑的因素在作怪,也有大环境的原因。
可是不论怎么样,我们都应该保持一颗平常心,多看,多练,不要一味地贪多,没必要达成各种helloworld成就。
看看现在视频网站上面各种脚手架的漂亮博客系统,收藏量和点赞量都高的吓人。
可是我个人觉得,这些对于真正提升自己的代码能力,帮助并不大。
一句话,真的,别被带歪了。
以上都属于我个人吐槽,仅代表我本人观点,欢迎一起交流讨论。本文也主要针对新入行的萌新小白,大佬轻喷。