[toc]
话说进入一家公司,如果经常
CURD
,是不是感觉很枯燥,但是自己有没有想过,写了一个模块又一个模块,怎么能让自己的代码稍微提高些逼格呢?本文就是基于此背景,应运而生。
1.前言
话说曾经在一家公司,尤其是参与了后端给运营端比如某某平台,参与的大多数需求,常年日积月累就是一个个增加、删除、编辑、查询列表等等。写的多了,或多或少会有些感慨,哪部分代码是可以抽象出来,哪些代码可以不再CP/CV
了,就这样经过一代代升华,诞生了今天的主题-基于SpringMVC
的一个三层抽象模块封装-BaseModule
。
2.思考
日常最多的功能模块具备的特征如下:
- 功能模块都具备新增、编辑、查看详情、分页查询。
- 数据ORM对象大多是单表,也有甚少的关联查询操作。
- 返回给前端数据模型和业务处理逻辑抽象部分相同。
- 都是基于SpringMVC+MyBatis框架实现相关功能。
3.架构
从上图,我们可以看到:- 第一个区域即本文抽象的基础模块,称之为
抽象模块(Abstract Module)
。它提供了BaseMapper
、BaseService
、BaseFacade
的抽象封装,具备了日常的通用功能操作,无需再编码重复的代码。另外,以Impl结尾的BaseServiceImpl
、BaseFacadeImpl
实现了相关对象的依赖注入,使得你的代码只需要继承该抽象类即可。 - 第二个区域即你的功能模块需要使用抽象部分,比如继承相关接口或者抽象类,即可以实现扩展。
这里需要特别说明一下,为何要引入一个Facade层,其实我们知道为了对Service进行包装,比如我们把VO转换Entity,比如我们新增、删除需要返回统一的Result协议类型。
4.设计
BaseMapper
DAO层的抽象封装,自己功能模块的Mapper需要继承该接口。
package org.suze.framework.base;
import org.suze.framework.base.page.PageForm;
import java.io.Serializable;
import java.util.List;
/**
* @description: Mapper基类
* @Date : 下午3:36 2018/2/5
* @param <E> entity对象
* @param <PK> 主键,1、返回自增ID;2:查询对象主键;3:删除对象主键(联合主键)
* @param <F> 封装查询参数
* @Author : 石冬冬-Heil Hitler
*/
public interface BaseMapper<E extends Serializable,PK,F extends Serializable> {
/**
* 分页加载数据
* @param form
* @return
*/
List<E> queryByPage(PageForm<F> form);
/**
* 分页查询数量
* @param form
* @return
*/
int queryCount(PageForm<F> form);
/**
* 查询列表
* @param form
* @return
*/
List<E> queryList(F form);
/**
* 根据主键id查询对象
* @param uniqueKey
* @return E
*/
E selectByPrimaryKey(PK uniqueKey);
/**
* 根据主键删除
* @param uniqueKey
* @return
*/
int deleteByPrimaryKey(PK uniqueKey);
/**
* 有选择性的新增对象
* @param record
* @return 主键id
*/
int insertSelective(E record);
/**
* 新增对象
* @param record
* @return 主键id
*/
int insert(E record);
/**
* 有选择性的更新对象
* @param record
* @return
*/
int updateByPrimaryKeySelective(E record);
/**
* 更新
* @param record
* @return
*/
int updateByPrimaryKey(E record);
/**
* 批量新增
* @param list
* @return
*/
int batchInsert(List<E> list);
/**
* 批量新增,忽略已存在数据
* @param list
* @return
*/
int batchInsertIgnore(List<E> list);
/**
* 批量删除
* @param list
* @return
*/
int batchDelete(List<E> list);
/**
* 根据条件删除
* @param form
* @return
*/
int deleteByForm(F form);
}
复制代码
BaseService
Service层的抽象封装,相应Service需要继承该接口。
package org.suze.framework.base;
import org.suze.framework.base.page.PageForm;
import java.io.Serializable;
import java.util.List;
/**
* BaseService
* @Date : 2019/5/31 下午2:28
* @Author : 石冬冬-Seig Heil
* @param <E> 数据库实体对象
* @param <P> 查询主键
* @param <F> 查询条件Form对象
*/
public interface BaseService<E extends Serializable,P,F extends Serializable> {
/**
* 分页加载数据
* @param form
* @return
*/
List<E> queryByPage(PageForm<F> form);
/**
* 分页查询数量
* @param form
* @return
*/
int queryCount(PageForm<F> form);
/**
* 查询实体对象
* @param primaryKey
* @return
*/
E queryRecord(P primaryKey);
/**
* 新增实体对象
* @param record
* @return
*/
int insertRecord(E record);
/**
* 修改实体对象
* @param record
* @return
*/
int updateRecord(E record);
/**
* 修改实体对象
* @param record
* @return
*/
int updateByPrimaryKeySelective(E record);
/**
* 删除对象
* @param primaryKey
* @return
*/
int deleteRecord(P primaryKey);
/**
* 批量新增
* @param list
* @return
*/
int batchInsert(List<E> list);
/**
* 批量插入(没有即插入,有的话不做处理)
* @param list
* @return
*/
int batchInsertIgnore(List<E> list);
/**
* 批量删除
* @param list
* @return
*/
int batchDelete(List<E> list);
/**
* 根据条件删除
* @param form
* @return
*/
int deleteByForm(F form);
/**
* 查询列表
* @param form
* @return
*/
List<E> queryList(F form);
}
复制代码
BaseServiceImpl
Service实现类的抽象封装,相应Service实现类需要继承该接口。
package org.suze.framework.base.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.suze.framework.base.BaseMapper;
import org.suze.framework.base.BaseService;
import org.suze.framework.base.page.PageForm;
import java.io.Serializable;
import java.util.List;
/**
* @description: BaseService
* @Date : 2019/5/31 下午2:25
* @Author : 石冬冬-Seig Heil
* @param <E> 数据库实体对象
* @param <P> 查询主键
* @param <F> 查询条件Form对象
*/
public abstract class BaseServiceImpl<E extends Serializable,P,F extends Serializable> implements BaseService<E,P,F> {
/**
* mapper
*/
protected BaseMapper<E,P,F> mapper;
@Autowired
public void setBaseMapper(BaseMapper<E, P, F> baseMapper) {
this.mapper = baseMapper;
}
@Override
public List<E> queryByPage(PageForm<F> form) {
return mapper.queryByPage(form);
}
@Override
public int queryCount(PageForm<F> form) {
return mapper.queryCount(form);
}
@Override
public E queryRecord(P primaryKey) {
return mapper.selectByPrimaryKey(primaryKey);
}
@Override
public int insertRecord(E record) {
return mapper.insert(record);
}
@Override
public int updateRecord(E record) {
return mapper.updateByPrimaryKey(record);
}
@Override
public int updateByPrimaryKeySelective(E record) {
return mapper.updateByPrimaryKeySelective(record);
}
@Override
public int deleteRecord(P primaryKey) {
return mapper.deleteByPrimaryKey(primaryKey);
}
@Override
public int batchInsert(List<E> list) {
return mapper.batchInsert(list);
}
@Override
public int batchInsertIgnore(List<E> list) {
return mapper.batchInsertIgnore(list);
}
@Override
public int batchDelete(List<E> list) {
return mapper.batchDelete(list);
}
@Override
public int deleteByForm(F form) {
return mapper.deleteByForm(form);
}
@Override
public List<E> queryList(F form) {
return mapper.queryList(form);
}
}
复制代码
BaseFacade
Facade层的抽象封装,相应Facade需要继承该接口。
package org.suze.framework.base;
import org.suze.framework.base.page.PageForm;
import org.suze.framework.base.page.PageVO;
import java.io.Serializable;
/**
* @description: Facade 接口基类
* @Date : 2019/5/31 下午2:45
* @Author : 石冬冬-Seig Heil
* @param <V> 页面显示VO对象
* @param <E> 数据库实体对象
* @param <P> 查询主键
* @param <F> 查询条件Form对象
*/
public interface BaseFacade<V extends Serializable,E extends Serializable,P,F extends Serializable> {
/**
* 分页加载数据
* @param form
* @return
*/
Result<PageVO<V>> loadRecords(PageForm<F> form);
/**
* 查询实体对象
* @param primaryKey
* @return
*/
Result<V> queryRecord(P primaryKey);
/**
* 保存实体对象
* @param record
* @return
*/
Result<String> saveRecord(E record);
/**
* 删除对象
* @param primaryKey
* @return
*/
Result<String> deleteRecord(P primaryKey);
/**
* 更新状态
* @param record
* @return
*/
Result<String> modifyStatus(E record);
}
复制代码
BaseFacadeImpl
Facade实现类的抽象封装,相应Facade实现类需要继承该接口。
package org.suze.framework.base.impl;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.suze.framework.base.BaseConverter;
import org.suze.framework.base.BaseFacade;
import org.suze.framework.base.BaseService;
import org.suze.framework.base.Result;
import org.suze.framework.base.enums.RemoteEnum;
import org.suze.framework.base.page.PageForm;
import org.suze.framework.base.page.PageVO;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
/**
* @description: BaseFacadeImpl,相关功能模块应该继承此类
* @Date : 2019/5/31 下午10:04
* @Author : 石冬冬-Seig Heil
* @param <V> 页面显示VO对象
* @param <E> 数据库实体对象
* @param <P> 查询主键
* @param <F> 查询条件Form对象
*/
@Slf4j
public abstract class BaseFacadeImpl<V extends Serializable,E extends Serializable,P extends Serializable,F extends Serializable> implements BaseFacade<V,E,P,F> {
/**
* service
*/
protected BaseService<E,P,F> service;
/**
* 是否修改操作
* @param record
* @return
*/
protected abstract boolean isModify(E record);
/**
* VO转换器
* @return
*/
protected abstract BaseConverter<V,E> converter();
@Autowired
public void setService(BaseService<E, P, F> service) {
this.service = service;
}
@Override
public Result<PageVO<V>> loadRecords(PageForm<F> form) {
List<V> voList = Collections.emptyList();
int count = 0;
try {
List<E> queryList = service.queryByPage(form);
count = service.queryCount(form);
voList = converter().convertList(queryList);
} catch (Exception e) {
log.error("loadRecords exception,form={}", JSON.toJSON(form),e);
return Result.failInServer(PageVO.newInstance(count,voList));
}
return Result.suc(PageVO.newInstance(count,voList));
}
@Override
public Result queryRecord(P primaryKey) {
E entity = service.queryRecord(primaryKey);
if(null == entity){
return Result.fail(RemoteEnum.WARN_EMPTY_RECORD);
}
return Result.suc(converter().convert(entity));
}
@Override
public Result<String> saveRecord(E record) {
if(isModify(record)){
service.updateRecord(record);
}else{
service.insertRecord(record);
}
return Result.suc();
}
@Override
public Result<String> deleteRecord(P primaryKey) {
service.deleteRecord(primaryKey);
return Result.suc();
}
@Override
public Result<String> modifyStatus(E record) {
service.updateByPrimaryKeySelective(record);
return Result.suc();
}
}
复制代码
这里要特别说明一下,该Facade层的,我们可以看到saveRecord
把新增、编辑通过定义一个抽象isModify
方法,以区分该共操作是新增还是编辑。
BaseConverter
提供Entity对VO的转换,自己需要实现VO转换器并继承该抽象类。
package org.suze.framework.base;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 值对像转化抽像类,目的将源对像转化为值对像。
* @Date : 2019/6/1 上午8:27
* @Author : 石冬冬-Seig Heil
* @param <R> 目标值对像
* @param <O> 源对像
*/
public abstract class BaseConverter<R extends Serializable, O extends Serializable> {
/**
* 勾子方法,由了子类实现具体的转换规则。
* @param o 源对像
* @return <R> 目标值对像
*/
public abstract R convert(O o);
/**
* List转化方法
* @param es 源对像列表
* @return List<V>目标值对像列表
*/
public List<R> convertList(List<O> es) {
if(null == es || es.isEmpty()){
return Collections.emptyList();
}
List<R> voList = new ArrayList<>(es.size());
for(O origin : es) {
R vo = convert(origin);
voList.add(vo);
}
return voList;
}
}
复制代码
PageForm
对分页查询功能的页面查询参数封装。
package org.suze.framework.base.page;
import java.io.Serializable;
/**
* Description: 分页Form类<br/>
* @author 石冬冬
* @version V1.0 by 石冬冬-Heil Hitler on 2020/3/27 12:48
*/
public class PageForm<T> implements Serializable {
private static final long serialVersionUID = 1315360688901318671L;
/**
* 分页起止
*/
private int start = 0;
/**
* 分页大小(每页多数条)
*/
private int limit = 20;
/**
* 是否分页
*/
private boolean paging = true;
/**
* 分页包装的Form条件
*/
private T form;
public PageForm() {
}
public PageForm(T form) {
this.form = form;
}
public PageForm(int start, int limit, T form) {
this.start = start;
this.limit = limit;
this.form = form;
}
public PageForm(boolean paging) {
this.paging = paging;
}
public PageForm(boolean paging, T form) {
this.paging = paging;
this.form = form;
}
/**
* 静态实例方法,外部调用
* @param start 分页起止
* @param limit 分页大小(每页多数条)
* @param form 分页包装的Form条件
* @param <T>
* @return
*/
public static <T> PageForm newInstance(int start, int limit, T form){
return new PageForm(start,limit,form);
}
/**
* 静态实例方法,外部调用
* @param paging 是否分页
* @param form 分页包装的Form条件
* @param <T>
* @return
*/
public static <T> PageForm newInstance(boolean paging,T form){
return new PageForm(paging,form);
}
public int getStart() {
return start;
}
public void setStart(int start) {
this.start = start;
}
public int getLimit() {
return limit;
}
public void setLimit(int limit) {
this.limit = limit;
}
public T getForm() {
return form;
}
public void setForm(T form) {
this.form = form;
}
public boolean isPaging() {
return paging;
}
public void setPaging(boolean paging) {
this.paging = paging;
}
}
复制代码
PageVO
对分页查询功能的数据结果集封装。
package org.suze.framework.base.page;
import java.io.Serializable;
import java.util.List;
/**
* Description:分页查询VO类 <br/>
* @author 石冬冬
* @version V1.0 by 石冬冬-Heil Hitler on 2020/3/27 12:57
*/
public class PageVO<T> implements Serializable {
private static final long serialVersionUID = 1724063683524348852L;
/**
* 总条数
*/
private int recordsTotal;
/**
* 结果集
*/
private List<T> data;
public PageVO() {
}
public PageVO(int total, List<T> data){
this.data = data;
this.recordsTotal = total;
}
/**
* 静态方法,返回实例对象
* @param total 总条数
* @param data 数据记录集合
* @param <T>
* @return
*/
public static <T> PageVO newInstance(int total, List<T> data){
return new PageVO(total,data);
}
public PageVO(List<T> data){
this.data = data;
}
public int getRecordsTotal() {
return recordsTotal;
}
public void setRecordsTotal(int recordsTotal) {
this.recordsTotal = recordsTotal;
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
}
复制代码
Result
对Restful API接口的响应封装。
package org.suze.framework.base;
import lombok.Data;
import org.suze.framework.base.enums.RemoteEnum;
import java.io.Serializable;
/**
* @description: 用于DTO返回泛型使用,提供Dobbo服务一种数据协议
* @Date : 上午11:49 2020/3/29
* @Author : 石冬冬-Heil Hitler
*/
@Data
public class Result<E> implements Serializable {
private final static int SUCCESS_CODE = 0;
private final static int FAILURE_CODE = 1;
private final static String SUCCESS_MSG = "操作成功";
private final static String FAILURE_MSG = "操作失败";
private static final long serialVersionUID = -6237151417035547947L;
/**
* 是否执行成功
*/
private boolean success;
/**
* 包体
*/
private E data;
/**
* 执行操作code
*/
private int code;
/**
* 业务消息
*/
private String msg;
public Result() {
}
/**
* 构造函数
* @param success
* @param data
* @param code
* @param msg
*/
public Result(boolean success, E data, int code, String msg) {
this.success = success;
this.data = data;
this.code = code;
this.msg = msg;
}
/**
* 静态方法,返回执行成功
* @return
*/
public static Result suc(){
return suc(SUCCESS_MSG,SUCCESS_CODE,SUCCESS_MSG);
}
/**
* 静态方法,返回执行成功
* @param t
* @param <E>
* @return
*/
public static <E> Result<E> suc(E t){
return suc(t,SUCCESS_CODE,SUCCESS_MSG);
}
/**
* 静态方法,返回执行成功
* @param t
* @param code
* @param msg
* @param <E>
* @return
*/
public static <E> Result<E> suc(E t,int code,String msg){
return new Result<>(Boolean.TRUE,t,code,msg);
}
/**
* 静态方法,返回执行成功
* @param t 返回具体的包装对象
* @param remoteEnum 业务枚举对象
* @param msg 业务信息
* @param <E>
* @return
*/
public static <E> Result<E> suc(E t, RemoteEnum remoteEnum, String msg){
return new Result<>(Boolean.TRUE,t,remoteEnum.getIndex(),msg);
}
/**
* 静态方法,返回执行失败
* @return
*/
public static Result<Object> fail(){
return fail(FAILURE_MSG,FAILURE_CODE,FAILURE_MSG);
}
/**
* 静态方法,返回执行失败
* @param t
* @param <E>
* @return
*/
@Deprecated
public static <E> Result<E> fail(E t){
return fail(t,FAILURE_CODE,FAILURE_MSG);
}
@Deprecated
public static Result fail(String msg){
return fail(null, -1, msg);
}
/**
* 静态方法,返回执行失败
* @param code 业务代码
* @param msg 业务信息
* @param <E>
* @return
*/
public static <E> Result<E> fail(int code,String msg){
return fail(null, code, msg);
}
/**
* 静态方法,返回执行失败
* @param remoteEnum 业务枚举
* @param msg 业务信息
* @param <E>
* @return
*/
public static <E> Result<E> fail(RemoteEnum remoteEnum,String msg){
return fail(null, remoteEnum.getIndex(), msg);
}
/**
* 静态方法,返回执行失败
* @param remoteEnum
* @return
*/
public static <E> Result<E> fail(RemoteEnum remoteEnum){
return new Result<>(Boolean.FALSE,null,remoteEnum.getIndex(),remoteEnum.getName());
}
/**
* 静态方法,返回执行失败
* @param t
* @param code
* @param msg
* @param <E>
* @return
*/
public static <E> Result<E> fail(E t,int code,String msg){
return new Result<>(Boolean.FALSE,t,code,msg);
}
/**
* 静态方法,返回执行成功
* @param t 返回具体的包装对象
* @param remoteEnum 业务枚举对象
* @param msg 业务信息
* @param <E>
* @return
*/
public static <E> Result<E> fail(E t, RemoteEnum remoteEnum, String msg){
return fail(t,remoteEnum.getIndex(),msg);
}
/**
* 静态方法,返回执行失败:参数为空
* @param t
* @param <E>
* @return
*/
public static <E> Result<E> failWithEmptyParam(E t){
return fail(t, RemoteEnum.ERROR_WITH_EMPTY_PARAM.getIndex(),RemoteEnum.ERROR_WITH_EMPTY_PARAM.getName());
}
/**
* 静态方法,返回执行失败:服务端内部错误
* @param t
* @param <E>
* @return
*/
public static <E> Result<E> failInServer(E t){
return fail(t, RemoteEnum.ERROR_IN_SERVER.getIndex(),RemoteEnum.ERROR_IN_SERVER.getName());
}
/**
* 静态方法,返回执行失败:运行时错误
* @param t
* @param <E>
* @return
*/
public static <E> Result<E> failInRuntime(E t){
return fail(t, RemoteEnum.ERROR_IN_RUNTIME.getIndex(),RemoteEnum.ERROR_IN_RUNTIME.getName());
}
}
复制代码
5.总结
上述三层Mapper
、Service
、Facade
,即我们Abstract Module
。Facade
的意义使得我们日常的业务处理通过它来负责,无需侵入到Service
层。
下面的是我的公众号二维码图片,欢迎关注。