现在是Java培训机构林立的时代,更有360行,行行转java的谣传。但是,我们很多人都一味地追求知识面的广度,孜孜不倦地学习各种新技术。
网上大量地博客都打着xxx管理系统的亮眼标题,哇,免费的可以白嫖,赶紧下载,编译,部署。搭出来一个博客页面,有类型,文章的增删改查,各种新技术,boot,cloud,vue,感觉自己碉堡了。
但是,我们往往却疏忽了业务逻辑的锻炼,那些项目再好,页面再华丽,也无非就是最最简单的增删改查,为一大群crud boy提供了肥沃的土壤。
于是,又有人说,我要跳出舒适圈,去卷算法。
算法对应高阶的程序来说是有意义的,但是我们更多时候,工作还是写业务代码为主。
不要求你的代码有多么牛逼,但是要求你的代码质量过硬,不要频繁出现各种BUG,引起生产事故。
不要觉得这是一件很简单的事情哦,往往一个很小的功能,就会埋藏了数不清的坑。
甚至你都会觉得愕然,我TM一个妥妥的crud小能手,怎么遇到这么简单的需求,一不小心就跳进了坑里。
这是一个思维的问题,业务思维无法用crud的数量来升级。
要我说,对一个初入java行业的小白来说,最好的试金石就是这个修改唯一数据的问题。
先把环境搞一搞,为了方便起见,我直接使用了之前写的 《清新版 Springboot日记本系统》的代码,添加好test依赖:
org.springframework.boot
spring-boot-starter-test
直接搞起。
先来一个Customer类:
@Data
@Builder
public 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
方法而沾沾自喜:
/**
* 测试新增
*/
@Test
public void insert(){
//模拟前台传过来的Req
Customer customerDto = Customer.builder().name("火球鼠").idno("1").build();
//先检查身份证号是否存在于数据库中
checkIdno(customerDto.getIdno());
//正常入库
customerMapper.insert(customerDto);
}
private void checkIdno(String idno) {
LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
Customer one = customerMapper.selectOne(lambdaQueryWrapper.eq(Customer::getIdno, idno));
//不是空,说明有这个idno的数据
if(one != null){
throw new WrongArgumentException("身份证重复!");
}
}
测试,报错了,业务正确。
新增已经开了一个好头,接下来是编辑,作为一个刚毕业后进入公司的萌新,此时正兴致勃勃地敲起了键盘。
嗯,修改的时候正好可以复用刚刚封装的checkIdno
方法。
/**
* 测试修改
*/
@Test
public 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!
打完收工,注释完美,代码完整,我还封装了呢!
接下来就是升职加薪,迎娶白富美,从此走上人生巅峰了吧。
人生总是起起落落,酸甜苦辣都有,不可能只有甜。
直到有一天,测试小姐姐找到你,为什么我修改个姓名,也报错了呢?
你直接懵逼了,马上打开postman自测,结果发现还真有问题。
@Test
public 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不变
@Test
public void testEdit01(){
//模拟前台传过来的Req => 只改姓名不改身份证
Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("1").build();
edit(customerReq);
}
//测试编辑功能,修改idno但是和其他数据的idno一样
@Test
public void testEdit02(){
//模拟前台传过来的Req => 只改姓名不改身份证
Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("2").build();
edit(customerReq);
}
//测试编辑功能,修改idno但是和其他数据的idno都不一样
@Test
public void testEdit03(){
Customer customerReq = Customer.builder().id(1).name("喷火龙").idno("8").build();
edit(customerReq);
}
第一个测试是正常的,因为没有改idno。第二个测试报错了,也符合预期。第三个是正例,结果依然没问题。
上面的逻辑是没问题的,代码可读性也很强,但是不够简洁。在实际开发中,我也看到有人是这么处理的。
直接上代码,注释很详细了。
public void edit2(Customer customerReq){
/**
* 直接根据idno查询,无非有两种情况
* 1.查出来为null,说明没有这个idno,安全
* 2.查出来有一条数据,又分两种情况
* 2.1 这条数据就是要编辑的原数据:说明我们修改这条记录并且没改idno,安全
* 2.2 这条数据不是要编辑的原数据:说明我们修改这条记录并且改了idno,还和别的记录的idno重复了,不安全
* 总结:如果查出来有数据并且id和要编辑数据的id不同,才不安全
*/
LambdaQueryWrapper 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 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 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);
}
新增方法也修改下:
@Test
public 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成就。
看看现在视频网站上面各种脚手架的漂亮博客系统,收藏量和点赞量都高的吓人。
可是我个人觉得,这些对于真正提升自己的代码能力,帮助并不大。
一句话,真的,别被带歪了。
以上都属于我个人吐槽,仅代表我本人观点,欢迎一起交流讨论。本文也主要针对新入行的萌新小白,大佬轻喷。