芝法酱躺平攻略(3)—— 搭建基于mybatis-plus的开发框架

发布于:2023-01-24 ⋅ 阅读:(567) ⋅ 点赞:(0)

一、framework-mysql模块编写

上节我们展示了SpringBoot下常见的4种ORM,对于一般性的公司业务,小编认为mybatis-plus更为适合,下面,就让我们一起来搭建基于mybatis-plus的开发框架吧。

1.1 mybatis-plus的entity的基类

我们的Entity基类有2个,分别为SysBaseEntity和BaseEntity
SysBaseEntity一般用于系统生成的数据和子表。比如版本记录、用户操作记录等,就属于系统生成数据。比如某项目的计划有若干子项,项目计划建一张表,子项建一张表。修改了子项就意味着修改了项目计划,删除项目计划那么子项也会无法查找到。
BaseEntity用于由系统内用户创建的,需要记录创建修改者的用户id,用户昵称,用户ip等信息。假删除的字段也放在这里。

@Data
public class SysBaseEntity {
    @Schema(title = "主键")
    @TableId(type = IdType.ASSIGN_ID)
    protected Long id;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.INSERT)
    @Schema(title = "创建时间")
    protected LocalDateTime createTime;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.UPDATE)
    @Schema(title = "修改时间")
    protected LocalDateTime modifyTime;
    @Schema(title = "版本")
    @Version
    protected Integer version;

    public void createInit() {
        id = IdWorker.getId();
        // 靠全局配置填充
        createTime = null;
        modifyTime = null;
        version = 0;
    }

    public void updateInit() {
        modifyTime = null;
    }
}
@Data
public class BaseEntity extends SysBaseEntity{

    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.INSERT)
    @Schema(title = "创建用户Id")
    protected Long createBy;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.INSERT)
    @Schema(title = "创建用户昵称")
    protected String createName;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.INSERT)
    @Schema(title = "创建者Ip")
    protected String createIp;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.UPDATE)
    @Schema(title = "修改用户Id")
    protected Long modifyBy;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.UPDATE)
    @Schema(title = "修改用户昵称")
    protected String modifyName;
    @JSONField(serialize = false, deserialize = false)
    @TableField(fill = FieldFill.UPDATE)
    @Schema(title = "创建者Ip")
    protected String modifyIp;
    @JSONField(serialize = false, deserialize = false)
    @TableLogic(delval = "id",value = "0")
    @Schema(title = "是否删除")
    protected Long del;

    @Override
    public void createInit() {
        super.createInit();
        // 靠全局配置填充
        createBy = null;
        createName = null;
        createIp = null;

        modifyBy = null;
        modifyName = null;
        modifyIp = null;
        version = 0;
        del = 0L;
    }

    @Override
    public void updateInit() {
        super.updateInit();
        modifyBy = null;
        modifyName = null;
        modifyIp = null;
    }

}

1.2 SpringBoot下的Dto、Po、VO转换

通常,我们把Java类与数据库表的对应叫Po;把前端传向后端,后端controller的接受类叫Dto;把通过Controller返回到前端的类叫Vo。
为方便编码,我们写Vo时,会让Vo继承自Po;如果想隐藏字段,直接在Po中的相应字段添加@JSONField(serialize = false, deserialize = false)注解;如果想增加字段,直接在Vo中添加。
Spring框架下有很多Bean转换的工具类,但我用起来,最舒适的还是dozer的库(虽然他的执行效率不是最高的)。原因在于他的接口可以直接帮程序员new出一个转换的类,节省了一行没有意义的代码。
工具类分两个,一个是基类的DtoEntityUtil转换,放在framework-common模块中;一个是DbDtoEntityUtil,放在framework-mysql模块中;
DbDtoEntityUtil相比基类,添加了2个dtoToPo的接口,createFromDto用于实体的创建,editByDto用于实体的更新。
下面分别展示两个类的代码:

public class DtoEntityUtil {
    protected static DozerBeanMapper mapper;
    public static void init(){
        mapper = new DozerBeanMapper();
        mapper.setMappingFiles(Collections.singletonList("dozerJdk8Converters.xml"));
    }
    public static <D, E> E trans(D t, Class<E> clazz) {
        if (t == null) {
            return null;
        }
        return mapper.map(t, clazz);
    }
    public static <D, E> List<E> trans(D[] ts, Class<E> clazz) {
        List<E> es = new ArrayList<E>();
        if (ts == null) {
            return es;
        }
        for (D d : ts) {
            E e = (E) trans(d, clazz);
            if (e != null) {
                es.add(e);
            }
        }
        return es;
    }
    public static <D, E> List<E> trans(List<D> ts, Class<E> clazz) {
        List<E> es = new ArrayList<E>();
        if (ts == null) {
            return es;
        }
        for (D d : ts) {
            E e = (E) trans(d, clazz);
            if (e != null) {
                es.add(e);
            }
        }
        return es;
    }
    public static <T> void copy(T dst, T src){
        mapper.map(src, dst);
    }
}
public class DbDtoEntityUtil extends DtoEntityUtil {

    /**
     * 用于新增
     *
     * @param pDto   dto实体
     * @param clazz  po的class
     * @return 初始化好的po
     */
    public static <D, E> E createFromDto(D pDto, Class<E> clazz) {
        if (pDto == null) {
            return null;
        }
        E result = mapper.map(pDto, clazz);
        if(result instanceof SysBaseEntity){
            ((SysBaseEntity) result).createInit();
        }
        return result;
    }

    /**
     * 用于更新
     *
     * @param  pPo       待更新的po实体
     * @param  pDto      dto实体
     * @param  pPoClass  po的类
     * @return 更新后的po
     */
    public static <D, E> E editByDto(E pPo, D pDto, Class<E> pPoClass) {
        if (pDto == null) {
            return null;
        }
        E result = mapper.map(pPo, pPoClass);
        copy(result,pDto);
        if(result instanceof SysBaseEntity){
            ((SysBaseEntity) result).updateInit();
        }
        return result;
    }
}

1.3 mybatis-plus的通用的service

mybatis-plus的通用service虽然很好用,但并不能完全满足我们的需求。在实际开发中,我们通常会自己写一个通用service继承自mybatis-plus的ServiceImpl。下面将展示代码:

@Service
public @interface ZfDbService{
    String name();
}
public interface IZfDbService<T> extends IService<T> {
    <VO>Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls);

    <VO>Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls, Function<T,VO> convertor);

    <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls);

    <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls, Function<T,VO> convertor);

    T findOne(Wrapper<T> queryWrapper);

    boolean exist(Wrapper<T> queryWrapper);

    T check(Serializable pId);
}
public class ZfDbServiceImpl<M extends BaseMapper<T>, T> extends ServiceImpl<M, T> implements IZfDbService<T> {

    @Override
    public <VO> Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls) {
        IPage<T> pageData = super.page(page,queryWrapper);
        Page<VO> pageVoData = new Page<VO>(pageData.getCurrent(),pageData.getSize());
        pageVoData.setTotal(pageData.getTotal());
        pageVoData.setPages(pageData.getPages());
        List<VO> voList = DtoEntityUtil.trans(pageData.getRecords(),cls);
        pageVoData.setRecords(voList);
        return pageVoData;
    }

    @Override
    public <VO> Page<VO> page(IPage page, Wrapper<T> queryWrapper, Class<VO> cls, Function<T, VO> convertor) {
        IPage<T> pageData = super.page(page,queryWrapper);
        Page<VO> pageVoData = new Page<VO>(pageData.getCurrent(),pageData.getSize());
        pageVoData.setTotal(pageData.getTotal());
        pageVoData.setPages(pageData.getPages());
        List<VO> voList = new ArrayList<>(pageData.getRecords().size());
        for(T data : pageData.getRecords()){
            VO voData = convertor.apply(data);
            voList.add(voData);
        }
        pageVoData.setRecords(voList);
        return pageVoData;
    }

    @Override
    public <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls) {
        List<T> listData = super.list(queryWrapper);
        List<VO> listVoData = DtoEntityUtil.trans(listData,cls);
        return listVoData;
    }

    @Override
    public <VO> List<VO> list(Wrapper<T> queryWrapper, Class<VO> cls, Function<T, VO> convertor) {
        List<T> listData = super.list(queryWrapper);
        List<VO> listVoData = new ArrayList<>();
        for(T data : listData){
            VO voData = convertor.apply(data);
            listVoData.add(voData);
        }
        return listVoData;
    }

    @Override
    public T findOne(Wrapper<T> queryWrapper) {
        List<T> listData = super.list(queryWrapper);
        if(CollectionUtils.isEmpty(listData)){
            return null;
        }
        return listData.get(0);
    }

    @Override
    public boolean exist(Wrapper<T> queryWrapper) {
        Long cnt = baseMapper.selectCount(queryWrapper);
        return cnt > 0;
    }

    @Override
    public T check(Serializable pId) {
        T entity = baseMapper.selectById(pId);
        if(null == entity){
            throw new ServiceException("没有找到Id为"+pId+"的"+getEntityName()+"数据");
        }
        return entity;
    }

    protected String getEntityName(){
        ZfDbService zfDbService = this.getClass().getAnnotation(ZfDbService.class);
        if(null != zfDbService){
            return zfDbService.name();
        }else {
            return "service";
        }
    }
}

1.4 mybatis-plus的自动填充

在躺平攻略(2)中,我们已经展示了mybatis-plus的自动填充,我们在这里使用模板模式写一个基类,使业务系统编写时更方便。

@Slf4j
public abstract class BaseMetaObjectHandler implements MetaObjectHandler {

    protected static final Long SYS_ID = 0L;
    protected static final String SYS_NAME = "sys";
    protected static final String SYS_IP = "localhost";

    protected String createTimeStr = "createTime";
    protected String createByStr= "createBy";
    protected String createByNameStr = "createName";
    protected String createIpStr = "createIp";

    protected String modifyTimeStr = "modifyTime";
    protected String modifyByStr= "modifyBy";
    protected String modifyByNameStr = "modifyName";
    protected String modifyIpStr = "modifyIp";


    protected boolean checkFieldNull(MetaObject pMetaObject, String pField){
        if(pMetaObject.hasSetter(modifyTimeStr)){
            Object orgModifyTime = pMetaObject.getValue(modifyTimeStr);
            if(null == orgModifyTime){
                return true;
            }
        }
        return false;
    }

    protected void insertCreateTime(MetaObject pMetaObject){
        if(checkFieldNull(pMetaObject,createTimeStr)){
            this.strictInsertFill(pMetaObject,createTimeStr,()-> LocalDateTime.now(),LocalDateTime.class);
        }
    }

    protected abstract void insertCreateBy(MetaObject pMetaObject);

    protected abstract void insertCreateByName(MetaObject pMetaObject);

    protected abstract void insertCreateByIp(MetaObject pMetaObject);

    abstract protected void otherInsertFill(MetaObject pMetaObject);

    abstract protected boolean hasTokenObject();

    @Override
    public void insertFill(MetaObject pMetaObject) {
        insertCreateTime(pMetaObject);
        if(hasTokenObject()){
            try{
                if(checkFieldNull(pMetaObject,createByStr)){
                    insertCreateBy(pMetaObject);
                }
                if(checkFieldNull(pMetaObject,createByNameStr)){
                    insertCreateByName(pMetaObject);
                }
                if(checkFieldNull(pMetaObject,createIpStr)){
                    insertCreateByIp(pMetaObject);
                }

            }catch (Exception ex){
                log.error("insertFill 出现异常",ex);
            }
        }else{
            if(checkFieldNull(pMetaObject,createByStr)){
                this.strictInsertFill(pMetaObject,createByStr,()->SYS_ID,Long.class);
            }
            if(checkFieldNull(pMetaObject,createByNameStr)){
                this.strictInsertFill(pMetaObject,createByNameStr,()->SYS_NAME,String.class);
            }
            if(checkFieldNull(pMetaObject,createIpStr)){
                this.strictInsertFill(pMetaObject,createIpStr,()->SYS_IP,String.class);
            }
        }
        otherInsertFill(pMetaObject);
    }

    protected void updateModifyTime(MetaObject pMetaObject){
        if(checkFieldNull(pMetaObject,modifyTimeStr)){
            this.strictUpdateFill(pMetaObject,modifyTimeStr,()-> LocalDateTime.now(),LocalDateTime.class);
        }
    }

    protected abstract void updateModifyBy(MetaObject pMetaObject);

    protected abstract void updateModifyByName(MetaObject pMetaObject);

    protected abstract void updateModifyByIp(MetaObject pMetaObject);

    abstract protected void otherUpdateFill(MetaObject pMetaObject);


    @Override
    public void updateFill(MetaObject pMetaObject) {
        updateModifyTime(pMetaObject);
        if(hasTokenObject()){
            try{
                if(checkFieldNull(pMetaObject,modifyByStr)){
                    updateModifyBy(pMetaObject);
                }
                if(checkFieldNull(pMetaObject,modifyByNameStr)){
                    updateModifyByName(pMetaObject);
                }
                if(checkFieldNull(pMetaObject,modifyIpStr)){
                    updateModifyByIp(pMetaObject);
                }
            }catch (Exception ex){
                log.error("updateFill 出现异常",ex);
            }
        }else{
            if(checkFieldNull(pMetaObject,modifyByStr)){
                this.strictUpdateFill(pMetaObject,modifyByStr,()->SYS_ID,Long.class);
            }
            if(checkFieldNull(pMetaObject,modifyByNameStr)){
                this.strictUpdateFill(pMetaObject,modifyByNameStr,()->SYS_NAME,String.class);
            }
            if(checkFieldNull(pMetaObject,modifyIpStr)){
                this.strictUpdateFill(pMetaObject,modifyIpStr,()->SYS_IP,String.class);
            }
        }
        otherUpdateFill(pMetaObject);
    }
}
public class BaseMetaObjectHandlerAdapter extends BaseMetaObjectHandler{
    @Override
    protected void insertCreateBy(MetaObject pMetaObject) {}
    @Override
    protected void insertCreateByName(MetaObject pMetaObject) {}
    @Override
    protected void insertCreateByIp(MetaObject pMetaObject) {}

    @Override
    protected void otherInsertFill(MetaObject pMetaObject) {}
    @Override
    protected boolean hasTokenObject() {
        return false;
    }
    @Override
    protected void updateModifyBy(MetaObject pMetaObject) {}
    @Override
    protected void updateModifyByName(MetaObject pMetaObject) {}
    @Override
    protected void updateModifyByIp(MetaObject pMetaObject) {}
    @Override
    protected void otherUpdateFill(MetaObject pMetaObject) {}
}

1.5 mybatis-plus的枚举实现与相关配置

对于mybatis-plus的枚举实现,官网有所介绍。
我们这里还是才用方法1,在枚举的code字段上增加@EnumValue注解
mybatis-plus的相关配置很简单:

@AllArgsConstructor
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

        // 分页
        PaginationInnerInterceptor mysqlInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        interceptor.addInnerInterceptor(mysqlInnerInterceptor);

        // 乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

在yml中,做如下配置:

mybatis-plus:
  typeEnumsPackage: indi.zhifa.recipe.bailan.busy.enums

二、应用代码编写和规范

2.1 实体类编写

@Data
@TableName("表名")
@Schema(title = "对象", description = "描述")
public class 主表Entity extends BaseEntity {

    @Schema(title = "字段描述")
    private 变量类型 变量;

}

一般来说,数据库表字段用小写+下划线模式,变量名用驼峰模式。如果变量名与数据库关键字段重名,可以添加这样的注解

@TableField(value = "`round`")

2.2 继承BaseMetaObjectHandler

应用服务需要写一个类继承BaseMetaObjectHandler,来处理审计字段自动填充。
由于我们还没有讲到鉴权,直接继承BaseMetaObjectHandlerAdapter即可

@Component
public class MetaObjectHandler extends BaseMetaObjectHandlerAdapter {
}

2.3 使用脚手架提供的DBService

有了我们自己写的脚手架,在使用mybatis-plus的通用service时,可以继承我们的IZfDbService,减少重复代码。

2.4 代码目录与分层

小编习惯的目录结构是这样的

  • controller 控制器
    • api restful接口
    • view 用于服务端渲染视图接口
    • init 用于服务器的初始化
    • aop 用于aop
    • schedule 用于定时任务
  • dao
    • dbservice 用于mybatis-plus的通用接口
    • mapper 用于mybatis-plus的mapper
  • entity 实体
    • annotation 注解
    • enums 枚举
    • po 数据库实体
    • dto 接受前端的实体
    • vo 返回前端的实体
    • datastruct 通用数据结构
  • facade 门面类,按模块分包,一个门面可注入多个dao层的类
  • handler 一些处理器
  • util 工具类
  • memo 缓存
    分层开发 controller -> facade -> dao.dbservice->dao.mapper。表层类可以注入底层类,同层类原则上不能相互注入。

2.5 常见的实体接口

任何一个主表的facade,都可以无脑的写这几个接口。

  • 按id查询info
实体VO info(Long pId)
  • 分页查询
Page<实体VO> page(int pCurrent, int pSize,... 各查询条件)
  • 创建实体
实体VO create(实体创建Dto pDto)
  • 修改实体
实体VO edit(Long pId, 实体修改Dto pDto)
  • 删除实体
boolean edit(Long pId)

2.5 连表操作

在实际开发中,常常会遇到连表操作。首先,我们来构想一个简单的demo业务。
假定我们做个选课系统,有课程和老师两个实体。课程包括id,name,description,lesson_period,score, teacher_id;老师包括id,name,description,age,gender,telephone,email。
为方便起见,这里直接把实体代码给出

@RequiredArgsConstructor
public enum EGender {
    MALE(1,"男"),
    FEMALE(2,"女");

    @EnumValue
    @Getter
    final Integer code;
    @Getter
    final String name;
}
@Data
@TableName(value = "sch_teacher")
public class TeacherEntity  extends BaseEntity{
    @TableId
    @Schema(title = "主键")
    Long id;
    @Schema(title = "教师名字")
    String name;
    @Schema(title = "性别")
    EGender gender;
    @Schema(title = "出生日期")
    LocalDate birth;
    @Schema(title = "电话")
    String telephone;
    @Schema(title = "邮箱")
    String email;

    @Schema(title = "年龄")
    public Integer getAge(){
        if(null != birth){
            return birth.until(LocalDate.now()).getYears();
        }
        return -1;
    }
}
@Data
@TableName(value = "sch_course")
public class CourseEntity  extends BaseEntity{
    @TableId
    @Schema(title = "主键")
    Long id;
    @Schema(title = "课程名称")
    String name;
    @Schema(title = "课程描述")
    String description;
    @Schema(title = "课时")
    Integer lessonPeriod;
    @Schema(title = "学分")
    Integer score;
    @Schema(title = "是否开放")
    Boolean open;
    @Schema(title = "老师Id")
    Long teacherId;
    @Schema(title = "老师名字")
    String teacherName;
    @Schema(title = "老师出生日期")
    LocalDate teacherBirth;

    @Schema(title = "老师年龄")
    public Integer getTeacherAge(){
        if(null != teacherBirth){
            return teacherBirth.until(LocalDate.now()).getYears();
        }
        return -1;
    }
}

2.5.1 适当冗余

对不太可能发生改变的字段,进行适当冗余,大大有助于提升开发效率。
以上面的例子,老师的名字、性别、生日在创建好后,一般不会更改,所以干脆冗余过来,方便查找,既提高程序执行效率,也使程序员写代码更省力。但要注意,在老师信息的修改接口,要处理这些级联的修改。

2.5.2 使用视图

连表操作一般用于做查询。如果公司领导脑筋不死板的话,建议使用视图,这种方式最方便。
在mabatis-plus中,操作视图和表是一样的。这里不做赘述。
有很多人会质疑执行效率的问题,使用视图相当于查询了2遍数据库。我觉得这点效率的损失可以接受,web开发的瓶颈通常不在这里。
在视图中连表时,查询条件也是可以命中索引的。有兴趣的同学可以使用explain命令自行测试。

2.5.3 查询2次数据库,使用map做关联

为方便理解,这里直接上代码:

@RequiredArgsConstructor
@ZfFacade(name = "SchoolCourse")
public class SchoolCourseFacadeImpl implements ISchoolCourseFacade {
    private final ICourseDbService mCourseDbService;
    private final ITeacherDbService mTeacherDbService;

    @Override
    public Page<CourseDetailViewVo> page(Integer pCurrent, Integer pSize,
                                         Boolean pOpen,
                                         Long pId, String pName, Integer pLessonPeriod, Integer pScore,
                                         Long pTeacherId, String pTeacherName, Integer pAgeMin, Integer pAgeMax, EGender pGender) {
        if(null == pOpen){
            pOpen = true;
        }
        LambdaQueryWrapper<CourseEntity> wrapper = Wrappers.<CourseEntity>lambdaQuery()
                .eq(CourseEntity::getOpen,pOpen)
                .eq(null != pId,CourseEntity::getId,pId)
                .like(StringUtils.hasText(pName),CourseEntity::getName,pName)
                .eq(null != pLessonPeriod,CourseEntity::getLessonPeriod, pLessonPeriod)
                .eq(null != pScore,CourseEntity::getScore,pScore)
                .eq(null != pTeacherId,CourseEntity::getTeacherId,pTeacherId)
                .like(StringUtils.hasText(pTeacherName),CourseEntity::getTeacherName,pTeacherName)
                .between(null != pAgeMin && null != pAgeMax,CourseEntity::getTeacherBirth, LocalDate.now().plusYears(pAgeMin),LocalDate.now().plusYears(pAgeMax))
                .eq(null != pGender,CourseEntity::getTeacherGender,pGender);
        Page<CourseDetailViewVo> pageConfig = new Page<>(pCurrent,pSize);
        Page<CourseDetailViewVo> coursePage = mCourseDbService.page(pageConfig,wrapper,CourseDetailViewVo.class);
        List<CourseDetailViewVo> courseEntityList = coursePage.getRecords();
        List<Long> teacherIds = courseEntityList.stream().filter(course->course.getOpen()).map(CourseDetailViewVo::getTeacherId).distinct().collect(Collectors.toList());
        List<TeacherEntity> teacherEntityList = mTeacherDbService.listByIds(teacherIds);
        Map<Long,TeacherEntity> teacherEntityMap = new HashMap<>();
        for(TeacherEntity teacherEntity : teacherEntityList){
            teacherEntityMap.put(teacherEntity.getId(),teacherEntity);
        }
        for(CourseDetailViewVo courseDetailViewVo : courseEntityList){
            Long teacherId = courseDetailViewVo.getTeacherId();
            if(null != teacherId && teacherId > 0){
                TeacherEntity teacherEntity = teacherEntityMap.get(teacherId);
                courseDetailViewVo.setTeacherEmail(teacherEntity.getEmail());
                courseDetailViewVo.setTeacherTelephone(teacherEntity.getTelephone());
            }
        }
        return coursePage;
    }
}

2.5.4 使用mybatis原生的xml方式

如果查询条件字段分布在两个实体的字段中,又不想使用视图,最简单的方式就是退回到mybatis的xml

2.6 部分更新

在实际工作中,有的操作并不需要全表更新,甚至也不需要全表更新。比如设置课程的授课老师,或者任课老师的基本信息改变。
当然,很多时候即使全量更新,问题也不大,性能的瓶颈通常不在这里。

2.6.1 使用updateWrapper做更新

mybatis-plus可以使用updateWrapper手动设定需要更新的字段。但这种方式有一个弊端,就是自动填充的那一系列功能就此失效。
所以小编认为,这种做法更适合处理一些联动的改变,比如教师基础信息的变化。实例代码在2.6.4中展示

2.6.2 把不想更新的字段设置为null

对于类似给课程分配教师这样的业务,其实直接把原先的课程查出来,把需要变化的字段更改下,再调用updateById接口全量更新一下并没有什么问题。如果担心性能,可以把预估数据量比较大的字段设置为null,比如这里的教师介绍:

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CourseEntity allocateTeacher(Long pCourseId, Long pTeacherId) {
        CourseEntity courseEntity = mCourseDbService.check(pCourseId);
        TeacherEntity teacherEntity = mTeacherDbService.check(pTeacherId);
        courseEntity.updateInit();
        courseEntity.setTeacherId(teacherEntity.getId());
        courseEntity.setTeacherName(teacherEntity.getName());
        courseEntity.setTeacherBirth(teacherEntity.getBirth());
        courseEntity.setDescription(null);
        mCourseDbService.updateById(courseEntity);
        return courseEntity;
    }

2.6.3 把一个表拆分成多个Java实体

如果是在介意这种做法,可以把一个表分成多个实体操作,但同时意味着需要手动写多分mapper,service……

2.6.3 使用Spring框架下的消息处理冗余字段更新

Spring框架有个简单的消息系统,该功能十分适合处理冗余字段更新,废话不多说,直接上代码吧:

@RequiredArgsConstructor
public enum EAppEventType {
    TEACHER_INFO_CHANGE(1,"老师信息更改");

    @EnumValue
    @Getter
    final Integer code;
    @Getter
    final String name;
}
public class AppEvent extends ApplicationEvent {
    @Getter
    private final EAppEventType appEventType;
    @Getter
    private final Long targetId;
    @Getter
    private final Object param;

    public AppEvent(EAppEventType pAppEventType, Long pTargetId, Object pParam, Object pSource){
        super(pSource);
        appEventType = pAppEventType;
        targetId = pTargetId;
        param = pParam;
    }
}
@Data
public class TeacherCreateDto {
    @Schema(title = "教师名字")
    String name;
    @Schema(title = "性别")
    EGender gender;
    @Schema(title = "出生日期")
    LocalDate birth;
    @Schema(title = "电话")
    String telephone;
    @Schema(title = "邮箱")
    String email;
}
@Data
public class TeacherEditDto {
    @Schema(title = "教师名字")
    String name;
    @Schema(title = "性别")
    EGender gender;
    @Schema(title = "出生日期")
    LocalDate birth;
    @Schema(title = "电话")
    String telephone;
    @Schema(title = "邮箱")
    String email;
}
@RequiredArgsConstructor
@ZfFacade(name = "TeacherFacade")
public class TeacherFacadeImpl implements ITeacherFacade {

    private final ITeacherDbService mTeacherDbService;
    private final ApplicationContext mApplicationContext;

    @Override
    public TeacherEntity create(TeacherCreateDto pCreateConfig) {
        TeacherEntity teacherEntity = DbDtoEntityUtil.createFromDto(pCreateConfig,TeacherEntity.class);
        mTeacherDbService.save(teacherEntity);
        return teacherEntity;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public TeacherEntity edit(Long pId, TeacherEditDto pTeacherEditDto) {
        TeacherEntity orgTeacherEntity = mTeacherDbService.check(pId);
        TeacherEntity teacherEntity = DbDtoEntityUtil.editByDto(orgTeacherEntity,pTeacherEditDto,TeacherEntity.class);
        mTeacherDbService.updateById(teacherEntity);
        AppEvent appEvent = new AppEvent(EAppEventType.TEACHER_INFO_CHANGE,teacherEntity.getId(),null,teacherEntity);
        mApplicationContext.publishEvent(appEvent);
        return teacherEntity;
    }
}
@RequiredArgsConstructor
@ZfFacade(name = "SchoolCourse")
public class CourseFacadeImpl implements ICourseFacade , ApplicationListener<AppEvent> {
    private final ICourseDbService mCourseDbService;
    private final ITeacherDbService mTeacherDbService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public CourseEntity allocateTeacher(Long pCourseId, Long pTeacherId) {
        CourseEntity courseEntity = mCourseDbService.check(pCourseId);
        TeacherEntity teacherEntity = mTeacherDbService.check(pTeacherId);
        courseEntity.updateInit();
        courseEntity.setTeacherId(teacherEntity.getId());
        courseEntity.setTeacherName(teacherEntity.getName());
        courseEntity.setTeacherBirth(teacherEntity.getBirth());
        mCourseDbService.updateById(courseEntity);
        return courseEntity;
    }

    private void updateForTeacherInfoChange(TeacherEntity pTeacherEntity){
        LambdaQueryWrapper<CourseEntity> wrapper = Wrappers.<CourseEntity>lambdaQuery()
                .eq(CourseEntity::getTeacherId,pTeacherEntity.getId());
        List<CourseEntity> courseEntityList = mCourseDbService.list(wrapper);
        for(CourseEntity courseEntity : courseEntityList){
            courseEntity.setTeacherName(pTeacherEntity.getName());
            courseEntity.setTeacherBirth(pTeacherEntity.getBirth());
            courseEntity.setTeacherGender(pTeacherEntity.getGender());
        }
        mCourseDbService.updateBatchById(courseEntityList);
    }

    @Override
    public void onApplicationEvent(AppEvent event) {
        switch (event.getAppEventType()) {
            case TEACHER_INFO_CHANGE:
                updateForTeacherInfoChange((TeacherEntity)event.getSource());
                break;
        }
    }
}
本文含有隐藏内容,请 开通VIP 后查看