项目心得(一)

这两天完成了项目中的一个不是很大的功能实现,功能虽小,但是暴露出较多的问题.在这里做简单记录总结.

功能简述

器具受检率统计是一个实体,有一个年份字段,器具每年可以受检也可以不检.由于新的一年,器具的基本信息都不会改变,但是器具要重新统计是否受检,所以受检总数变为0.由于大部分的字段都不用改变,所以需要一个clone方法,clone上一年份的所有信息,然后将年份和受检总数重新设置.

实现构想

  1. 获取系统当前年份,进而求得上一年年份

  2. 根据年份获取上一年的所有记录

  3. 遍历所有记录,将年份重置为今年年份,受检总数重置为0,其他属性直接复制

遇到的问题

(一)如何获取所有记录?

第一想法,for循环.但马上就被自己否掉了.数据少还可以,但是数据多了呢?显然不切实际,那么如何能一下子将所有的记录都直接取出来呢?

解决办法

在对应的repository上直接添加函数 findAllByYear:

1
List<InstrumentCheckedRate> findAllByYear(Short year);

这是一个Spring Data JPA提供给我们的查询策略,这里不做详细解释,参考https://docs.spring.io/spring-data/jpa/docs/2.0.5.RELEASE/reference/html/#jpa.query-methods.query-creation

(二)如何获取大量数据

在获取大量数据时,当数据量达到十万级百万级,显然我们再一下子或去除所有的信息就显得行不通了.这么大量的数据量必然会造成数据库卡顿甚至崩溃.

解决办法

分组获取.我们将对应的信息进行一个分组,将直接全部获取变为批量获取,这样就能减小数据库的压力.同样的,我们还是再repository上建立分组获取的方法.

1
Page<InstrumentCheckedRate> findAllByYear(Short year, Pageable pageRequest);

pageRequest 是用来limit的参数,它会控制批量获取数据.参考https://docs.spring.io/spring-data/commons/docs/current/api/

(三)如何进行单元测试?

既然我们设计了要获取大量的数据,所以我们就要测试一下在较多的数据下的情况.所以我们要创建多组数据.然后在执行Clone方法后如何判断方法执行成功?

解决办法

首先,插入大量数据这点还没有一个较好的解决办法,只是用循环逐条插入,虽然效率较低,但是,我们此单元测试的主要功能并不是测试插入,所以这部分暂时搁置.

然后说一下我们如何判断方法执行成功.执行玩这个方法后,由于是按已有对象的克隆,所以首先就是要断言上一年分的对象依然存在.然后断言新的对象是否成功持久化.最后再判断新的对象的属性是否都成功赋值.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void clonePreYearForCurrentYear() throws Exception {
...
logger.info("调用service层方法克隆当前年份的的实体对象");
instrumentCheckedRateService.clonePreYearForCurrentYear();
logger.info("从数据库中获取刚刚持久化的当前年份的对象");
List<InstrumentCheckedRate> newInstrumentCheckedRates = instrumentCheckedRateRepository.findAllByYear((short)currentYear);
logger.info("断言上一年份的记录依然存在");
assertThat(instrumentCheckedRateRepository.countByYear((short)preYear)).isEqualTo(10000L);
logger.info("断言新的记录已经 clone");
assertThat(instrumentCheckedRateRepository.countByYear((short)currentYear)).isEqualTo(10000L);
logger.info("断言当前年份的对象的属性与上一年份的属性相同");
for (int i = 0; i < newInstrumentCheckedRates.size(); i++) {
assertThat(newInstrumentCheckedRates.get(i).getYear()).isEqualTo((short) currentYear);
assertThat(newInstrumentCheckedRates.get(i).getCheckedTotalCount()).isEqualTo(0);
assertThat(newInstrumentCheckedRates.get(i).getInstrumentType()).isEqualTo(instrumentType);
assertThat(newInstrumentCheckedRates.get(i).getInstrumentUserDepartment()).isEqualTo(department);
...
}
}

代码重构

方法功能虽然实现了,但是再代码结构不清,比较混乱重复代码较多.所以这就急需重构代码.

公共函数提炼

克隆部分是最多但是都是重复的代码,而且以后很有可能还会使用这个方法,所以选择将其提炼成一个公共函数,克隆自身.

1
2
3
4
5
6
7
8
9
10
11
12
13
public InstrumentCheckedRate clone() {
InstrumentCheckedRate instrumentCheckedRate = new InstrumentCheckedRate();
// clone 所有属性
instrumentCheckedRate.setYear(this.getYear());
instrumentCheckedRate.setTotalCount(this.getTotalCount());
instrumentCheckedRate.setCheckedTotalCount(this.getCheckedTotalCount());
instrumentCheckedRate.setInstrumentType(this.getInstrumentType());
instrumentCheckedRate.setInstrumntUserDepartment(this.getInstrumentUserDepartment());
instrumentCheckedRate.setProvince(this.getProvince());
instrumentCheckedRate.setCity(this.getCity());
instrumentCheckedRate.setProvince(this.getProvince());
return instrumentCheckedRate;
}

函数逻辑重构

开始定义多个变量用来实现控制分组获取数据,但是变量一多,就显得结构不够清晰,而且也不利于代码的修改,不方便阅读.修改逻辑后,只选择使用一个变量,用来判断是否为最后一页,直到最后一页循环停止.最后,在保存的时候,不要每复制完一个对象都保存一次,应该是在复制完一组之后,将整组批量存入.这样会减少访问数据库的次数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
while(!isLastPage) {
// 用来限定每组1000条数据
PageRequest pageRequest = new PageRequest(page, this.onceDataNum);
// 获取一组对象并加进数组中
Page<InstrumentCheckedRate> preInstrumentCheckedRate = instrumentCheckedRateRepository.findAllByYear((short)preYear, pageRequest);
// 当前页为最后一页
if (preInstrumentCheckedRate.isLast()) {
isLastPage = true;
}
page++; // 组号自增,获取下一组
// 创建一个数组,存放当前年份
List<InstrumentCheckedRate> instrumentCheckedRateList = new ArrayList<>();
// clone
for (InstrumentCheckedRate instrumentCheckedRate : preInstrumentCheckedRate) {
// 新建一个对象
InstrumentCheckedRate newInstrumentCheckedRate = instrumentCheckedRate.clone();
// 设置年份为当前年份
newInstrumentCheckedRate.setYear((short) currentYear);
// 设置受检总数为0
newInstrumentCheckedRate.setCheckedTotalCount(0);
instrumentCheckedRateList.add(newInstrumentCheckedRate);
}
// 当前年份数组持久化
instrumentCheckedRateRepository.save(instrumentCheckedRateList);

总结

每一个小的功能实现都不是简单的思考就可以实现的.勤思,会为你后面的大功能做思维的铺垫;规范,会让你的代码结构清晰.

坚持原创技术分享,您的支持将鼓励我继续创作!