今天来讲两个查询设计的考虑
查询结果为Null要不要抛异常
@Override
public yyyModel queryByXXX(String cardNo) {
YyyDO yyyDO = yyyDAO.selectByXXX(cardNo);
if (yyyDO == null) {
return null; // 这是直接返回的情况
// throw NullPointerException(); // 这是抛出异常的情况。 当然这里也可以是自定义的其他Exception
}
return YyyConvert.convertDO2Model(yyyDO);
}
在此,有个前提先阐述下,我们的系统架构分为多层,一般的调用链路是:业务Service实现 -> 内部共享Service -> core核心组件层 -> DAO&Mapper,这里所讲的查询结果为Null,发生在core核心组件层(该层主要职责为封装数据库等操作)
那么当查询到结果为Null时,应该如何处理,抛出Null Exception并不是不可以,但在实际场景中,一般不抛出异常,而是直接返回null,这里的核心原因在于:core层能否决定null是不是一个异常。一般来说,Null值是否异常,应是业务决定,也就是业务层,而core层的职责只是封装一些基本操作,如实返回;
当然很多项目会直接在业务层调用 mapper.query 方法,这种情况也就不存在查询为null要不要抛出异常的问题了,因为这个问题的关键设计在于层的职责设计;
select * 还是 select 指定字段?
先排除一下存在 text 及 long text 等大字段的情况,假设所有字段都是普通字段。在此假设的基础上,我们的选择是 select 全部字段,在我刚入行的时候,也经常纠结 select 指定需要的字段和全部字段的问题,但在后面的工作实践中,发现大部分情况下,完全没必要考虑这个问题,主要有几点:
1、select 全部字段,对编程比较友好,业务层根据自己的需要选择使用指定字段即可,不用因为业务的变化,导致后续数据层查询逻辑的变化,这对于一个项目“好维护”这一点非常之重要。
2、SQL的查询性能,很大程度上是设计和数据决定,而设计问题导致的性能问题往往多于数据问题,什么叫设计问题,比如说本来一个表可以搞定,设计成两个表(这种情况在新增某些业务功能时容易遇见),本来查询字段可以设计成等号,但因为设计导致只能使用 like ‘%xxx%‘,这种设计问题在项目中普遍存在;数据变多导致的查询问题,也算是比较常见,但因为数据量导致查询效率变差的业务也并不是很多,查询所有字段并不会引起性能上的问题;
两个过亿表的关联查询,普遍查询速度在10ms以内,极少数在500ms以内,这是我们目前实践下来的结果,当然这里数据库的配置也确实要做优化和调整,你说拿个几核心的,几G内存的,那肯定是做不到,我们所讲的性能问题,必然是在其硬件应该支撑的范围内谈,同时两个表也是精心设计过,这点也一样重要;
关于 text longtext 等字段类型,这些类型字段在查询时,确实可能会带来影响,所以表里若有这些类型的字段,那没办法确实要根据实际情况来区分查询,一般我们会设计成一个不带长字段,一个带长字段,不带的除了长字段,其他照样全部查询出来;
再讲一个知识点,text,longtext等大字段,在更新和删除时,会全量写入到Binlog,因此带这类字段的数据,最好少去更新或者删除
3、查询全部字段后,同一类的查询写一次就够了,不需要根据业务写多个查询条件,本质上这也是“好维护”的一种,但这里的设计理念是抽象与复用,抽象中核心的一些查询,之后复用就可以,上层业务在使用核心组件时,不会因为查询条件相同,查询字段不同的两个方法而陷入沉思,直接用就好了,这其实也是一种单一职责的设计理念!
这里核心的设计在于,通过牺牲一点可以承受的性能,换来整个团队在工程项目上的易于维护,总体来说是划算的。