一个项目里通常会有很多服务,比如userService,userInfoService等多个服务。那么每个服务都会有增删改查方法。当我们在这些服务中需要统一去处理一些功能时,就需要自己封装一些通用CURD方法。
1、实现思路
主要有两种实现,第一种是JDK反射。
第二种是在通用类中继承CRUD方法,然后各实现类中进行子类重写。
本文是基于第二种思路,比如在通用类中封装了add方法,然后各子类中对有需要方法进行重写,实现个性化。如果不需要个性化,直接继承就可以使用add方法。
假设:你已经写好了mapper、entity了。
2、封装baseService的接口。
这个里面封装的需要对外暴露的方法。比如说我们在里面封装一个add方法。
我们只需要关注入参及返回,不用写具体实现过程。
boolean add(T entity)这样即可。表示返回成功或失败,接收一个实体类。
本文采用的是继承IService接口,也可以改为继为BaseMapper。
package com.luo.comm.services.mp;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 通用服务接口。
*
* mybatisPlus 已经有了高度的封装。为了防止方法名冲突,所以用了新的方法名以保证不重复。
* 添加:单条添加add, 批量添加addBatch(暂不支持)
* 查找:按条件查找全部findAll, 按条件查找一条findOne, 按id查找findById
* 删除:按id删除deleteById ,按条件删除delete
* 更新: 按条件更新modify,拒绝修改多条,条件必须要带id。modifyById,按id强制更新
* @author bill
* @date 2023-07-07
*/
public interface BaseService <T> extends IService<T> {
/**
* 新增
*
* @param entity 实体对象
* @return true:成功 ; false:失败
*/
boolean add(T entity);
/**
* 修改
*
* @param entity 实体对象
* @return ture:成功;false:失败
*/
boolean modify(T entity);
T selectById(Long id);
/**
* 删除-通过条件表达式
* 注意,删除大量数据会导致性能问题
*
* @param wrapper
* @return
*/
boolean delete(Wrapper<T> wrapper);
/**
* 删除-通过id删除
*
* @param id
* @return
*/
boolean remove(Long id);
}
3、封装一个实现类。
该实现类中,我们进行一个基础的方法定义。
以add方法为例,通常来说,我们会在插入前,插入后,都执行些功能的处理。
比如,在插入前,我们会把不允许插入的字段进行删除。那么我们就可以定义在add方法,分别执行beforeAdd,afterAdd方法。
同时,我们考虑到,比如会在beforeAdd中,我们一般会将创建者、创建时间、更新人、更新时间、删除人、删除时间、ID等这些不能被用户自行更新的字段进行删除,以免插入时有脏数据。
同时,不同的实体类,又会有不同的个性化需要。比如user表一般会校验user_name字段,不能重复,也不能缺失这个段。user_info表user_name不能缺失。nick_name字段是必填等。
那么,为了兼顾公共字段的处理以及个性化的字段处理,我们在add方法,就定义两个方法,分别是beforeAdd和beforeAddComm。其中,beforeAddComm就用来处理公共的字段,不允许子类继承。beforeAdd就用来给子类继承并按其个性化进行重写以满足各不同实体类的需要。
**为什么需要在这里进行删除一些公用字段的处理呢?因为我技术有限,在上一节中,其实已经提到可以用mybatisPlus的自动填充功能对更新人、更新时间等进行全局的填充。但是,但是,我没有找到好的方法实现删除。举例来说,当前端传修改时,传了一个创建者字段来,我们知道这个字段是我们在自动填充里就配好的,是不能让用户改的,所以我们需要把这个字段删除掉。但是mybatisPlus的自动填充功能我竟然没找到remove方法。试过了把该字段设为null不可行
经过上述分析,我们的add方法,大致就会包括如下方法了:
public boolean add(T entity) {
/**调用新增以前的处理方法beforeAdd,允许子类个性化重写*/
beforeAdd(entity);
/**调用新增以前的公共处理方法beforeAddComm,禁止子类重写*/
beforeAddComm(entity);
/**执行父类的save方法,即IService方法中的save方法*/
boolean save = super.save(entity);
/**调用新增以后的通用处理过程afterAddComm,禁止子类重写*/
afterAddComm(entity);
/**调用新增以后的个性化处理过程afterAdd,允许子类个性化重写*/
afterAdd(entity);
return save;
}
那么,我们为了实现子类是否重写,那么我们在定义这些具体的方法时,就要用修饰符来区分。
add这样的方法,是暴露给外部调用的,所以用public来修饰。
beforeAdd是供子类重写的,我们可以用 protected来修饰。
beforeAddComm不能让子类重写,我们就直接写private就可以了。
4、在beforeAddComm和afterAddComm中进行一些通用的功能处理。
/**修改前的公用方法 私有,子类不能继承和重写*/
private T beforeModifyComm(T entity) throws NoSuchFieldException, IllegalAccessException {
// 这里要可以进行一通用的方法处理。
return (T)MpUtils.beforeAddCommHandle(entity);
}
/** 修改以后的通用处理过程,不允许子类重写
* 可以在这儿进行一些转换、脱敏等的通用处理
* 暂时留空,返回原对象
* */
private T afterModifyComm(T entity){
return entity;
}
比如,我们在 beforeAddComm删除一些像id\创建人、创建时间等不能自行添加的字段处理的
我们就可以把写成工具类来处理。
package com.luo.comm.utils.mp;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import java.util.Arrays;
import java.util.List;
public class MpUtils {
/**
* 保存前的预处理函数,像删除id\isDelete字段等
* @param obj 入参是一个通用实体类,转换成jsonobject进行处理*/
public static Object beforeAddCommHandle(Object obj) {
if(obj ==null ){
return obj;
}
/** 将baseEntity的属性补充到当前类,供后面使用 */
Class<?> cls = obj.getClass();
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(obj));
/** 处理需要删除的字段。这些是不能指定添加的 */
// removeList列表中字段都会被删除处理。
List<String> removeList = Arrays.asList("id","createdAt","updatedAt","createdBy","updatedBy","deletedAt","deletedBy");
for (String item: removeList ) {
if(jsonObject.containsKey(item)){
jsonObject.remove(item);
}
}
return jsonObject.toJavaObject(cls);
}
/**
* 保存前的预处理函数,像删除id\isDelete字段等
* @param obj 入参是一个通用实体类,转换成jsonobject进行处理*/
public static Object updateBeforeCommHandle(Object obj) {
if(obj ==null ){
return obj;
}
System.out.println("obj:"+ obj);
/** 将baseEntity的属性补充到当前类,供后面使用 */
Class<?> cls = obj.getClass();
JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(obj));
/** 处理需要删除的字段。这些是不能指定的 */
// removeList列表中字段都会被删除处理。
List<String> removeList = Arrays.asList("createdBy","createdAt","deletedAt","deletedBy");
for (String item: removeList ) {
System.out.println(item);
if(jsonObject.containsKey(item)){
jsonObject.remove(item);
}
}
System.out.println(jsonObject);
return jsonObject.toJavaObject(cls);
}
}
5、完整的baseServiceImpl类示例。
考虑到一些传参调用的便利性,我在add方法用了Beanutils的copy属性功能。以下是封装了add\modify示例的完整代码:
package com.luo.comm.services.mp;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luo.comm.utils.mp.MpUtils;
import org.springframework.beans.BeanUtils;
import static com.luo.comm.utils.MyBeanUtils.getNullPropertyNames;
/**
* 通用实现类,定义了通用的实现过程
* public方法是最终对外暴露的方法,重写了BaseService中的通用方法。不建议子类重写!!如确有需要也可重写,重写后需自行处理相关逻辑。
* add方法,内部调用了beforeAdd、beforeAddComm、afterAddComm、afterAdd。Comm结尾的是公共的,不能被子类重写。
* 同理,增删改查方法内部都会调用一些处理过程。Comm结尾的是公共的方法,不能被子类重写。
*
* protected 方法 能被子类继承和重写。
* private 方法 仅能在本类中调用,不能被子类继承,也不能被子类重写。
*
* @author bill
* @date 2023-07-07
* */
public abstract class BaseServiceImpl<M extends BaseMapper<T>,T> extends ServiceImpl<M,T> implements BaseService<T> {
/**
* 重写 新增 方法 final 禁止子类重写
* 增加保存前后的过程处理
* @param entity 实体对象
* @return true:成功 ; false:失败
*/
@Override
public boolean add(T entity) {
T newEntity =null;
/**调用新增以前的处理方法beforeAdd,允许子类个性化重写*/
newEntity = beforeAdd(entity);
/**调用新增以前的公共处理方法beforeAddComm,禁止子类重写*/
newEntity = beforeAddComm(newEntity);
/**执行父类的save方法,即IService方法中的save方法*/
boolean save = super.save(newEntity);
/**调用新增以后的通用处理过程afterAddComm,禁止子类重写*/
newEntity = afterAddComm(newEntity);
/**调用新增以后的个性化处理过程afterAdd,允许子类个性化重写*/
newEntity = afterAdd(newEntity);
// 将newEntity的属性返回给原entity属性,这样调用者就可以通过原entity获取新值,而不用再接收。
BeanUtils.copyProperties(newEntity, entity, getNullPropertyNames(newEntity));
return save;
}
/**protected 使得子类可以继承并重写*/
protected T beforeAdd(T entity){
return entity;
}
/**private 私有,子类不能继承和重写*/
private T beforeAddComm(T entity) {
// 通用方法处理。
return (T)MpUtils.beforeAddCommHandle(entity);
}
/**afterAdd 新增后的私有方法,允许子类继承和重写*/
protected T afterAdd(T entity){
return entity;
}
/** afterAddComm新增以后的通用处理过程,不允许子类重写
* 可以在这儿进行一些转换、脱敏等的通用处理
* 暂时留空,返回原对象
* */
private T afterAddComm(T entity){
return entity;
}
@Override
public boolean modify(T entity) {
T newEntity =null;
/**调用新增以前的处理方法beforeAdd,允许子类个性化重写*/
newEntity = beforeModify(entity);
/**调用新增以前的公共处理方法beforeAddComm,禁止子类重写*/
newEntity = beforeModifyComm(newEntity);
/**执行父类的save方法,即IService方法中的save方法*/
boolean modify = super.updateById(newEntity);
/**调用新增以后的通用处理过程afterAddComm,禁止子类重写*/
newEntity = afterModifyComm(newEntity);
/**调用新增以后的个性化处理过程afterAdd,允许子类个性化重写*/
newEntity = afterModify(newEntity);
// 将newEntity的属性返回给原entity属性,这样调用者就可以通过原entity获取新值,而不用再接收。
BeanUtils.copyProperties(newEntity, entity, getNullPropertyNames(newEntity));
return modify;
}
/** 修改前的方法,允许子类进行个性化重写*/
protected T beforeModify(T entity){
return entity;
}
/**修改前的公用方法 私有,子类不能继承和重写*/
private T beforeModifyComm(T entity) {
// 这里要可以进行一通用的方法处理。
return (T)MpUtils.updateBeforeCommHandle(entity);
}
/**afterAdd 修改后的私有方法,允许子类继承和重写*/
protected T afterModify(T entity){
return entity;
}
/** 修改以后的通用处理过程,不允许子类重写
* 可以在这儿进行一些转换、脱敏等的通用处理
* 暂时留空,返回原对象
* */
private T afterModifyComm(T entity){
return entity;
}
@Override
public T selectById(Long id) {
return super.getById(id);
}
@Override
public boolean delete(Wrapper<T> wrapper) {
return false;
}
@Override
public boolean remove(Long id) {
return false;
}
}
6、各实体类继承时,只需覆写需要重写的方法。其他方法不用管。
比如,我们user表,因为要校验userName不能为空,且不能重复,所以我们要重写beforeAdd方法。其他的方法我们就不用管了。示例如下:
package com.luo.comm.services.mp;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.luo.comm.entity.User;
import com.luo.comm.mapper.UserMapper;
import com.luo.comm.vo.BusinessException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
@Component
public class UserServiceImpl extends BaseServiceImpl<UserMapper,User> implements BaseService<User> {
@Override
protected User beforeAdd(User entity){
// userName不能为空 校验
if(StringUtils.isEmpty(entity.getUserName())){
throw BusinessException.error("userName不能为空");
}
// userName 重复校验
QueryWrapper<User> wrapper = new QueryWrapper();
wrapper.eq("user_name",entity.getUserName());
if(super.count(wrapper)>0){
throw BusinessException.error("userName已存在");
};
return entity;
}
@Override
protected User beforeModify(User entity){
if(StringUtils.isNoneEmpty(entity.getUserName())){
throw BusinessException.error("beforeModify userName不能修改");
}
return entity;
}
}
7、controller调用示例。
我们在BaseServicer接口有继承IService方法,所以我们在子类中,可以直接用ISerice的原生方法,也可以用我们封装的add\modify这些方法来使用。
只要不同名,并不会覆盖原生方法。比如userService服务,除了add,还有save\insert等都可以使用。
package com.luo.comm.controller.mp;
import com.luo.comm.entity.User;
import com.luo.comm.services.mp.UserServiceImpl;
import com.luo.comm.vo.ResultsObj;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@RequestMapping("/mp")
public class MPController {
@Resource
UserServiceImpl userService;
/**
* MybatisPlus 通用add方法调用示例,传递一个实体类。
*/
@PostMapping("/add")
public ResultsObj save(@RequestBody User user) {
userService.add(user);
System.out.println(user);
ResultsObj resultsObj = new ResultsObj(user);
return resultsObj;
}
/**
* MybatisPlus 通用modify方法调用示例,传递一个实体类。
*/
@PostMapping("/modify")
public ResultsObj modify(@RequestBody User user) {
userService.modify(user);
System.out.println(user);
ResultsObj resultsObj = new ResultsObj(user);
return resultsObj;
}
}
8、测试效果。
至于,controller中如何拿到最新user对象,这个就需要去改modify方法的返回值就行了。