本文为笔者实习期间个人学习记录
絮絮叨叨
要说实习这段时间印象最深的,不是认识了很多之前没接触过的技术,而是没完没了地修改controller和service层的代码…
现在项目结束了再回望,大致可分为三个阶段:
返工颇多,分析后主要是因为之前没有开发经验,前期设计考虑不足,并且减少代码重复率的意识不强,一味地复制黏贴导致较多冗余.
原本抱有这样的想法:都是相似的代码,修改起来很快。【x】 然而 并 不 是.
6个模块,6个controller+6个service的抽象类和6个service的实现类+6个以上DTO+至少3*6个VO,有好几十个类都有可能进行重复的、类似的修改,一是修改起来很麻烦工作量大,二是非常枯燥且没有什么意义.
因此下面复盘总结一下自己重构整理代码的过程,以后少走弯路,减少返工.
仅以CategoryController.java与CategoryServiceImpl.java为例
第一遍写的代码非常粗糙,仅仅是实现了基本功能使得前后端能够调通,逻辑乱,存在较多Bug.
/**
* @author : huangyuhui
* @version : 1.0
* @date : 2019/9/3
*/
@RestController
public class CategoryController {
@Autowired
private CategoryService categoryService;
@GlobalExceptionLog
@CrossOrigin
@PostMapping("api/deleteCategories")
@ApiLog
public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
List<CategoryDataItemVO> volist= commonRequest.getBody();
if(volist!=null && volist.size()>0){
//vo列表转dto列表
List<CategoryDTO> dtos=new ArrayList<CategoryDTO>();
CategoryDTO categoryDTO=null;
for(CategoryDataItemVO vo: volist){
categoryDTO=new CategoryDTO();
BeanUtils.copyProperties(vo,categoryDTO);
dtos.add(categoryDTO);
}
//调用service里面的对应方法进行删除
int result=0;
try {
result = categoryService.delete(dtos);
}catch (ServiceException serviceException){
//抛出自定义异常
throw new BusinessException(serviceException);
}
//返回CommonResponse给前端
CommonResponse<String> response=new CommonResponse<>();
ResponseHead head = new ResponseHead();
head.setEncryption(0);
head.setCode("0");
//
if(result>0){
head.setMessage("删除类别成功");
}else{
head.setMessage("删除类别失败");
}
response.setResponseHead(head);
return response;
}
return null;
}
@GlobalExceptionLog
@CrossOrigin
@PostMapping("api/addCategory")
@ApiLog
public CommonResponse<String> add(@RequestBody CommonRequest<CategoryDataItemVO> commonRequest ){
CategoryDataItemVO vo=commonRequest.getBody();
if(vo!=null) {
CategoryDTO categoryDTO = new CategoryDTO();
BeanUtils.copyProperties(vo, categoryDTO);
int result = 0;
try {
result = categoryService.add(categoryDTO);
} catch (ServiceException serviceException) {
throw new BusinessException(serviceException);
}
//返回CommonResponse给前端
CommonResponse<String> response = new CommonResponse<>();
ResponseHead head = new ResponseHead();
head.setEncryption(0);
head.setCode("0");
//
if (result > 0) {
head.setMessage("增加类别成功");
} else {
head.setMessage("增加类别失败");
}
response.setResponseHead(head);
return response;
}
return null;
}
@GlobalExceptionLog
@CrossOrigin
@PostMapping("api/updateCategory")
@ApiLog
public CommonResponse<String> update(@RequestBody CommonRequest<CategoryDataItemVO> commonRequest){
CategoryDataItemVO vo=commonRequest.getBody();
if(vo!=null) {
CategoryDTO categoryDTO = new CategoryDTO();
BeanUtils.copyProperties(vo, categoryDTO);
int result = 0;
try {
result = categoryService.update(categoryDTO);
} catch (ServiceException serviceException) {
throw new BusinessException(serviceException);
}
//返回CommonResponse给前端
CommonResponse<String> response = new CommonResponse<>();
ResponseHead head = new ResponseHead();
head.setEncryption(0);
head.setCode("0");
//
if (result > 0) {
head.setMessage("修改类别成功");
} else {
head.setMessage("修改类别失败");
}
response.setResponseHead(head);
return response;
}
return null;
}
@CrossOrigin
@GetMapping("api/loadCategories")
@ApiLog
public List<CategoryDTO> list() throws Exception {
CategoryDTO dto = new CategoryDTO();
return categoryService.queryByCondition(dto);
}
@GlobalExceptionLog
@CrossOrigin
@PostMapping("api/queryCategory")
@ApiLog
public List<CategoryDTO> query(@RequestBody CommonRequest<CategoryQueryConditionVO> commonRequest) {
CategoryQueryConditionVO vo=commonRequest.getBody();
if(vo!=null){
CategoryDTO dto=new CategoryDTO();
BeanUtils.copyProperties(vo,dto);
System.out.println(dto);
List<CategoryDTO> list=categoryService.queryByCondition(dto);
return list;
}
return null;
}
}
/**
* @author : huangyuhui
* @version : 1.0
* @date : 2019/9/2 0002
*/
@Service
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryDao categoryDao;
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
/**
* 增加题目类别
* @param categoryDTO
* @return
*/
public int add(CategoryDTO categoryDTO) {
if(categoryDTO!=null){
SnowFlake snowFlake=new SnowFlake(2,3);
Date createdTime= DateUtils.getDate();
//设置id(雪花算法获得)
categoryDTO.setId(snowFlake.nextId());
//更新时间和创建时间一样
categoryDTO.setCreatedTime(createdTime);
categoryDTO.setUpdatedTime(createdTime);
//创建者和更新者后续从登录信息中获取
categoryDTO.setCreatedBy((long)9527);
categoryDTO.setUpdatedBy((long)9527);
//设置版本
categoryDTO.setVersion((long)1.0);
try{
categoryDao.insert(categoryDTO);
return 1;
}catch (Exception e){
e.printStackTrace();
}
}
return 0;
}
/**
* 删除题目类别
* @param categoryDtos
* @return
*/
public int delete(List<CategoryDTO> categoryDtos) {
CategoryDTO categoryDTO=new CategoryDTO();
if(categoryDtos!=null){
for(CategoryDTO categoryDto : categoryDtos){
categoryDTO=categoryDto;
categoryDao.delete(categoryDTO);
}
}
return 0;
}
/**
* 修改题目类别信息
* @param categoryDTO
* @return
*/
public int update(CategoryDTO categoryDTO) {
//省略版本号对比
Category category =new Category();
BeanUtils.copyProperties(categoryDTO,category);
category.setUpdatedTime(DateUtils.getDate());
return categoryDao.updateByPrimaryKeySelective(category);
}
/**
* 通过id查找题目类别
* @param id
* @return
*/
public CategoryDTO getByPrimaryKey(Long id) {
Category category=categoryDao.selectByPrimaryKey(id);
CategoryDTO dto= new CategoryDTO();
BeanUtils.copyProperties(category,dto);
return dto;
}
/**
* 根据条件查找
* @param categoryDTO
* @return
*/
public List<CategoryDTO> queryByCondition(CategoryDTO categoryDTO) {
Condition condition = new Condition(Category.class);
Example.Criteria criteria=condition.createCriteria();
if(!StringUtils.isEmpty(categoryDTO.getName())){
criteria.andLike("name","%"+categoryDTO.getName()+"%");
}
List<Category> categories=categoryDao.selectByExample(condition);
List<CategoryDTO> dtos=null;
CategoryDTO dto=null;
if(categories!=null){
dtos=new ArrayList<CategoryDTO>(categories.size());
for(Category category:categories){
dto=new CategoryDTO();
BeanUtils.copyProperties(category,dto);
dtos.add(dto);
}
}else{
dtos=new ArrayList<CategoryDTO>();
}
return dtos;
}
/**
* 查找所有题目类别
* @return List 不会为null
*/
public List<CategoryDTO> queryAll() {
List<CategoryDTO> dtos=new ArrayList<CategoryDTO>();
List<Category> categories=categoryDao.selectAll();
CategoryDTO dto=null;
if(categories!=null){
for(Category category:categories){
dto=new CategoryDTO();
BeanUtils.copyProperties(category,dto);
dtos.add(dto);
}
}
return dtos;
}
}
入参:CommonRequest
返参:CommonResponse
@GlobalExceptionLog
@CrossOrigin
@PostMapping("/delete")
@ApiLog
public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
//返回CommonResponse给前端
CommonResponse<String> response=new CommonResponse<>();
ResponseHead head = new ResponseHead();
List<CategoryDataItemVO> volist= commonRequest.getBody();
int result=0;
if(volist!=null && volist.size()>0){
//vo列表转dto列表
List<CategoryDTO> dtos=new ArrayList<CategoryDTO>(volist.size());
CategoryDTO categoryDTO=null;
for(CategoryDataItemVO vo: volist){
categoryDTO=new CategoryDTO();
categoryDTO.setId(vo.getId());
categoryDTO.setVersion(vo.getVersion());
dtos.add(categoryDTO);
}
//调用service里面的对应方法进行删除
result= categoryService.delete(dtos);
if(result>0){
head.setCode("0");
head.setMessage("删除题目类别成功");
}else {
head.setMessage("删除题目类别失败");
}
}else {
head.setMessage("请求参数为空");
}
head.setEncryption(0);
response.setResponseHead(head);
response.setBody(""+result);
return response;
}
/**
* 删除题目类别
* @param categoryDtos
* @return
*/
@Override
public int delete(List<CategoryDTO> categoryDtos) {
int result=0;
if(categoryDtos!=null){
for(CategoryDTO categoryDTO : categoryDtos){
Category category=categoryDao.selectByPrimaryKey(categoryDTO.getId());
//判断数据是否存在
if(StringUtils.isEmpty(category)){
//如果为空则抛出不存在异常
throw new ServiceException(BesDataExceptionEnum.CATEGORY_NON_EXISTENT);
}
//判断数据版本是否一致
if(!category.getVersion().equals((categoryDTO.getVersion()))){
throw new ServiceException(BesDataExceptionEnum.CATEGORY_VERSION_DISACCORD);
}
if(category.getStatus()==1){
//数据正在被使用,不允许删除
throw new ServiceException(BesDataExceptionEnum.CATEGORY_IN_USE);
}
try{
int res=categoryDao.deleteByPrimaryKey(categoryDTO.getId());
result+=res;
}catch (Exception e){
throw new ServiceException(BesDataExceptionEnum.CATEGORY_IN_USE);
}
}
}
return result;
}
@GlobalExceptionLog
@CrossOrigin
@PostMapping("/query")
@ApiLog
public CommonResponse<PageInfo<CategoryDataListVO>> query(@Valid @RequestBody CommonRequest<CategoryQueryConditionVO> commonRequest) {
CategoryQueryConditionVO vo=commonRequest.getBody();
CommonResponse<PageInfo<CategoryDataListVO>> response= new CommonResponse<>();
ResponseHead head=new ResponseHead();
CategoryDTO dto=new CategoryDTO();
if(vo!=null){
BeanUtils.copyProperties(vo,dto);
}
PageInfo<CategoryDTO> categoryDTOPageInfo=categoryService.queryByConditions(dto);
PageHelper.clearPage();
//将List装换为List
List<CategoryDataListVO> voList=new ArrayList<>();
CategoryDataListVO categoryDataListVO=null;
for(CategoryDTO categoryDTO:categoryDTOPageInfo.getList()){
categoryDataListVO=new CategoryDataListVO();
BeanUtils.copyProperties(categoryDTO,categoryDataListVO);
voList.add(categoryDataListVO);
}
//PageInfo转化为PageInfo
PageInfo<CategoryDataListVO> categoryDataListVOPageInfo=new PageInfo<>();
BeanUtils.copyProperties(categoryDTOPageInfo,categoryDataListVOPageInfo);
categoryDataListVOPageInfo.setList(voList);
//设置返回数据格式
head.setCode("0");
head.setMessage("查询成功");
response.setBody(categoryDataListVOPageInfo);
response.setResponseHead(head);
return response;
}
/**
* 根据条件查找
* @param categoryDTO
* @return
*/
@Override
public PageInfo<CategoryDTO> queryByConditions(CategoryDTO categoryDTO) {
//设置查询条件
Condition condition = new Condition(Category.class);
Example.Criteria criteria=condition.createCriteria();
if(!StringUtils.isEmpty(categoryDTO.getName())){
criteria.andLike("name","%"+categoryDTO.getName()+"%");
}
if(categoryDTO.getParentId()!=null){
criteria.andEqualTo("parentId",categoryDTO.getParentId());
}
if(categoryDTO.getId()!=null){
criteria.andEqualTo("id",categoryDTO.getId());
}
List<Category> categories=null;
PageHelper.startPage(categoryDTO.getIndex(),categoryDTO.getPageSize());
try {
categories= categoryDao.selectByExample(condition);
}catch (Exception e){
throw new ServiceException(BesDataExceptionEnum.CATEGORY_QUERY_ERROR);
}
PageInfo<Category> categoryPageInfo=new PageInfo<>(categories);
List<CategoryDTO> dtos=new ArrayList<>();
CategoryDTO dto=null;
for(Category category : categoryPageInfo.getList()){
dto=new CategoryDTO();
BeanUtils.copyProperties(category,dto);
dtos.add(dto);
}
//将entity分页信息转化为DTO分页
PageInfo<CategoryDTO> categoryDTOPageInfo=new PageInfo<>();
BeanUtils.copyProperties(categoryPageInfo,categoryDTOPageInfo);
//修改list对象
categoryDTOPageInfo.setList(dtos);
return categoryDTOPageInfo;
}
由于存在大量的List
上一版本:
//vo列表转dto列表
List<CategoryDTO> dtos=new ArrayList<CategoryDTO>();
CategoryDTO categoryDTO=null;
for(CategoryDataItemVO vo: volist){
categoryDTO=new CategoryDTO();
BeanUtils.copyProperties(vo,categoryDTO);
dtos.add(categoryDTO);
}
修改后:
List<CategoryDTO> dtos=TransformClass.convertList(volist,CategoryDTO.class);
原本五六行需要重复写的代码,使用了工具类以后只需一行就搞定了,而且逻辑清晰一目了然.
修改后的delete方法:
@GlobalExceptionLog
@CrossOrigin
@PostMapping("/delete")
@ApiLog
public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
//返回CommonResponse给前端
CommonResponse<String> response=new CommonResponse<>();
ResponseHead head = new ResponseHead();
List<CategoryDataItemVO> volist= commonRequest.getBody();
int result=0;
if(volist!=null && volist.size()>0){
//vo列表转dto列表
List<CategoryDTO> dtos=TransformClass.convertList(volist,CategoryDTO.class);
//调用service里面的对应方法进行删除
result= categoryService.delete(dtos);
if(result>0){
head.setCode("0");
head.setMessage("题目类别删除成功");
}else {
head.setMessage("题目类别删除失败");
}
}else {
head.setMessage("请求参数为空");
}
head.setEncryption(0);
response.setResponseHead(head);
response.setBody(""+result);
return response;
}
TransformClass内部还需要注意什么呢?
在第2步的时候,已经将入参和返参规范起来了,统一为CommonRequest和CommonResponse,但是这样做仍然存在较多重复操作,而且对于从入参取得的body部分判空等操作逻辑也不够清晰,所以编写了统一处理的工具类CommonRequestUtils和CommonResponseUtils.
下面来看修改后的controller代码:
/**
* 删除题目类别
* @param commonRequest 前端请求报文[body:List]
* @return 响应报文[body:删除成功的记录数result]
*/
@GlobalExceptionLog
//@CrossOrigin
@PostMapping("/delete")
@ApiLog
public CommonResponse<String> delete(@RequestBody CommonRequest<List<CategoryDataItemVO>> commonRequest){
CommonRequestUtils.checkCommonRequest(commonRequest);
try {
List<CategoryDTO> dtos=TransformClass.convertList(commonRequest.getBody(),CategoryDTO.class);
//调用service里面的对应方法进行删除
int result= categoryService.delete(dtos);
return CommonResponseUtils.success(String.valueOf(result));
} catch (ServiceException exception) {
throw new BusinessException(exception);
}
}
简洁明了了许多,大大提高了代码的整洁度和可读性
实体存在公用字段,如创建时间、修改时间、创建人、修改人等,可采用自定义注解进行注入,原本设置相关数据的代码可以删去.
ps:其实这里创建人和修改人的设置原本就未曾实现,就算不使用注解也需要修改成从redis中取得用户的id再设值,那么改成注解之后可以一并处理了.
/**
* 增加题目类别
* @param categoryDTO
* @return
*/
@Override
@SetCommonField(methodType= CommonFieldAspect.TYPE_INSERT)
@Transactional(rollbackFor = {RuntimeException.class, Exception.class, ServiceException.class})
public int add(CategoryDTO categoryDTO) {
int result=0;
if(categoryDTO!=null){
if(categoryDao.selectByPrimaryKey(categoryDTO.getId())!=null){
throw new ServiceException(BesDataExceptionEnum.CATEGORY_REPEAT);
}
//将DTO对象转换为ENTITY对象
Category category=new Category();
BeanUtils.copyProperties(categoryDTO,category);
//设置id(雪花算法获得)
SnowFlake snowFlake=new SnowFlake(2,3);
category.setId(snowFlake.nextId());
//设置版本
category.setVersion(System.currentTimeMillis());
try {
result = categoryDao.insert(category);
}catch (Exception e){
throw new ServiceException(BesDataExceptionEnum.CATEGORY_INSERT_ERROR);
}
}
return result;
}
可以看到,controller层代码经过了一个逐渐丰富完善到重构降低重复率的过程,最终一个方法只需十几二十行即可,且逻辑清晰明了,可读性高. 我认为这整个过程也许无法避免,但是可以尽量预见到.
如,一旦发现有经常重复的代码,可以抽象成工具类对其进行简化;巧用自定义注解进行异常捕获或常用/公用数据设值;一些必要的操作,如分页,能提前写好就不要留到后面修改,否则前端接因为收到的数据不同也要进行相应更改;养成随时写注释的习惯,不要到最后再补.
service层代码一般是越来越多的,因为逻辑都在这层,但也要注意过长的方法要尽量拆分成多个短方法.