Mybatis Plus自定义IService与BaseMapper

Mybatis Plus自定义IService与BaseMapper

一、为什么研究起了这个东西

最近在公司独立负责了一个创新业务的java模块的服务研发,在搭建项目的时候,选择了Mybatis Plus 做数据库Dao层的工作。从工作以来,虽然不是第一次接触Mybatis Plus了,但是之前的接触都是在组里的前辈们搭建好框架后直接使用,这次是完完全全自己亲自提刀上阵了。想要自己去自定义IService 和 BaseMapper 实际上是来自一个问题:当一个entity类的某个字段被@TableLogic(逻辑删除)标识后 ,IService 中提供的 boolean updateById(T entity) 方法,无法再对改注释字段进行编辑。 hhh,因为这个原因,盲写的所有业务逻辑删除的接口都没有生效。“也许你会说,可以直接使用IService里提供的remove** 系列的接口,但是这些接口不能在逻辑删除的时候同时更新业务中的 [当前操作人姓名、操作人ID、操作时间…]等信息。” 总而言之就是因为懒的原因,并不想每一个数据库模型,都去对应的mapper里自定义SQL,还有一方面如果调整数据库字段还需要去做对应的调整,作为技术狗来说也觉得挨个调整不够炫酷。

二、实现步骤

2.1 自定义一个注入的方法类

新建一个类MyUpdateById.class,继承com.baomidou.mybatisplus.core.injector包下的抽象类AbstractMethod.class。

public class MyUpdateById extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.UPDATE_BY_ID;
        //方法名称 需要与mapper中的名称保持一致
        String method = "updateByIdWithLogicDelete";
        final String additional = optlockVersion() + tableInfo.getLogicDeleteSql(true, true);
        //因为是基于updateById(T entity)进行改造,这里只是把sqlSet里的logic更改为false,并未调整其他的
        String sql = String.format(sqlMethod.getSql(), tableInfo.getTableName(),
                sqlSet(false, false, tableInfo, false, ENTITY, ENTITY_DOT),
                tableInfo.getKeyColumn(), ENTITY_DOT + tableInfo.getKeyProperty(), additional);
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return addUpdateMappedStatement(mapperClass, modelClass, method, sqlSource);
    }
}

一些额外的说明,其实当你用IDEA打开AbstractMethod时,你会发现该抽象类的实现类,就是BaseMapper中提供的那些方法。如下图:
Mybatis Plus自定义IService与BaseMapper
我在寻找updateById(T entity) 为什么在set 语句部分会自动移除@LogicDelete 注释的字段时,发现AbstractMethod的实现类DeleteById.class,习惯性的在给DeleteById.class 的injectMappedStatement方法打上断点后,去启动服务,就会看到,每一个entity都会先执行这个方法,去初始化该entity的执行SQL模板,如下图所示:
Mybatis Plus自定义IService与BaseMapper
sql变量的数据如下(隐去了工作表的名称与一些其他信息):

<script>
UPDATE table_activity <set>
<if test="et['code'] != null">code=#{et.code},</if>
<if test="et['name'] != null">name=#{et.name},</if>
<if test="et['scopeType'] != null">scope_type=#{et.scopeType},</if>
<if test="et['scopeCode'] != null">scope_code=#{et.scopeCode},</if>
<if test="et['itemCode'] != null">item_code=#{et.itemCode},</if>
<if test="et['sector'] != null">sector=#{et.sector},</if>
<if test="et['helpFileUrls'] != null">help_file_urls=#{et.helpFileUrls},</if>
<if test="et['status'] != null">status=#{et.status},</if>
<if test="et['comment'] != null">comment=#{et.comment},</if>
<if test="et['updater'] != null">updater=#{et.updater},</if>
<if test="et['updateBy'] != null">update_by=#{et.updateBy},</if>
<if test="et['createdAt'] != null">create_at=#{et.createdAt},</if>
<if test="et['updatedAt'] != null">update_at=#{et.updatedAt},</if>
</set> WHERE id=#{et.id} <if test="et instanceof java.util.Map"> AND ${et.MP_OPTLOCK_VERSION_COLUMN}=#{et.MP_OPTLOCK_VERSION_ORIGINAL}</if> AND is_delete=0
</script>

当我们自定义的MyUpdateById配置成功后,将会得到与updateById十分相似,并且多了isDelete字段的set部分信息,详情参考如下信息
Mybatis Plus自定义IService与BaseMapper

2.2 自定义一个SQL注入器

定义一个MySqlInjector.class继承com.baomidou.mybatisplus.core.injector包下的DefaultSqlInjector.class,重写获取方法列表接口,在该接口中添加上我们第一步配置的方法。

public class MySqlInjector extends DefaultSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {

        //拿到父类的getMethodList方法
        List<AbstractMethod> methodList = super.getMethodList(mapperClass);

        //自定义包含逻辑删除的更新
        methodList.add(new MyUpdateById());


        return methodList;
    }
}

2.3 将自定义的SQL注入器添加到配置中

@Bean
    public MySqlInjector myLogicSqlInjector() {
        return new MySqlInjector();
    }

2.4 自定义一个通用的BaseMapper接口,添加上updateByIdWithLogicDelete(@Param(Constants.ENTITY)T entity) 方法

public interface MyBaseMapper<T> extends BaseMapper<T> {

    int updateByIdWithLogicDelete(@Param(Constants.ENTITY) T entity);

}

其实到这里,我们已经可以独立使用mapper模块的功能了
Mybatis Plus自定义IService与BaseMapper
可以直接通过注入的baseMapper,调用我们定义的updateByIdWithLogicDelete方法。
Mybatis Plus自定义IService与BaseMapper

但其实这样做的话,我们还是需要在每个service中自己定义一个方法,再通过mapper来调用该方法,既然是如此通用的方法,也许70%的业务模型都会涉及到逻辑删除时并更新保存一些业务信息,那干脆就直接用自己的IMyService来替代MyBatis Plus 提供给我们IService,在包含IService的基础上,扩充自己项目里通用的一写方法,不就可以简单的实现了吗~

2.5 定义自定义的IMyService接口,提供一些自定义的通用方法

public interface IMyBaseService<T> extends IService<T> {

    /**
     * 对updateById的升级,包含逻辑删除字段的更新
     * @param entity
     * @return
     */
    boolean updateByIdWithLogicDelete(T entity);
}

2.6 添加IMyService的实现

@SuppressWarnings("unchecked")
public class MyBaseServiceImpl <M extends MyBaseMapper<T>, T> extends ServiceImpl<M, T> implements IMyBaseService<T>{

    @Override
    public boolean updateByIdWithLogicDelete(T entity) {
        return retBool(baseMapper.updateByIdWithLogicDelete(entity));
    }
}

2.7 剩下的只有用起来!

1.在service中继承IMyService

public interface IDictService extends IMyBaseService<Dict>

2.在service的实现中继承IMyBaseService的实现

@Service
public class DictServiceImpl extends MyBaseServiceImpl<DictMapper, Dict> implements IDictService {...}

3在mapper中继承MyBaseMapper替代IBaseMapper

public interface DictMapper extends MyBaseMapper<Dict> {
}

总结

最近的工作好无聊,好无力啊,什么时候才能拥有正规化的研发流程。当你速度太快,开完评审的东西都已经通过大家的评审了,当你接口研发好了以后,你就会接到产品的需求变更、领导侧另一服务对接的流程变更… 还要处理模糊没有边界的数据,最近的工作好烦。打工不易,为了这个项目搞上线,可真难,还没有同事陪我一起加班…

上一篇:将单向链表按某值划分成左边小、中间相等、右边大的形式


下一篇:时间序列1——随机序列xt的计算方法