前面已经起了个头,今天接着往下找CRUD的道道,都是日常的小事,但道就隐藏在其中!
入参设计
做Java,正常来讲,大部分都是与业务功能打交道,真正用Java搞技术类的项目并不是没有,但占比一定少,实际工作中,遇到一些高并发,秒杀类的都算是比较有技术含量了,这也就造成了了一种假象,即:业务功能开发就不需要设计,就不用设计。其实不然,设计理念无处不在,业务功能开发亦需设计,排除功能本身设计不说,单拿个入参设计就有自己的一些门道!
先来两个接口设计:
java
// 设计一:
public BaseResult create(UserRequest request);
public BaseResult update(UserRequest request);
java
// 设计二:
public BaseResult create(UserRequest request);
public BaseResult update(UserUpdateRequest request);
以上两组接口设计哪个更优?显然是后者,但实际工作中,用前者的不在少数。从开发角度看,特别是0-1,最开始写下这个代码的开发来看,本身可能并没有什么问题,两个对象甚至属性完全相同,使用同一个Bean就很自然了,但这为后面的开发埋下了隐患,当遇到变更时,该不该调整 UserRequest,因为增加属性,可能对 update 方法没有用,而后面继续维护的人会疑惑 update 中没有用的属性,每次做处理都要特别小心。这明显违背了单一职责原则这一最基本的设计原则。
示例还比较明显,容易区分,实际业务中,往往是不同接口用了同一个入参对象,但不一定是
create update这种比较明显区别的情况,而是类似这种updateByXX(UserUpdateRequest request) updateByBB(UserUpdateRequest request),但其同样属于第一种设计
职责越清晰,越明确,变更时就越方便,只需要关心自己接口的入参,而不需要考虑对其他接口的影响。这就是一种接口稳定,易于维护的表现!
DupicateKeyException
数据库的异常很多,为什么单单要讲这个异常,因为这个异常他不仅是异常,还参与了一些重要功能的设计。
并发插入这个问题,在小规模的系统里,其实并算是一个问题,但在大流量,高吞吐量的系统里,确实存在这样的情况,那么如何处理并发写,就显得尤为重要。
最容易想到的方案就是在写之前先查一把,确认没有数据再写入,或者用 Redis 锁,前者其实存在一些问题,毕竟竞态条件本身就是线程不安全的,所以不能完全阻止,少部分情况是有效的,后者基本可以避免,但多了一次redis操作,也是有点不够干脆利落!
那么采用数据库天然自带的机制,从业务代码层面就显得非常干净,在条件允许的情况下,捕获这种异常并加以处理,将非常有益于代码的维护!
redis 锁的方案在最近一些年比较常见,可能是这锁的成本确实不高吧。
当然能够使用这个方案的,也有一定的前提条件,须有唯一索引,否则确实不行
一般我们这么做:
java
try {
Long impact = xxxDAO.insert(obj);
if (impact == null || impact.intValue() <= 0) {
logger.error("xxx异常,obj={}", obj);
throw new XXXRuntimeException(...); // 这里自定义异常,看项目情况做,不便细说
}
} catch (DuplicateKeyException e) {
logger.error("xxx异常,xx健重复,obj={}", obj);
throw new XXXRuntimeException(); // 同上
}
这样即使存在并发,也会随异常抛出,真正入库的肯定只会有一条记录。当然异常我默认大家都是有统一的方式处理的,不必与示例一样。