接口 io.mybatis.mapper.fn.FnMapper<T>

# 2.5.1 基本用法

这个接口主要目的是为了演示如何通过传入字段改变查询的逻辑。接口包含下面几个方法:

public interface FnMapper<T> {

  /**
   * 根据主键更新实体中不为空的字段,强制字段不区分是否 null,都更新
   *
   * @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);

  /**
   * 根据实体字段条件查询唯一的实体({@link io.mybatis.mapper.example.ExampleMapper} 可以实现一样的功能,当前方法只是示例)
   *
   * @param entity       实体类
   * @param selectFileds 查询的字段,不区分字段是否为 null,通过 {@link Fn#of(Fn...)} 创建 {@link Fn.Fns}
   * @return 单个实体,查询结果由多条时报错
   */
  @Lang(Caching.class)
  @SelectProvider(type = FnProvider.class, method = "selectColumns")
  Optional<T> selectColumnsOne(@Param("entity") T entity, @Param("fns") Fn.Fns<T> selectFileds);

  /**
   * 根据实体字段条件批量查询({@link io.mybatis.mapper.example.ExampleMapper} 可以实现一样的功能,当前方法只是示例)
   *
   * @param entity       实体类
   * @param selectFileds 查询的字段,不区分字段是否为 null,通过 {@link Fn#of(Fn...)} 创建 {@link Fn.Fns}
   * @return 实体列表
   */
  @Lang(Caching.class)
  @SelectProvider(type = FnProvider.class, method = "selectColumns")
  List<T> selectColumns(@Param("entity") T entity, @Param("fns") Fn.Fns<T> selectFileds);

}

第一个方法名有点长,用列表展示不太合适了。

这几个方法,都可以通过传入字段改变行为,并且第一个名字最长的方法,直接被复制到了 Mapper 接口的定义中,这就是第 3 点要说明的内容, 实际上你可以复制粘贴的方式构造一个自己的基类 Mapper。

名字最长这个方法是许多人想要的一个方法,在选择更新的情况下还能指定必须更新的字段,这个方法的示例如下:

User user = mapper.selectByPrimaryKey(1L).get();
user.setUserName(null);
int count = mapper.updateByPrimaryKeySelectiveWithForceFields(user, Fn.of(User::getUserName));

单纯从 Mapper 接口调用来看,这个方法名字太长,写法也有点别扭,等看到后续 Service 层封装时, 会掩盖这些不舒服的地方,会变得更简洁,例如 Service 中的接口定义:

/**
 * 更新(非空字段),指定的强制更新字段不区分是否为空
 *
 * @param entity            实体类
 * @param forceUpdateFields 强制更新的字段,不区分字段是否为 null
 * @return 返回更新成功后的实体,远程服务调用时,由于序列化和反序列化,入参和返回值不是同一个对象
 */
T updateSelective(T entity, Fn<T, Object>... forceUpdateFields);

调用的示例:

userService.updateSelective(user, User::getName, User::getRoleId);

# 2.5.2 字符串用法

2.1.0 版本之后,Fn支持直接输入字段名或列名,支持使用字符串形式的参数。

由于字段只能通过方法引用方式,这样会导致部分场景无法使用字符串传递字段或列名。为了解决这个问题,Fn 增加了几个方法,可以直接传递字段名或列名,支持使用字符串形式的参数。

Assert.assertEquals("is_admin", Fn.field(UserIs.class, UserIs::isAdmin).toColumn());
Assert.assertEquals("is_admin", Fn.field(UserIs.class, "admin").toColumn());
Assert.assertEquals("is_admin", Fn.column(UserIs.class, "is_admin").toColumn());

第一个方法时之前默认提供的方法,第二个方法允许指定Java字段名,第三个方法允许指定数据库列名。

下面是示例:

users = mapper.selectColumns(user, Fn.of(UserIds.class, "id1", "name"));
users.forEach(u -> {
    Assert.assertNotNull(u.getId1());
    Assert.assertNull(u.getId2());
    Assert.assertNotNull(u.getName());
});
//使用字段名
example.createCriteria().andEqualTo(Fn.field(User.class, "roleId"), user.getRoleId());
//使用列名
example.createCriteria().andEqualTo(Fn.column(User.class, "role_id"), user.getRoleId());