网站数据包如何做架构,廊坊网站建设方案托管,平台开发的基本流程,门户设计方案基于 mybatis-mapper/provider 核心部分实现的基础的增删改查操作#xff0c;提供了一个核心的 io.mybatis.mapper.BaseMapper 接口和一个 预定义 的 io.mybatis.mapper.Mapper 接口#xff0c;BaseMapper 接口定义如下#xff1a; /*** 基础 Mapper 方法#xff0c;可以在…
基于 mybatis-mapper/provider 核心部分实现的基础的增删改查操作提供了一个核心的 io.mybatis.mapper.BaseMapper 接口和一个 预定义 的 io.mybatis.mapper.Mapper 接口BaseMapper 接口定义如下 /*** 基础 Mapper 方法可以在此基础上继承覆盖已有方法** param T 实体类类型* param I 主键类型* author liuzh*/
public interface BaseMapperT, I extends Serializableextends EntityMapperT, I, ExampleMapperT, ExampleT, CursorMapperT, ExampleT {/*** Example 查询封装*/default ExampleWrapperT, 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.FnsT forceUpdateFields);/*** 根据指定字段集合查询field in (fieldValueList)* p* 这个方法是个示例你也可以使用 Java8 的默认方法实现一些通用方法** param field 字段* param fieldValueList 字段值集合* param F 字段类型* return 实体列表*/default F ListT selectByFieldList(FnT, F field, CollectionF fieldValueList) {ExampleT example new Example();example.createCriteria().andIn((FnT, Object) field.in(entityClass()), fieldValueList);return selectByExample(example);}/*** 根据指定字段集合删除field in (fieldValueList)* p* 这个方法是个示例你也可以使用 Java8 的默认方法实现一些通用方法** param field 字段* param fieldValueList 字段值集合* param F 字段类型* return 实体列表*/default F int deleteByFieldList(FnT, F field, CollectionF fieldValueList) {ExampleT example new Example();example.createCriteria().andIn((FnT, 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 MapperT, I extends Serializable extends BaseMapperT, I {/*** 保存实体默认主键自增并且名称为 id* p* 这个方法是个示例你可以在自己的接口中使用相同的方式覆盖父接口中的配置** param entity 实体类* return 1成功0失败*/OverrideLang(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失败*/OverrideLang(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 继承其他通用接口
上面接口定义中继承了 EntityMapper, ExampleMapper 和 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.FnsT forceUpdateFields); 这就是完全的复制粘贴利用这一点你可以不用 BaseMapper 接口作为自己的基类接口你可以定义一个自己的接口复制粘贴自己的需要的通用方法作为基础接口 例如一个 GuozilanMapper 示例如下
public interface GuozilanMapperT {/*** 保存实体** 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)OptionalT selectByPrimaryKey(Long id);
}
只要继承了上面的接口你就直接拥有了这两个基础方法。 使用这种方式可以自定义一些自己项目需要用到的不同类别的通用接口例如如果你有大量实体都没有主键默认的 BaseMapperT, I 就不太适合 此时你可以自己创建一个 NoIdMapperT把除了主键操作方法外的其他方法有选择的都拷过来就形成了符合自己实际需要的通用 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 ListT selectByFieldList(FnT, F field, ListF fieldValueList) {ExampleT example new Example();example.createCriteria().andIn((FnT, Object) field, fieldValueList);return selectByExample(example);
}/*** 根据指定字段集合删除field in (fieldValueList)* p* 这个方法是个示例你也可以使用 Java8 的默认方法实现一些通用方法** param field 字段* param fieldValueList 字段值集合* param F 字段类型* return 实体列表*/
default F int deleteByFieldList(FnT, F field, ListF fieldValueList) {ExampleT example new Example();example.createCriteria().andIn((FnT, Object) field, fieldValueList);return deleteByExample(example);
}
这两个方法可以直接根据某个字段值的集合进行批量查询或者删除用法示例如下 ListUser users mapper.selectByFieldList(User::getUserName, Arrays.asList(张无忌, 赵敏, 周芷若));
mapper.deleteByFieldList(User::getUserName, Arrays.asList(张无忌, 赵敏, 周芷若));
除了这个例子外还有一段 EntityMapper 被注释的示例 /*** 根据实体字段条件分页查询** param entity 实体类* param rowBounds 分页信息* return 实体列表*/
ListT selectList(T entity, RowBounds rowBounds);/*** 根据查询条件获取第一个结果** param entity 实体类* return 实体*/
default OptionalT selectFirst(T entity) {ListT 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 ListT selectTopN(T entity, int n) {return selectList(entity, new RowBounds(0, n));
}
合理的通过 Java8 的默认方法能够实现海量的通用方法。至于那些是真正需要用到的通用方法就需要根据自己的需要来选择因此虽然上面的方法能通用 但是在缺乏频繁使用场景的情况下BaseMapper 接口并没有接纳这几个方法。 特别注意 上面示例中 ListT selectList(T entity, RowBounds rowBounds); 没有添加 SelectProvider 注解 这是因为 MyBatis 中不允许出现相同名称的方法同时对于 RowBounds 参数有特殊处理 这个方法会直接复用ListT 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 idinsert useGeneratedKeystrue keyPropertyid
seGeneratedKeys 意思是要用JDBC接口方式取回主键主键字段对应的属性名为 id就是要回写到 id 字段。
上面的配置对 MySQL 这类自增数据库是可行的如果你自己的主键不叫 id甚至如果每个表的主键都不统一如 {tableName}_id 你需要在每个具体实现的接口中重写。例如 public interface UserMapper extends MapperUser, Long {/*** 保存实体默认主键自增并且名称为 id* p* 这个方法是个示例你可以在自己的接口中使用相同的方式覆盖父接口中的配置** param entity 实体类* return 1成功0失败*/OverrideLang(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)
OptionalT selectOne(T entity);Lang(Caching.class)
SelectProvider(type EntityProvider.class, method select)
ListT selectList(T entity);Lang(Caching.class)
SelectProvider(type EntityProvider.class, method select)
ListT selectAll();Lang(Caching.class)
SelectProvider(type EntityProvider.class, method select)
CursorT selectCursor(T entity);
利用这一特点通过修改接口方法的返回值和入参就能变身无数个通用方法。 如果在加个 RowBounds 分页参数通用方法直接翻倍。