当前位置: 首页 > news >正文

BaseMapper 接口介绍

基于 mybatis-mapper/provider 核心部分实现的基础的增删改查操作,提供了一个核心的 io.mybatis.mapper.BaseMapper 接口和一个 预定义 的 io.mybatis.mapper.Mapper 接口,BaseMapper 接口定义如下:


/*** 基础 Mapper 方法,可以在此基础上继承覆盖已有方法** @param <T> 实体类类型* @param <I> 主键类型* @author liuzh*/
public interface BaseMapper<T, I extends Serializable>extends EntityMapper<T, I>, ExampleMapper<T, Example<T>>, CursorMapper<T, Example<T>> {/*** Example 查询封装*/default ExampleWrapper<T, I> wrapper() {return new ExampleWrapper<>(BaseMapper.this, example());}/*** 根据主键更新实体中不为空的字段,强制字段不区分是否 null,都更新* <p>* 当前方法来自 {@link io.mybatis.mapper.fn.FnMapper},该接口中的其他方法用 {@link ExampleMapper} 也能实现** @param entity            实体类* @param forceUpdateFields 强制更新的字段,不区分字段是否为 null,通过 {@link Fn#of(Fn...)} 创建 {@link Fn.Fns}* @return 1成功,0失败*/@Lang(Caching.class)@UpdateProvider(type = FnProvider.class, method = "updateByPrimaryKeySelectiveWithForceFields")int updateByPrimaryKeySelectiveWithForceFields(@Param("entity") T entity, @Param("fns") Fn.Fns<T> forceUpdateFields);/*** 根据指定字段集合查询:field in (fieldValueList)* <p>* 这个方法是个示例,你也可以使用 Java8 的默认方法实现一些通用方法** @param field          字段* @param fieldValueList 字段值集合* @param <F>            字段类型* @return 实体列表*/default <F> List<T> selectByFieldList(Fn<T, F> field, Collection<F> fieldValueList) {Example<T> example = new Example<>();example.createCriteria().andIn((Fn<T, Object>) field.in(entityClass()), fieldValueList);return selectByExample(example);}/*** 根据指定字段集合删除:field in (fieldValueList)* <p>* 这个方法是个示例,你也可以使用 Java8 的默认方法实现一些通用方法** @param field          字段* @param fieldValueList 字段值集合* @param <F>            字段类型* @return 实体列表*/default <F> int deleteByFieldList(Fn<T, F> field, Collection<F> fieldValueList) {Example<T> example = new Example<>();example.createCriteria().andIn((Fn<T, Object>) field.in(entityClass()), fieldValueList);return deleteByExample(example);}}

这个接口展示了好几个通用方法的特点:

1.可以继承其他通用接口

2.可以直接复制其他接口中的通用方法定义

3.可以使用 Java8 默认方法灵活实现通用方法

在看 Mapper 接口:


/*** 自定义 Mapper 示例,这个 Mapper 基于主键自增重写了 insert 方法,主要用作示例* <p>* 当你使用 Oracle 或其他数据库时,insert 重写时也可以使用 @SelectKey 注解对主键进行定制** @param <T> 实体类类型* @param <I> 主键类型* @author liuzh*/
public interface Mapper<T, I extends Serializable> extends BaseMapper<T, I> {/*** 保存实体,默认主键自增,并且名称为 id* <p>* 这个方法是个示例,你可以在自己的接口中使用相同的方式覆盖父接口中的配置** @param entity 实体类* @return 1成功,0失败*/@Override@Lang(Caching.class)//@SelectKey(statement = "SELECT SEQ.NEXTVAL FROM DUAL", keyProperty = "id", before = true, resultType = long.class)@Options(useGeneratedKeys = true, keyProperty = "id")@InsertProvider(type = EntityProvider.class, method = "insert")int insert(T entity);/*** 保存实体中不为空的字段,默认主键自增,并且名称为 id* <p>* 这个方法是个示例,你可以在自己的接口中使用相同的方式覆盖父接口中的配置** @param entity 实体类* @return 1成功,0失败*/@Override@Lang(Caching.class)//@SelectKey(statement = "SELECT SEQ.NEXTVAL FROM DUAL", keyProperty = "id", before = true, resultType = long.class)@Options(useGeneratedKeys = true, keyProperty = "id")@InsertProvider(type = EntityProvider.class, method = "insertSelective")int insertSelective(T entity);}

这个接口中通过重写继承接口对主键进行了设置,除非你系统正好使用自增的 id 字段作为主键,否则不应该继承 Mapper 接口使用,应该使用 BaseMapper 作为基础。这个接口主要体现了一个特点:

 4. 可以重写继承接口的定义

除了上面已经提到的4个特点外,在下面内容中,还能看到一个特点,5. 那就是一个 provider 实现,通过修改接口方法的返回值和入参,就能变身无数个通用方法,通用方法的实现极其容易。

下面开始详细介绍这些特性。

2.1.1 继承其他通用接口

上面接口定义中,继承了 EntityMapperExampleMapper 和 CursorMapper 接口。这些接口中定义了大量的通用方法, 通过继承使得 BaseMapper 接口获得了大量的通用方法,通过继承可以组合不同类别的方法。 你可以以 BaseMapper 为基础创建自己的基类接口,也可以完全自己创建集成 EntityMapper 等接口来选择需要的通用方法。

提供的最基础的接口可以通过 2.2~2.7 来了解其中具体的方法。

2.1.2 复制其他接口中的通用方法定义

这是最灵活的一点,在 BaseMapper 中直接复制了 FnMapper 的一个方法:


/*** 根据主键更新实体中不为空的字段,强制字段不区分是否 null,都更新* <p>* 当前方法来自 {@link io.mybatis.mapper.fn.FnMapper},该接口中的其他方法用 {@link ExampleMapper} 也能实现** @param entity            实体类* @param forceUpdateFields 强制更新的字段,不区分字段是否为 null,通过 {@link Fn#of(Fn...)} 创建 {@link Fn.Fns}* @return 1成功,0失败*/
@Lang(Caching.class)
@UpdateProvider(type = FnProvider.class, method = "updateByPrimaryKeySelectiveWithForceFields")
int updateByPrimaryKeySelectiveWithForceFields(@Param("entity") T entity, @Param("fns") Fn.Fns<T> forceUpdateFields);

 这就是完全的复制粘贴,利用这一点,你可以不用 BaseMapper 接口作为自己的基类接口,你可以定义一个自己的接口,复制粘贴自己的需要的通用方法作为基础接口, 例如一个 GuozilanMapper 示例如下:

public interface GuozilanMapper<T> {/*** 保存实体** @param entity 实体类* @return 1成功,0失败*/@Lang(Caching.class)@InsertProvider(type = EntityProvider.class, method = "insert")int insert(T entity);/*** 根据主键查询实体** @param id 主键* @return 实体*/@Lang(Caching.class)@SelectProvider(type = EntityProvider.class, method = "selectByPrimaryKey")Optional<T> selectByPrimaryKey(Long id);
}

只要继承了上面的接口,你就直接拥有了这两个基础方法。

使用这种方式可以自定义一些自己项目需要用到的不同类别的通用接口,例如,如果你有大量实体都没有主键,默认的 BaseMapper<T, I> 就不太适合, 此时你可以自己创建一个 NoIdMapper<T>,把除了主键操作方法外的其他方法(有选择的)都拷过来,就形成了符合自己实际需要的通用 Mapper。

推而广之之后,还有更绝的用法,不继承接口,或者基础接口没有某个方法,直接复制注解过来,不需要自己写 XML:


public interface UserMapper {/*** 保存实体** @param entity 实体类* @return 1成功,0失败*/@Lang(Caching.class)@InsertProvider(type = EntityProvider.class, method = "insert")int insert(User entity);
}

你不需要任何具体的 SQL,上面的 insert 方法就可以直接使用了。

2.1.3 使用 Java8 默认方法灵活实现通用方法

在 BaseMapper 接口中,利用现有的 Example 方法,实现了两个非常常用的通用方法:


/*** 根据指定字段集合查询:field in (fieldValueList)* <p>* 这个方法是个示例,你也可以使用 Java8 的默认方法实现一些通用方法** @param field          字段* @param fieldValueList 字段值集合* @param <F>            字段类型* @return 实体列表*/
default <F> List<T> selectByFieldList(Fn<T, F> field, List<F> fieldValueList) {Example<T> example = new Example<>();example.createCriteria().andIn((Fn<T, Object>) field, fieldValueList);return selectByExample(example);
}/*** 根据指定字段集合删除:field in (fieldValueList)* <p>* 这个方法是个示例,你也可以使用 Java8 的默认方法实现一些通用方法** @param field          字段* @param fieldValueList 字段值集合* @param <F>            字段类型* @return 实体列表*/
default <F> int deleteByFieldList(Fn<T, F> field, List<F> fieldValueList) {Example<T> example = new Example<>();example.createCriteria().andIn((Fn<T, Object>) field, fieldValueList);return deleteByExample(example);
}

这两个方法可以直接根据某个字段值的集合进行批量查询或者删除,用法示例如下:


List<User> users = mapper.selectByFieldList(User::getUserName, Arrays.asList("张无忌", "赵敏", "周芷若"));
mapper.deleteByFieldList(User::getUserName, Arrays.asList("张无忌", "赵敏", "周芷若"));

除了这个例子外,还有一段 EntityMapper 被注释的示例:


/*** 根据实体字段条件分页查询** @param entity    实体类* @param rowBounds 分页信息* @return 实体列表*/
List<T> selectList(T entity, RowBounds rowBounds);/*** 根据查询条件获取第一个结果** @param entity 实体类* @return 实体*/
default Optional<T> selectFirst(T entity) {List<T> entities = selectList(entity, new RowBounds(0, 1));if (entities.size() == 1) {return Optional.of(entities.get(0));}return Optional.empty();
}/*** 根据查询条件获取指定的前几个对象** @param entity 实体类* @param n      指定的个数* @return 实体*/
default List<T> selectTopN(T entity, int n) {return selectList(entity, new RowBounds(0, n));
}

合理的通过 Java8 的默认方法,能够实现海量的通用方法。至于那些是真正需要用到的通用方法,就需要根据自己的需要来选择,因此虽然上面的方法能通用, 但是在缺乏频繁使用场景的情况下,BaseMapper 接口并没有接纳这几个方法。

特别注意
上面示例中 List<T> selectList(T entity, RowBounds rowBounds); 没有添加 @SelectProvider 注解, 这是因为 MyBatis 中不允许出现相同名称的方法,同时对于 RowBounds 参数有特殊处理, 这个方法会直接复用List<T> selectList(T entity);方法,这个方法已经有了 @SelectProvider 注解配置。

2.1.4 重写继承接口的定义

在 EntityMapper 中有 insert 方法定义如下:


/*** 保存实体** @param entity 实体类* @return 1成功,0失败*/
@Lang(Caching.class)
@InsertProvider(type = EntityProvider.class, method = "insert")
int insert(T entity);

这个定义没有处理主键,需要自己设置好主键后调用该方法新增数据。

特别注意 在 2.x 版本之后支持在实体上配置主键策略,因此在实体配置主键策略的情况下,这个方法可以直接使用。 主键策略示例如下:


@Entity.Table("user")
public class User {@Entity.Column(value = "user_id", id = true, useGeneratedKeys = true)private Long   userId;

当调用 insert(user) 方法的时候会自动处理主键,而且也可以避免主键名称必须固定为统一名称的问题。

如果我使用的 MySql 自增怎么办?主键null也能直接保存,但是不回写。

如果使用 Oracle 序列怎么办?直接用这个方法是没有办法的。

因为可以 重写继承接口的定义,所以可以支持所有 MyBatis 本身能支持的所有主键方式。

在 Mapper 中,覆盖定义如下:


/*** 保存实体,默认主键自增,并且名称为 id* <p>* 这个方法是个示例,你可以在自己的接口中使用相同的方式覆盖父接口中的配置** @param entity 实体类* @return 1成功,0失败*/
@Override
@Lang(Caching.class)
@Options(useGeneratedKeys = true, keyProperty = "id")
@InsertProvider(type = EntityProvider.class, method = "insert")
int insert(T entity);

首先 @Override 是重写父接口定义,然后和原来相比增加了下面的注解:

@Options(useGeneratedKeys = true, keyProperty = "id")

这个注解对应 xml 中的配置如下:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">

seGeneratedKeys 意思是要用JDBC接口方式取回主键,主键字段对应的属性名为 id,就是要回写到 id 字段。

上面的配置对 MySQL 这类自增数据库是可行的,如果你自己的主键不叫 id,甚至如果每个表的主键都不统一(如 {tableName}_id), 你需要在每个具体实现的接口中重写。例如:


public interface UserMapper extends Mapper<User, Long> {/*** 保存实体,默认主键自增,并且名称为 id* <p>* 这个方法是个示例,你可以在自己的接口中使用相同的方式覆盖父接口中的配置** @param entity 实体类* @return 1成功,0失败*/@Override@Lang(Caching.class)@Options(useGeneratedKeys = true, keyProperty = "userId")@InsertProvider(type = EntityProvider.class, method = "insert")int insert(User entity);}

如果是Oracle序列或者需要执行SQL生成主键或者取回主键时,可以配置 @SelectKey 注解,示例如下:


@Override
@Lang(Caching.class)
@SelectKey(statement = "CALL IDENTITY()", keyProperty = "id", resultType = Long.class, before = false)
@InsertProvider(type = EntityProvider.class, method = "insert")
int insert(User entity);

上面还只是通过增加注解重新定义了接口方法。实际上你还可以更换 @InsertProvider(type = EntityProvider.class, method = "insert"), 将其中的实现换成其他的也可以,如果对默认的方法和逻辑不满意,就可以改成别的。

通过 重写继承接口的定义,应该能感觉出有多强大,多么灵活。

特别注意 在 2.x 版本之后支持在实体上配置主键策略,这种方式更方便,详情看 3. 实体类注解

2.1.5 通过修改接口方法的返回值和入参,就能变身无数个通用方法

以 EntityProvider 中的 select 方法为例,方法的具体实现如下:


/*** 根据实体字段条件查询唯一的实体,根据实体字段条件批量查询,查询结果的数量由方法定义** @param providerContext 上下文* @return cacheKey*/
public static String select(ProviderContext providerContext) {return SqlScript.caching(providerContext, new SqlScript() {@Overridepublic String getSql(EntityTable entity) {return "SELECT " + entity.baseColumnAsPropertyList()+ " FROM " + entity.table()+ ifParameterNotNull(() ->where(() ->entity.whereColumns().stream().map(column ->ifTest(column.notNullTest(), () -> "AND " + column.columnEqualsProperty())).collect(Collectors.joining(LF))))+ entity.groupByColumn().orElse("")+ entity.havingColumn().orElse("")+ entity.orderByColumn().orElse("");}});
}

最终会生成一个 SELECT .. FROM .. WHERE ... 的 SQL,在 MyBatis 中,SQL 只定义了如何在数据库执行, 执行后的结果和取值方式是通过接口方法定义决定的,因此就这样一个 SELECT 查询,能够实现很多个方法,举例如下:


@Lang(Caching.class)
@SelectProvider(type = EntityProvider.class, method = "select")
Optional<T> selectOne(T entity);@Lang(Caching.class)
@SelectProvider(type = EntityProvider.class, method = "select")
List<T> selectList(T entity);@Lang(Caching.class)
@SelectProvider(type = EntityProvider.class, method = "select")
List<T> selectAll();@Lang(Caching.class)
@SelectProvider(type = EntityProvider.class, method = "select")
Cursor<T> selectCursor(T entity); 

利用这一特点,通过修改接口方法的返回值和入参,就能变身无数个通用方法。

如果在加个 RowBounds 分页参数,通用方法直接翻倍。

http://www.lryc.cn/news/383640.html

相关文章:

  • HAL-Cubemax定时器使用记录
  • 同时使用磁吸充电器和Lightning时,iPhone充电速度会变快吗?
  • 零成本搭建个人图床服务器
  • SpringBoot 搭建sftp服务 实现远程上传和下载文件
  • IDEA中使用leetcode 刷题
  • 华为海思CPU解读
  • 中介子方程三十三
  • 今年哪两个行业可能有贝塔?
  • 嵌入式软件开发工具使用介绍
  • 【TB作品】MSP430G2553,单片机,口袋板, 交通灯控制系统
  • windows 安装 Kubernetes(k8s)
  • C语言 | Leetcode C语言题解之第189题轮转数组
  • 【安全审核】音视频审核开通以及计费相关
  • 【实战】Spring Cloud Stream 3.1+整合Kafka
  • java之可变字符串之append方法
  • [保姆级教程]uniapp自定义导航栏
  • 项目训练营第二天
  • 考研数学一有多难?130+背后的残酷真相
  • vue2脚手架笔记总结1
  • 校园巡礼:一周只上四天课,入学即发钱?深圳理工大学,开局即王炸
  • 免交互 实验
  • Sublime Text 设置
  • spire.Pdf 将pdf转成image
  • 仓颉编程语言 -- 初识(一)
  • 前端JS必用工具【js-tool-big-box】学习,数值型数组的正向排序和倒向排序
  • python web框架哪家强?Flask、Django、FastAPI对比
  • Mybatis plus:IService接口
  • 时序分析基本概念介绍——min pulse width 最小脉冲宽度
  • PHP原生代码生成pdf---解决中文乱码问题
  • 智慧车库管理系统