前言
在我们平时的web项目中,都会用到增删改查接口。如果不去进行封装,那么controller、service、dao这几层就要对应的去进行重复编写。再考虑到全局的请求日志管理、数据权限过滤、异常处理等等,还是需要在基于自己业务基础上,进行一些必要的封装工作。同时要注意,封装的同时要留足够的自由度去应对不同业务可能会出现的的特殊需求。
1、统一分页请求参数
一个业务系统最多的可能是查询功能。首先我们对于分页请求的请求体进行封装。统一分页请求参数,同时也利于前端的代码封装。例如可以基于vue的Mixins混入机制,封装公共列表页基础功能,增删改查等。在各个页面在引入时,只需要定义具体的请求URL即可实现增删改查功能。前端代码在此文档中不作表述。废话少说,上代码:
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 接收前台参数辅助类 统一分页查询
*
* @author dzx
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageDataVo<T> {
private Page<T> page;
private T data;
@Override
public String toString() {
return "PageDataVo{" +
"page=Page(" + "records=" + page.getRecords() +
"total=" + page.getTotal() +
"size=" + page.getSize() +
"current=" + page.getCurrent() +
"orders=" + page.getOrders() +
", data=" + data +
'}';
}
}
2、dao封装
虽然在我们集成了mybatis-plus之后,在我们自己的dao实现其提供的BaseMapper,不用写XML就可以获得CRUD功能,但是如果遇到非单表查询、查询条件需要范围查询、模糊查询时,不写XML同时使用BaseMapper提供的方法就满足不了。如下面的IBaseMapper代码,首先定义一个接口实现mybatis-plus的BaseMapper。使其具有BaseMapper的功能之外,我们将自己定义的一些方法加入其中。比如分页。在这里定义并不意味着我们的xml中必须要写对应的sql语句。可以在serviceImpl中看到具体代码。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* <p>
* 封装的公共IBaseMapper,将一些公共方法提出来 ,如分页、列表等
* <p/>
*
* @author dzx
*/
public interface IBaseMapper<T> extends BaseMapper<T> {
/**
* 自定义分页查询
*
* @param page 分页
* @param entity 查询参数
* @return List
*/
List<T> queryPageByCond(IPage page, @Param("params") T entity);
}
3、service封装
首先是IBaseService的定义。同样的继承mybatis-plus提供的IService的同时,加入基于我们业务的自定义方法。
我这里是做了一个查询之后进行数据字典项的转换,我们在前端展示时,很多字段对应的是数据字典项的值,而不是对应的label。比如,0男1女。存到数据库肯定是0,但是前端需要展示的是1。当然,这种处理放到前端去处理也不是不可以。要么就是写死,要么就再进行一次后台查询,获得字典项列表。在获取列表后,再循环遍历,展示其对应的label。第一种写死,会造成如果数据字典进行变更,那在前后端分离部署的情况下,前端代码就需要重新部署。第二种方法,可以采用。但是会造成更多的请求。我这里选择的是放到后台进行处理。同时,包括一些数据权限过滤,对于一些插入和更新操作,如果需要获取当前登录人信息一并插入到表中。例如create_time、create_user、update_time等等。我们都可以将其放到这个接口中。
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.nari.ztdz.lockset.mainview.base.vo.PageDataVo;
import java.util.List;
/**
* <p>
* 封装的公共IBaseService,将一些公共方法提出来 ,如分页、列表等
* 以后的fillInsertInfo fillUpdateInfo都可以放到这里
* <p/>
*
* @author dzx
*/
public interface IBaseService<T> extends IService<T> {
/**
* <p>
* 分页查询
* <p/>
*
* @param pageDataVo
* @return Page<T>
*/
Page<T> queryPageByCond(PageDataVo<T> pageDataVo);
/**
* <p>
* 分页查询之后将list进行数据转换的方法
* <p/>
*
* @param list
* @return List<T>
*/
List<T> beforeList(List<T> list);
}
serviceImpl代码如下,M为我们实现了IBaseMapper的Mapper。T为当前对应的实体类泛型。刚才说道,IBaseMapper中自定义了queryPageByCond方法,但是我们并不一定要在xml中进行sql编写,因为我们在serviceImpl中进行了一个try catch的处理。如果我们的mapper.xml中没有queryPageByCond,那么就会抛出BindingException异常,我们在catch中走mybatis-plus自带的查询。这样做的好处是,如果不需要模糊查询、范围查询这种查询时,我们就不需要进行sql的编写。减少代码量。
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nari.ztdz.lockset.mainview.base.mapper.IBaseMapper;
import com.nari.ztdz.lockset.mainview.base.vo.PageDataVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.BindingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 封装的公共IBaseService,将一些公共的、代码或功能具有重复性的方法提出来,减少重复代码。如分页、列表等。
* 以后的fillInsertInfo fillUpdateInfo 以及查询前的数据权限过滤 都可以放到这里
* <p/>
*
* @author dzx
*/
@Slf4j
@Transactional
public class IBaseServiceImpl<M extends IBaseMapper<T>, T> extends ServiceImpl<M, T> implements IBaseService<T> {
@Autowired
protected M mapper;
/**
* <p>
* 如果说在分页的时候,查询结果中有某些字段需要在代码层进行处理,例如取某个字段对应的数据字典的label等
* 可以在自己的serviceImpl中重写beforeList,取进行具体的业务逻辑操作
* <p/>
*
* @param pageDataVo
* @return
*/
@Override
public Page<T> queryPageByCond(PageDataVo<T> pageDataVo) {
Page<T> page = pageDataVo.getPage();
List<T> tList = new ArrayList<T>();
try {
tList = this.mapper.queryPageByCond(page, pageDataVo.getData());
page.setRecords(tList);
} catch (BindingException var3) {
//当发现mapper.xml中没有queryPageByCond时,那就会走mybatis-plus自带的查询
//这样做的目的是为了有的分页不需要模糊查询或者时间范围这种需要重写xml或者重新构造QueryWrapper时,使用此方法亦可正常返回
log.debug("mapper.xml没有相应list映射");
IPage<T> iPage = this.mapper.selectPage(page, new QueryWrapper<T>(pageDataVo.getData()));
page.setRecords(iPage.getRecords());
}
if (!CollectionUtil.isEmpty(page.getRecords())) {
List<T> list = this.beforeList(page.getRecords());
}
return page;
}
/**
* <p>
* 这个方法是为了查询结果中有某些字段需要在代码层进行处理,例如取某个字段对应的数据字典的label等
* <p/>
*
* @param list
* @return List<T>
*/
@Override
public List<T> beforeList(List<T> list) {
return list;
}
}
4、controller封装
controller的封装一定涉及到统一请求参数、统一返回消息体。统一请求参数在一开始我们就已经介绍。包括统一返回消息体,都是自有封装,针对自己业务需要即可。代码就不贴出来了。controller接口、接口实现类代码如下。
需要注意的是@Log、 @ApiOperation两个注解,@Log是我定义的基于spring aspectJ来做的全局log管理。@ApiOperation是swagger注解。如果不需要,直接去掉就好。
import com.nari.ztdz.lockset.mainview.base.vo.PageDataVo;
import com.nari.ztdz.web.WebResponse;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* @author dzx
*/
public interface BaseController<T> {
/**
* 根据ID列表删除对象,如果idList 为空或者空列表则直接返回
*
* @param ids 要删除对象的ID列表
* @return WebResponse
*/
public WebResponse deleteList(Collection<? extends Serializable> ids);
/**
* 删除一条记录
*
* @param id
* @return WebResponse
*/
public WebResponse deleteOne(String id);
/**
* 添加一条实体,实体不能为null
*
* @param entity 要添加的实体
* @return WebResponse
*/
public WebResponse addOne(T entity);
/**
* 批量添加
*
* @param list 要添加的实体集合
* @return WebResponse
*/
public WebResponse addBatch(List<T> list);
/**
* 查询对象列表
*
* @param pageDataVo 查询对象
* @return WebResponse
*/
public WebResponse selectListByPage(PageDataVo<T> pageDataVo);
/**
* 查询所有
*
* @param entity 查询对象
* @return WebResponse
*/
public WebResponse selectList(T entity);
/**
* 根据ID查询一个对象
*
* @param id 不能为null
* @return WebResponse
*/
public WebResponse viewOne(String id);
/**
* @param entity 要更新的实体
* @return WebResponse
*/
public WebResponse editOne(T entity);
}
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.util.StringUtils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.nari.ztdz.lockset.annotation.Log;
import com.nari.ztdz.lockset.mainview.base.service.IBaseService;
import com.nari.ztdz.lockset.mainview.base.vo.PageDataVo;
import com.nari.ztdz.web.WebResponse;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* <p>
* 封装的公共controller,如果需要重写。可以有两种方式:
* 一、在controllerl类中直接重写baseController的方法
* 二、重写当前继承了IBaseService的service代码
* <p/>
*
* @param <T> 当前业务对象的实体类泛型;
* @param <B> 当前业务对象的service;
* @author dzx
*/
@Slf4j
@RestController
public abstract class BaseControllerImpl<T, B extends IBaseService<T>> implements BaseController<T> {
/**
* 获取基础的服务
*/
@Autowired
protected B baseService;
@Log("批量删除")
@RequestMapping(value = "/deleteByIds", method = RequestMethod.POST)
@Override
@ApiOperation(value = "批量删除")
public WebResponse deleteList(Collection<? extends Serializable> ids) {
if (CollectionUtils.isEmpty(ids)) {
log.error("要删除的ID号为null或空字符串!对象:{}", this.getClass().getName());
return WebResponse.FAILED;
}
boolean flag = baseService.removeByIds(ids);
if (flag) {
return new WebResponse();
}
log.error("删除失败!对象:{}", this.getClass().getName());
return WebResponse.FAILED;
}
@Log("主键删除")
@Override
@PostMapping(value = "/deleteById")
@ApiOperation(value = "主键删除")
public WebResponse deleteOne(String id) {
if (StringUtils.isEmpty(id)) {
log.error("要删除的ID号为null或空字符串!对象:{}", this.getClass().getName());
return WebResponse.FAILED;
}
boolean flag = baseService.removeById(id);
if (flag) {
return new WebResponse();
}
log.error("删除失败!对象:{}", this.getClass().getName());
return WebResponse.FAILED;
}
@Log("新增")
@PostMapping(value = "/add")
@Override
@ApiOperation(value = "新增")
public WebResponse addOne(@RequestBody T entity) {
boolean flag = baseService.save(entity);
if (flag) {
return new WebResponse("新增成功!");
}
return WebResponse.FAILED;
}
@Log("批量新增")
@PostMapping(value = "/addBatch")
@Override
@ApiOperation(value = "批量新增")
public WebResponse addBatch(@RequestBody List<T> list) {
boolean flag = baseService.saveBatch(list);
if (flag) {
return new WebResponse("新增成功!");
}
return WebResponse.FAILED;
}
@Log("分页查询")
@PostMapping("/list")
@Override
@ApiOperation(value = "分页查询")
public WebResponse selectListByPage(@RequestBody PageDataVo<T> pageDataVo) {
Page<T> page = baseService.queryPageByCond(pageDataVo);
return WebResponse.data(page);
}
@Log("查询所有")
@PostMapping("/getAll")
@Override
@ApiOperation(value = "查询所有")
public WebResponse selectList(@RequestBody T entity) {
Class<? extends BaseControllerImpl> aClass = this.getClass();
List<T> list = baseService.list(new QueryWrapper<T>(entity));
return WebResponse.data(list);
}
@Log("详情")
@PostMapping("/info")
@Override
@ApiOperation(value = "详情")
public WebResponse viewOne(String id) {
return WebResponse.data(baseService.getById(id));
}
@Log("修改")
@PostMapping("/update")
@Override
@ApiOperation(value = "修改")
public WebResponse editOne(@RequestBody T entity) {
boolean flag = baseService.updateById(entity);
if (flag) {
return new WebResponse("更新成功!");
}
return WebResponse.FAILED;
}
}
5、具体使用demo
dao
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* <p>
*
* </p>
*
* @author czx
* @since 2020-11-12
*/
@Data
@EqualsAndHashCode(callSuper = true)
@KeySequence(value = "SEQ_SYS_DICT_ID", clazz = Integer.class)
@Accessors(chain = true)
@TableName("SYS_DICT")
@ApiModel(value = "SysDict对象")
public class SysDict extends Model<SysDict> {
private static final long serialVersionUID = 1L;
@TableField("ID")
private String id;
@TableField("SORT")
private Integer sort;
@TableField("LABEL")
private String label;
@TableField("VALUE")
private String value;
@TableField("TYPE_ID")
private String typeId;
}
import com.nari.ztdz.lockset.mainview.base.mapper.IBaseMapper;
import com.nari.ztdz.lockset.mainview.sys.po.SysDict;
/**
* <p>
* Mapper 接口
* </p>
*
* @author czx
* @since 2020-11-12
*/
public interface SysDictMapper extends IBaseMapper<SysDict> {
}
service
import com.nari.ztdz.lockset.mainview.base.service.IBaseService;
import com.nari.ztdz.lockset.mainview.sys.po.SysDict;
import java.util.List;
/**
* <p>
* 服务类
* </p>
*
* @author czx
* @since 2020-11-12
*/
public interface ISysDictService extends IBaseService<SysDict> {
}
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.nari.ztdz.lockset.mainview.base.service.IBaseServiceImpl;
import com.nari.ztdz.lockset.mainview.sys.mapper.SysDictMapper;
import com.nari.ztdz.lockset.mainview.sys.mapper.SysDictTypeMapper;
import com.nari.ztdz.lockset.mainview.sys.po.SysDict;
import com.nari.ztdz.lockset.mainview.sys.po.SysDictType;
import com.nari.ztdz.lockset.mainview.sys.service.ISysDictService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 服务实现类
* </p>
*
* @author czx
* @since 2020-11-12
*/
@Service
public class SysDictServiceImpl extends IBaseServiceImpl<SysDictMapper, SysDict> implements ISysDictService {
}
controller
import cn.hutool.core.util.StrUtil;
import com.nari.ztdz.lockset.mainview.base.controller.BaseControllerImpl;
import com.nari.ztdz.lockset.mainview.sys.po.SysDict;
import com.nari.ztdz.lockset.mainview.sys.service.ISysDictService;
import com.nari.ztdz.web.WebResponse;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* 前端控制器
* </p>
*
* @author czx
* @since 2020-11-12
*/
@Api(tags = "数组字典")
@RestController
@RequestMapping("/dict")
public class SysDictController extends BaseControllerImpl<SysDict, ISysDictService> {
}
总结
其实这个后台封装,如果配合代码生成,以及前端封装,几乎可以实现简单业务逻辑不用任何代码。可以高效提升开发效率。使我们有足够的精力去面向业务。目前因为时间问题,说白了就是懒。。也因为个人技术水平有限,希望大家指正文中纰漏或不合理的地方。