ApiBoot 整合 持久化框架 Mybatis Enhance

ApiBoot Mybatis Enhance是一款数据持久化动态查询结构化框架,用户操作单表、多表关联的数据,在Mybatis的基础上进行封装扩展,不影响任何原生的使用,使用后完全替代mybatis-coremybatis-spring以及mybatis-spring-boot-starter,使用SpringBoot配置文件方式代替传统代码配置,减少繁琐代码编写。

1. 特性

ApiBoot Mybatis Enhance总结了目前主流框架的使用风格,对开发者来说使用不便的地方进行封装扩展,让数据操作更艺术。

特性一、提供增强方法

ApiBoot Mybatis Enhance内部提供了常用的方法实现,可以快速对单条、多条数据进行CRUD常规操作。

特性二、DSL

ApiBoot Mybatis Enhance提供动态查询方式,可以动态查询单表、多表关联指定的列值或者整条数据,可以将结果映射成你想要的类型。

特性三、方法规则查询

ApiBoot Mybatis Enhance提供方法规则查询,在对应数据接口Mapper内,遵循一定规则可以完成对数据的查询、统计、删除等。

2. 愿景

ApiBoot Mybatis Enhance致力于解决简单、快速操作数据问题,省去繁琐SQL的编写,让开发人员更专注业务编写。

3. 开始使用

在项目的pom.xml文件内添加如下依赖:

  1. <!--ApiBoot Mybatis Enhance-->
  2. <dependency>
  3. <groupId>org.minbox.framework</groupId>
  4. <artifactId>api-boot-starter-mybatis-enhance</artifactId>
  5. </dependency>

注意:如果未添加ApiBoot版本依赖,请访问版本依赖查看添加方式。

4. 定义数据实体

Enhance所需要的数据实体很简单,只需要告知Enhance所需要的表信息以及列信息即可(实际在开发过程中编写数据实体是一件很繁琐的事情,因此ApiBootEnhance编写了专属的代码生成插件,访问Enhance Codegen查看具体使用方式)利用Enhance Codegen自动生成的数据实体类如下所示:

  1. import com.gitee.hengboy.mybatis.enhance.common.annotation.Column;
  2. import com.gitee.hengboy.mybatis.enhance.common.annotation.Id;
  3. import com.gitee.hengboy.mybatis.enhance.common.annotation.Table;
  4. import com.gitee.hengboy.mybatis.enhance.common.enums.KeyGeneratorTypeEnum;
  5. import lombok.Data;
  6. import java.sql.Timestamp;
  7. /**
  8. * 用户基本信息
  9. * @author ApiBoot Mybatis Enhance Codegen
  10. */
  11. @Data
  12. @Table(name = "iot_user_info")
  13. public class UserInfo {
  14. /**
  15. * 用户主键
  16. */
  17. @Id(generatorType = KeyGeneratorTypeEnum.UUID)
  18. @Column(name = "UI_ID")
  19. private String id;
  20. /**
  21. * 用户编号
  22. */
  23. @Column(name = "UI_USER_NO")
  24. private String userNo;
  25. /**
  26. * 用户名
  27. */
  28. @Column(name = "UI_USER_NAME")
  29. private String userName;
  30. /**
  31. * 年龄
  32. */
  33. @Column(name = "UI_AGE")
  34. private Integer age;
  35. /**
  36. * 性别,boy:男,girl:女,other:其他
  37. */
  38. @Column(name = "UI_SEX")
  39. private String sex = "other";
  40. /**
  41. * 创建时间
  42. */
  43. @Column(name = "UI_CREATE_TIME",insertable = false)
  44. private Timestamp createTime;
  45. /**
  46. * 1:正常,0:禁用,-1:删除
  47. */
  48. @Column(name = "UI_STATUS")
  49. private Integer status = 1;
  50. /**
  51. * 备注信息
  52. */
  53. @Column(name = "UI_MARK")
  54. private String mark;
  55. }

在上面的数据实体类内我们都是使用的注解形式来完成的配置,简单介绍下注解的作用

  • @DataLombok内部的注解,如果没有了解过该插件可以访问恒宇少年的文章第二十九章:基于SpringBoot平台使用Lombok来优雅的编码查看具体使用方法。
  • @TableEnhance内部注解,用于标注该数据实体所使用的表信息
  • @IdEnhance内部注解,用户标注主键字段
  • @ColumnEnhance内部注解,用于标注字段对应的列信息

5. 了解EnhanceMapper接口

Enhance所管理的Mybatis Mapper以及内置的CRUD操作单表数据的接口方法都是由EnhanceMapper接口来完成的。

ApiBoot Mybatis Enhance在项目启动时会自动加载EnhanceMapper的所有子类进行数据操作方法实现并交付给Spring IOC进行托管,为每一个方法都提供对应的Statement以及SqlSource的实现,而StatementID则是Mapper类的全限定名。

5.1 EnhanceMapper提供的方法列表

EnhanceMapper接口内提供的数据操作方法,如下所示:

  1. // 统计数据
  2. Long countAll() throws EnhanceFrameworkException;
  3. // 清空数据
  4. void deleteAll() throws EnhanceFrameworkException;
  5. // 根据主键数组删除指定数据
  6. void deleteArray(Id... ids) throws EnhanceFrameworkException;
  7. // 根据自定义sql删除数据
  8. void deleteBySql(String sql, Map<String, Object> params) throws EnhanceFrameworkException;
  9. // 根据主键集合删除指定数据
  10. void deleteCollection(Collection<Id> collection) throws EnhanceFrameworkException;
  11. // 删除一条数据
  12. void deleteOne(Id id) throws EnhanceFrameworkException;
  13. // 数据保存
  14. void insert(T t) throws EnhanceFrameworkException;
  15. // 保存数组内的所有数据
  16. void insertArray(T... array) throws EnhanceFrameworkException;
  17. // 保存集合内的所有数据
  18. void insertCollection(Collection<T> collection) throws EnhanceFrameworkException;
  19. // 查询全部数据
  20. List<T> selectAll() throws EnhanceFrameworkException;
  21. // 根据主键数组查询指定数据
  22. List<T> selectArray(Id... ids) throws EnhanceFrameworkException;
  23. // 分页查询数据
  24. List<T> selectByPageable(Pageable pageable) throws EnhanceFrameworkException;
  25. // 自定义sql查询数据
  26. List<Map> selectBySql(String sql, Map<String, Object> params) throws EnhanceFrameworkException;
  27. // 根据主键集合查询指定数据
  28. List<T> selectCollection(Collection<Id> ids) throws EnhanceFrameworkException;
  29. // 根据主键查询单条数据
  30. T selectOne(Id id) throws EnhanceFrameworkException;
  31. // 根据主键更新数据实体
  32. void update(T t) throws EnhanceFrameworkException;
  33. // 自定义sql更新数据
  34. void updateBySql(String sql, Map<String, Object> params) throws EnhanceFrameworkException;

5.2 使用EnhanceMapper

针对上面的UserInfo数据实体,对应的Mapper接口定义如下所示:

  1. /**
  2. * 用户数据接口
  3. *
  4. * @author:恒宇少年 - 于起宇
  5. * <p>
  6. * DateTime:2019-05-23 17:02
  7. * Blog:http://blog.yuqiyu.com
  8. * WebSite:http://www.jianshu.com/u/092df3f77bca
  9. * Gitee:https://gitee.com/hengboy
  10. * GitHub:https://github.com/hengboy
  11. */
  12. public interface UserMapper extends EnhanceMapper<UserInfo, String> {
  13. }

在上面UserMapper拥有了EnhanceMapper的全部接口方法并且每一个方法都已经实现了对应的Statement,而且UserMapper已经被Spring IOC托管,我们在实体类内可以直接通过注入方法来调用使用。

6. 保存数据

EnhanceMapper所提供的insert方法参数为数据对象实例,添加一条数据如下所示:

  1. @Service
  2. @Transactional(rollbackFor = Exception.class)
  3. public class UserService {
  4. /**
  5. * 用户基本信息数据接口
  6. */
  7. @Autowired
  8. private UserMapper userMapper;
  9. /**
  10. * 保存用户信息
  11. */
  12. public void saveUser() {
  13. // 创建用户基本信息对象
  14. UserInfo userInfo = new UserInfo();
  15. // 设置用户编号
  16. userInfo.setUserNo("2019xxxx");
  17. // 设置用户年龄
  18. userInfo.setAge(24);
  19. // 执行数据保存
  20. userMapper.insert(userInfo);
  21. }
  22. }

通过上面saveUser就可以执行保存一条用户基本信息,在上面保存数据方法中并未设置主键的值,下面详细介绍Enhance内提供的几种主键生成策略。

6.1 自增主键

Enhance支持自增类型的主键,可以将本次添加数据的自增主键值查询并设置到保存对象配置@Id注解的字段内,配置使用如下所示:

  1. /**
  2. * 用户主键
  3. */
  4. @Id(generatorType = KeyGeneratorTypeEnum.AUTO)
  5. @Column(name = "UI_ID")
  6. private Integer userId;

6.2 UUID主键

如果主键使用UUID方式进行存储,Enhance同样是支持配置,如下所示:

  1. /**
  2. * 用户主键
  3. */
  4. @Id(generatorType = KeyGeneratorTypeEnum.UUID)
  5. @Column(name = "UI_ID")
  6. private String id;

注意:数据库设置主键字段长度时,UUID生成的长度为36位,包含-

6.3 自定义主键

除了自增、UUID方式的主键以外,还支持自定义主键的方式,比如用户编号:2019xxxxx,如下所示:

  1. /**
  2. * 用户主键
  3. */
  4. @Id(generatorType = KeyGeneratorTypeEnum.DIY)
  5. @Column(name = "UI_ID")
  6. private String id;

7. 查询数据

EnhanceMapper提供了多种查询方法,其中包含主键查询根据主键的集合查询分页查询等,下面详细介绍具体的使用方法。

7.1 根据主键查询单条记录

Enhance支持三种根据主键查询的方法,如下所示:

  1. // 查询单条记录
  2. UserInfo user = mapper.selectOne("test");
  3. // 根据主键数组查询多条记录
  4. List<UserInfo> userArray = mapper.selectArray("test", "admin");
  5. // 根据主键集合查询多条记录
  6. List<UserInfo> userCollection = mapper.selectCollection(Arrays.asList("test", "admin"));

其中selectArrayselectCollection两个方法是根据传递的多个主键进行查询出对应多条记录。

7.2 查询记录列表

Enhance支持查询表内的全部数据、分页查询数据,如下所示:

  1. // 查询全部数据
  2. List<UserInfo> users = mapper.selectAll();
  3. // 分页查询数据
  4. List<UserInfo> pageUsers = mapper.selectByPageable(Pageable.builder().limit(10).currentPage(2).build());

EnhanceMapper接口提供的查询记录列表的方法,有一定的局限性,目前并不支持添加查询条件进行筛选数据,不过ApiBoot Enhance提供了DSL方式进行查询数据,具体使用方法请参考本章节下面的文档内容。

8. 更新数据

EnhanceMapper提供更新单个对象的方法,具体使用方法如下所示:

  1. UserInfo userInfo = new UserInfo();
  2. userInfo.setId("test");
  3. userInfo.setAge(20);
  4. userInfo.setUserName("测试用户");
  5. mapper.update(userInfo);

更新数据时,不设置值得字段会自动将表内数据更新为null,如果你只想更新某一个字段,可以使用如下两种形式。

  • 查询后更新
  1. // 根据主键查询记录
  2. UserInfo userInfo = mapper.selectOne("test");
  3. // 更新查询出记录的年龄
  4. userInfo.setAge(24);
  5. // 更新查询出记录的名称
  6. userInfo.setUserName("测试用户");
  7. // 执行更新记录
  8. mapper.update(userInfo);

update方法调用时,参数对象的主键必须设置值,更新时自动根据主键进行设置。

  • 动态更新详见本章动态更新文档,支持根据主键自定义条件进行筛选定位更新的记录。

9. 删除数据

EnhanceMapper提供的删除方法都是物理删除,会从表内直接将匹配数据删除,EnhanceMapper提供的删除方法如下所示:

  1. // 根据主键删除单条数据记录
  2. mapper.deleteOne(1);
  3. // 根据主键数组删除多条对应数据记录
  4. mapper.deleteArray(1, 2, 3, 4);
  5. // 根据主键集合删除多条对应数据记录
  6. mapper.deleteCollection(Arrays.asList(1, 2, 3, 4));
  7. // 删除全部记录
  8. mapper.deleteAll();

如果你的业务需求是业务逻辑删除,只更新某一个状态字段的值时,可以考虑使用动态更新,详见下面文档。

10. 动态查询

ApiBoot Enhance的动态查询可以做很多事情,我们可以对单表数据多表数据进行查询操作,可以使用某一个数据对象作为查询的返回值映射,也可以自定义DTO基本数据类型来作为查询方法的返回值,每一个查询的查询条件都可以做到完全自定义,实现一行代码完成对数据的查询。

10.1 生成动态查询实体

在通过ApiBoot Enhance动态操作数据之前需要对应每一张表创建一个动态实体ApiBoot提供了对应每一张表自动生成数据实体动态实体maven plugin,访问ApiBoot Enhance Codegen查看具体使用方式。下面是一个动态实体示例,通过ApiBoot Enhance Codegen自动生成,对应数据库内user_info表结构。

  1. /**
  2. * 用户基本信息
  3. * @author ApiBoot Mybatis Enhance Codegen
  4. */
  5. public class DUserInfo extends TableExpression<UserInfo> {
  6. public DUserInfo(String root) {
  7. super(root);
  8. }
  9. public static DUserInfo DSL() {
  10. return new DUserInfo("user_info");
  11. }
  12. /**
  13. * 用户主键
  14. */
  15. public ColumnExpression id = new ColumnExpression("UI_ID", this);
  16. /**
  17. * 用户编号
  18. */
  19. public ColumnExpression userNo = new ColumnExpression("UI_USER_NO", this);
  20. /**
  21. * 用户名
  22. */
  23. public ColumnExpression userName = new ColumnExpression("UI_USER_NAME", this);
  24. /**
  25. * 年龄
  26. */
  27. public ColumnExpression age = new ColumnExpression("UI_AGE", this);
  28. /**
  29. * 性别,boy:男,girl:女,other:其他
  30. */
  31. public ColumnExpression sex = new ColumnExpression("UI_SEX", this);
  32. /**
  33. * 创建时间
  34. */
  35. public ColumnExpression createTime = new ColumnExpression("UI_CREATE_TIME", this);
  36. /**
  37. * 1:正常,0:禁用,-1:删除
  38. */
  39. public ColumnExpression status = new ColumnExpression("UI_STATUS", this);
  40. /**
  41. * 备注信息
  42. */
  43. public ColumnExpression mark = new ColumnExpression("UI_MARK", this);
  44. @Override
  45. public ColumnExpression[] getColumns() {
  46. return new ColumnExpression[]{id, userNo, userName, age, sex, createTime, status, mark};
  47. }
  48. }

10.2 单表查询

下面从查询单表内的数据开始一一拆分讲解。

10.2.1 主键方式查询

我们了解了EnhanceMapper接口所提供了根据主键查询的selectOne方法,这两种方式最终的效果是一样的,动态方式的主键查询如下所示:

  1. // 实例化动态查询实体
  2. DUserInfo dUserInfo = DUserInfo.DSL();
  3. // 从DUserInfo动态实体对应的数据表内查询主键为1的记录
  4. UserInfo user = dslFactory.createSearchable().selectFrom(dUserInfo)
  5. .where(dUserInfo.id.eq(1))
  6. // 查询的返回值类型是UserInfo数据实体
  7. .resultType(UserInfo.class)
  8. // 查询单条数据
  9. .fetchOne();

10.2.2 非主键方式查询

通过动态方式查询,可以根据动态实体内的任意一个、或者多个字段作为查询条件,如下所示:

  1. // 实例化动态查询实体
  2. DUserInfo dUserInfo = DUserInfo.DSL();
  3. // 根据userNo字段查询数据
  4. UserInfo user = dslFactory.createSearchable().selectFrom(dUserInfo)
  5. .where(dUserInfo.userNo.eq("2019xxx"))
  6. .resultType(UserInfo.class)
  7. .fetchOne();

10.2.3 多条件组合查询

一个查询的查询条件可以有很多个,使用动态查询可以完成andor的条件关联,模拟需求查询用户编号为2019xxx并且大于20岁男孩指定某一个用户,如下所示:

  1. // 实例化动态查询实体
  2. DUserInfo dUserInfo = DUserInfo.DSL();
  3. // 根据userNo字段查询数据
  4. UserInfo userInfo = dslFactory.createSearchable().selectFrom(dUserInfo)
  5. // where userNo = 2019xxx
  6. .where(dUserInfo.userNo.eq("2019xxx"))
  7. // and sex = boy
  8. .and(dUserInfo.sex.eq("boy"))
  9. // and age > 20
  10. .and(dUserInfo.age.gt(20))
  11. .resultType(UserInfo.class)
  12. .fetchOne();

上面动态查询对应生成的SQL,如下所示:

  1. -- 生成的SQL
  2. Preparing: SELECT iot_user_info.UI_ID,
  3. iot_user_info.UI_USER_NO,
  4. iot_user_info.UI_USER_NAME,
  5. iot_user_info.UI_AGE,
  6. iot_user_info.UI_SEX,
  7. iot_user_info.UI_CREATE_TIME,
  8. iot_user_info.UI_STATUS,
  9. iot_user_info.UI_MARK
  10. FROM iot_user_info
  11. WHERE (iot_user_info.UI_USER_NO = ? AND iot_user_info.UI_SEX = ? AND iot_user_info.UI_AGE > ?);
  12. -- 参数列表
  13. Parameters: 2019xxx(String), boy(String), 20(Integer)

下面我们模拟需求查询25岁以下用户性别为女孩的用户列表,动态查询如下所示:

  1. // 实例化动态查询实体
  2. DUserInfo dUserInfo = DUserInfo.DSL();
  3. // 根据userNo字段查询数据
  4. List<UserInfo> users = dslFactory.createSearchable().selectFrom(dUserInfo)
  5. // sex = girl
  6. .where(dUserInfo.sex.eq("girl"))
  7. // or age < 25
  8. .or(dUserInfo.age.lt(25))
  9. .resultType(UserInfo.class)
  10. .fetch();

上面动态查询对应生成的SQL如下所示:

  1. -- 生成的SQL
  2. Preparing: SELECT iot_user_info.UI_ID,
  3. iot_user_info.UI_USER_NO,
  4. iot_user_info.UI_USER_NAME,
  5. iot_user_info.UI_AGE,
  6. iot_user_info.UI_SEX,
  7. iot_user_info.UI_CREATE_TIME,
  8. iot_user_info.UI_STATUS,
  9. iot_user_info.UI_MARK
  10. FROM iot_user_info
  11. WHERE (iot_user_info.UI_SEX = ?)
  12. OR (iot_user_info.UI_AGE < ?);
  13. -- 参数列表
  14. Parameters: girl(String), 25(Integer)

10.3 多表关联查询

ApiBoot Enhance支持leftJoinrightJoininnerJoin三种关联方式查询,这三种关联查询的使用方式一致,下面我们来看下多表关联的leftJoin实现方式:

  1. // 系统用户动态实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. // 系统用户角色关联动态实体
  4. DSystemUserRole dSystemUserRole = DSystemUserRole.DSL();
  5. // 执行查询并且返回SystemUser类型对象
  6. List<UserInfo> users = dslFactory.createSearchable()
  7. .selectFrom(dSystemUser)
  8. .leftJoin(dSystemUser.id, dSystemUserRole.userId)
  9. .where(dSystemUserRole.roleId.eq("367c8078-a1f1-11e9-9b7e-3417eb9c0f80"))
  10. .and(dSystemUser.status.eq(1))
  11. .resultType(SystemUser.class)
  12. .fetch();

在上面查询定义中,我们查询用户角色为367c8078-a1f1-11e9-9b7e-3417eb9c0f80的用户列表,并且用户的状态为1,在leftJoin方法内有两个动态实体的字段,这两个字段则是在表内存在主外键关系的(当然不存在主外键关系我们也可以进行关联查询)。

leftJoin方法参数分别代表了主外键的字段配置,具体配置内容如下所示:

  1. // 可以配置多个leftJoin进行关联多表查询
  2. leftJoin(主表.主键,从表.外键);

上面查询代码对应生成SQL如下所示:

  1. SELECT iot_system_user.SU_ID, iot_system_user.SU_USER_NAME, iot_system_user.SU_NICK_NAME, iot_system_user.SU_PASSWORD, iot_system_user.SU_STATUS, iot_system_user.SU_CREATE_TIME, iot_system_user.SU_MARK FROM iot_system_user LEFT OUTER JOIN iot_system_user_role on iot_system_user_role.SUR_USER_ID = iot_system_user.SU_ID WHERE (iot_system_user_role.SUR_ROLE_ID = ? AND iot_system_user.SU_STATUS = ?)

10.4 自定义返回类型

有时我们在进行关联或者单表查询时,只需要获取其中的某一个、多个字段,这时我们就可以通过resultType方法来定义返回的类型,如果是基本数据类型的封装类型(如:IntegerLong等)可以直接返回,如果查询结果是非单列数值时,我们可以自定义返回类型来进行查询结果映射,简单示例如下所示:

  1. /**
  2. * 自定义系统用户数据转换实体
  3. *
  4. * @author:恒宇少年 - 于起宇
  5. * <p>
  6. * DateTime:2019-07-10 09:08
  7. * Blog:http://blog.yuqiyu.com
  8. * WebSite:http://www.jianshu.com/u/092df3f77bca
  9. * Gitee:https://gitee.com/hengboy
  10. * GitHub:https://github.com/hengboy
  11. */
  12. @Data
  13. public class SystemUserDTO {
  14. /**
  15. * 主键
  16. */
  17. @Column(name = "SU_ID")
  18. private String id;
  19. /**
  20. * 用户名
  21. */
  22. @Column(name = "SU_USER_NAME")
  23. private String userName;
  24. /**
  25. * 用户昵称
  26. */
  27. @Column(name = "SU_NICK_NAME")
  28. private String nickName;
  29. }

上面是我们定义的数据转换实体,用于接收查询数据表内的多个字段,查询代码如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. List<UserInfo> users = dslFactory.createSearchable()
  4. // 只查询id、userName、nickName对应的列值
  5. .select(dSystemUser.id, dSystemUser.userName, dSystemUser.nickName)
  6. // from table
  7. .from(dSystemUser)
  8. .where(dSystemUser.status.eq(1))
  9. // 自定义实体类类型定义
  10. .resultType(SystemUserDTO.class)
  11. .fetch();

上面查询代码对应生成的SQL如下所示:

  1. SELECT iot_system_user.SU_ID, iot_system_user.SU_USER_NAME, iot_system_user.SU_NICK_NAME FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?)

10.5 列值匹配实体类字段

ApiBoot Enhance在查询时,查询结果集的列值与实体类内的字段值对应关系是通过@Column注解的name属性来定的,如果查询结果集内的列名与实体类的字段名一致,那么@Column注解可以不配置,如果不一致需要在实体类字段上配置@Column(name='列名').

10.6 组装动态对象

ApiBoot Enhance支持动态组装查询对象Searchable,当我们根据业务逻辑进行判断时可以动态组装查询条件、关联表、返回值、查询字段等,简单示例如下所示:

  1. public List<SystemUser> assembleQuery(boolean isJoin) {
  2. // 系统用户动态查询对象
  3. DSystemUser dSystemUser = DSystemUser.DSL();
  4. // 创建Searchable查询对象
  5. Searchable searchable = dslFactory.createSearchable().selectFrom(dSystemUser)
  6. .where(dSystemUser.status.eq(1));
  7. // 根据参数组装关联查询
  8. if (isJoin) {
  9. // 系统用户角色关联查询对象
  10. DSystemUserRole dSystemUserRole = DSystemUserRole.DSL();
  11. searchable.leftJoin(dSystemUser.id, dSystemUserRole.userId);
  12. }
  13. // 设置返回值类型 & 查询数据
  14. return searchable.resultType(SystemUser.class).fetch();
  15. }

上面查询代码中只是根据参数组装了leftJoin关联表,ApiBoot Enhance的组装动态查询还待开发者挖掘。

10.7 Count函数使用

ApiBoot Enhance的动态查询不仅仅可以做数据查询,也可以做数据统计,使用count函数以及查询条件就可以实现复杂的数据统计功能,示例如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. // 系统用户角色动态查询实体
  4. DSystemUserRole dSystemUserRole = DSystemUserRole.DSL();
  5. Long count = dslFactory.createSearchable()
  6. // 建议使用主键统计,主键索引效率会有显著提升
  7. .count(dSystemUser.id)
  8. .from(dSystemUser)
  9. .leftJoin(dSystemUser.id, dSystemUserRole.userId)
  10. .where(dSystemUser.status.eq(1))
  11. // 统计的结果类型
  12. .resultType(Long.class)
  13. // 查询单个结果
  14. .fetchOne();

数据统计的查询条件跟数据查询完全一致,可以进行单表、多表关联、组装查询条件等。上面数据统计动态查询生成的SQL如下所示:

  1. SELECT count(iot_system_user.SU_ID) SU_ID FROM iot_system_user LEFT OUTER JOIN iot_system_user_role on iot_system_user_role.SUR_USER_ID = iot_system_user.SU_ID WHERE (iot_system_user.SU_STATUS = ?)

10.8 Avg函数使用

ApiBoot Enhance还支持针对某一个字段的平均值查询,使用avg函数以及查询条件可以实现复杂的平均值计算,示例如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. Integer avgAge = dslFactory.createSearchable()
  4. // 查询用户年龄平均值
  5. .avg(dSystemUser.age)
  6. .from(dSystemUser)
  7. // 用户状态为1
  8. .where(dSystemUser.status.eq(1))
  9. // 返回值类型为Integer,根据age对应表内字段而定
  10. .resultType(Integer.class)
  11. // 查询单个结果
  12. .fetchOne();

上面动态查询生成的SQL,如下所示:

  1. SELECT avg(iot_system_user.SU_AGE) SU_AGE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?)

10.9 Sum函数使用

ApiBoot Enhance支持针对某一个字段的总和查询,使用sum函数以及查询条件可以实现复杂的总和计算,示例如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. Long sum = dslFactory.createSearchable()
  4. // 查询用户年龄总和
  5. .sum(dSystemUser.age)
  6. .from(dSystemUser)
  7. // 用户状态为1
  8. .where(dSystemUser.status.eq(1))
  9. // 返回值类型为Integer,根据age对应表内字段而定
  10. .resultType(Long.class)
  11. // 查询单个结果
  12. .fetchOne();

上面动态查询生成的SQL,如下所示:

  1. SELECT sum(iot_system_user.SU_AGE) SU_AGE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?)

10.10 Min函数使用

ApiBoot Enhance支持针对某一个字段的最小值查询,使用min函数以及查询条件可以实现复杂查询指定字段的最小值计算,示例如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. Integer min = dslFactory.createSearchable()
  4. // 查询最小年龄的用户
  5. .min(dSystemUser.age)
  6. .from(dSystemUser)
  7. // 用户状态为1
  8. .where(dSystemUser.status.eq(1))
  9. // 返回值类型为Integer,根据age对应表内字段而定
  10. .resultType(Integer.class)
  11. // 查询单个结果
  12. .fetchOne();

上面动态查询生成的SQL,如下所示:

  1. SELECT min(iot_system_user.SU_AGE) SU_AGE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?)

10.11 Max函数使用

ApiBoot Enhance支持针对某一个字段的最大值查询,使用max函数以及查询条件可以实现指定字段复杂查询的最大值计算,示例如下所示:

  1. // 系统用户动态查询实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. Integer max = dslFactory.createSearchable()
  4. // 查询最大年龄的用户
  5. .max(dSystemUser.age)
  6. .from(dSystemUser)
  7. // 用户状态为1
  8. .where(dSystemUser.status.eq(1))
  9. // 返回值类型为Integer,根据age对应表内字段而定
  10. .resultType(Integer.class)
  11. // 查询单个结果
  12. .fetchOne();

上面动态查询生成的SQL,如下所示:

  1. SELECT max(iot_system_user.SU_AGE) SU_AGE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?)

11. 动态更新

EnhanceMapper接口内提供了update方法可以更新整个对象,如果我们只更新其中某一个、多个字段时update方法无法满足我们的需求,而ApiBoot Enhance DSL的动态更新可以完成这一需求,示例如下所示:

  1. // 系统用户动态实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. dslFactory.createUpdateable()
  4. // 需要更新的表对应的动态实体
  5. .update(dSystemUser)
  6. // 更新age对应列值 = 25
  7. .set(SetFilter.set(dSystemUser.age, 25))
  8. // 更新mark对应列值 = 备注信息
  9. .set(SetFilter.set(dSystemUser.mark, "备注信息"))
  10. // 指定更新用户
  11. .where(dSystemUser.id.eq("58eea57e-a1f1-11e9-9b7e-3417eb9c0f80"))
  12. // 执行更新
  13. .execute();

上面动态更新语句对应生成的SQL,如下所示:

  1. -- sql
  2. Preparing: UPDATE iot_system_user SET iot_system_user.SU_AGE = ? , iot_system_user.SU_MARK = ? WHERE (iot_system_user.SU_ID = ?)
  3. -- 参数
  4. Parameters: 25(Integer), 备注信息(String), 58eea57e-a1f1-11e9-9b7e-3417eb9c0f80(String)

12. 动态删除

EnhanceMapper接口提供的delete方法只可以根据主键操作数据,我们通过ApiBoot Enhnace DSL可以完成指定条件筛选删除,示例如下所示:

  1. // 系统用户动态实体
  2. DSystemUser dSystemUser = DSystemUser.DSL();
  3. dslFactory.createDeleteable()
  4. // 删除动态实体对应表内的数据
  5. .delete(dSystemUser)
  6. // status对应列值 = 1
  7. .where(dSystemUser.status.eq(1))
  8. // age对应列值 > 25
  9. .and(dSystemUser.age.gt(25))
  10. // 执行删除
  11. .execute();

上面动态删除语句对应生成的SQL,如下所示:

  1. -- sql
  2. Preparing: DELETE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ? AND iot_system_user.SU_AGE > ?)
  3. -- 参数
  4. Parameters: 1(Integer), 25(Integer)

13. 排序

ApiBoot Enhance动态查询支持根据查询字段进行正序倒序两种方式进行排序,如下所示:

// 系统用户动态实体
DSystemUser dSystemUser = DSystemUser.DSL();
List<SystemUser> users = dslFactory.createSearchable()
        .selectFrom(dSystemUser)
        // 查询status字段对应列值 = 1
        .where(dSystemUser.status.eq(1))
        // 根据age字段对应列值,正序排序,年龄从小到大排序
        .orderBy(dSystemUser.age, SortEnum.ASC)
        .resultType(SystemUser.class)
        .fetch();

SortEnum是一个排序的枚举,参考值如下所示:

  • ASC:正序
  • DESC:倒序上面动态查询排序语句对应生成的SQL,如下所示:
SELECT iot_system_user.SU_ID, iot_system_user.SU_USER_NAME, iot_system_user.SU_NICK_NAME, iot_system_user.SU_AGE, iot_system_user.SU_PASSWORD, iot_system_user.SU_STATUS, iot_system_user.SU_CREATE_TIME, iot_system_user.SU_MARK FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?) ORDER BY iot_system_user.SU_AGE ASC

14. 分页

ApiBoot Enhance 在使用动态查询时可以对结果集进行分页,简单示例如下所示:

// 系统用户动态实体
DSystemUser dSystemUser = DSystemUser.DSL();
List<SystemUser> users = dslFactory.createSearchable()
        .selectFrom(dSystemUser)
        // 查询status字段对应列值 = 1
        .where(dSystemUser.status.eq(1))
        // 根据age字段对应列值,正序排序,年龄从小到大排序
        .orderBy(dSystemUser.age, SortEnum.ASC)
        // 分页的开始位置,0是第一条
        .offset(0)
        // 每页查询20条
        .limit(20)
        .resultType(SystemUser.class)
        .fetch();

上面动态查询系统用户表内status=1的用户列表,并且根据age字段进行ASC方式排序,查询出0-20条数据。

ApiBoot Enhance的动态分页提供了两个函数,分别是:offsetlimit

  • offset():分页开始位置,从0开始
  • limit():每页查询记录数量上面分页动态查询对应生成的SQL,如下所示:
-- sql
Preparing: SELECT iot_system_user.SU_ID, iot_system_user.SU_USER_NAME, iot_system_user.SU_NICK_NAME, iot_system_user.SU_AGE, iot_system_user.SU_PASSWORD, iot_system_user.SU_STATUS, iot_system_user.SU_CREATE_TIME, iot_system_user.SU_MARK FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?) ORDER BY iot_system_user.SU_AGE ASC limit ?,?
-- 参数
Parameters: 1(Integer), 0(Integer), 20(Integer)

15. 分组

ApiBoot Enhance支持对指定字段查询分组,示例如下所示:

// 系统用户动态实体
DSystemUser dSystemUser = DSystemUser.DSL();
List<SystemUser> users = dslFactory.createSearchable()
        // 查询分组的字段
        // 最小年龄
        .select(dSystemUser.userName, dSystemUser.age.min())
        .from(dSystemUser)
        // 查询status字段对应列值 = 1
        .where(dSystemUser.status.eq(1))
        // 根据userName字段对应的列分组
        .groupBy(dSystemUser.userName)
        .resultType(SystemUser.class)
        .fetch();

在上面动态查询中,根据userName对应的列值进行分组。

上面分页动态查询对应生成的SQL,如下所示:

-- sql
Preparing: SELECT iot_system_user.SU_USER_NAME, min(iot_system_user.SU_AGE) SU_AGE FROM iot_system_user WHERE (iot_system_user.SU_STATUS = ?) GROUP BY iot_system_user.SU_USER_NAME
-- 参数
Parameters: 1(Integer)

16. 方法规则

ApiBoot Enhance 提供的方法规则查询不同于动态查询,并不需要编写过多的代码就可以实现多条件查询单条、多条数据,只需要通过在Mapper接口内根据方法命名的规则来生成对应的SqlSource以及Statement

16.1 正则表达式匹配方法名

方法命名规则内部是通过正则表达式进行提取查询的字段。

查询方法命名规则前缀为:findBy,根据某一个字段查询时可以写成findByXxx,如果根据多个字段则写成findByXxxAndXxx,当然也可以根据Or关键字进行查询,注意完全遵循驼峰命名方式来声明方法的名称,方法的返回值必须是声明方法内Mapper对应数据实体类型,可以是单个对象、也可以是List<对象>

删除方法命名规则前缀为:removeBy,根据某一个字段删除数据时可以写成remoteByXxx,如果根据多个字段则可以写成remoteByXxxAndXxx,删除方法没有返回值。

统计方法命名规则前缀为:countBy,根据主键进行统计数据,根据某一个字段作为查询条件可以成countByXxx,如果根据多个字段则可以写成countByXxxAndXxx,方法返回值为Long类型。

注意:方法命名规则的关联关系可以使用And也可以使用Or,尽量排除两种关系字符存在一个方法名称上。

16.2 规则条件查询数据

通过下面方法命名规则定义的方法来查看ApiBoot Enhance对应给生成的查询语句:

public interface SystemUserMapper extends EnhanceMapper<SystemUser, String> {
    /**
     * 根据userName查询单条记录
     *
     * @param userName
     * @return
     */
    SystemUser findByUserName(@Param("userName") String userName);

    /**
     * 根据userName and status 查询
     *
     * @param userName
     * @param userStatus
     * @return
     */
    SystemUser findByUserNameAndStatus(@Param("userName") String userName, @Param("status") Integer userStatus);

    /**
     * 根据status查询多条记录
     *
     * @param userStatus
     * @return
     */
    List<SystemUser> findByStatus(@Param("status") Integer userStatus);
}

方法规则查询有局限性,并不如动态查询灵活。

对应上面查询方法生成的SQL,如下所示:

-- findByUserName
SELECT SU_ID, SU_USER_NAME, SU_NICK_NAME, SU_AGE, SU_PASSWORD, SU_STATUS, SU_CREATE_TIME, SU_MARK FROM IOT_SYSTEM_USER WHERE SU_USER_NAME = ?

-- findByUserNameAndStatus
SELECT SU_ID, SU_USER_NAME, SU_NICK_NAME, SU_AGE, SU_PASSWORD, SU_STATUS, SU_CREATE_TIME, SU_MARK FROM IOT_SYSTEM_USER WHERE SU_USER_NAME = ? AND SU_STATUS = ?

-- findByStatus
SELECT SU_ID, SU_USER_NAME, SU_NICK_NAME, SU_AGE, SU_PASSWORD, SU_STATUS, SU_CREATE_TIME, SU_MARK FROM IOT_SYSTEM_USER WHERE SU_STATUS = ?

16.3 规则条件统计数据

ApiBoot Enhance 提供的方法命名规则统计,默认统计主键列目前无法修改默认方式,方法命名规则统计数据方法名定义方式如下所示:

public interface SystemUserMapper extends EnhanceMapper<SystemUser, String> {
    //...
    /**
     * 根据状态统计
     *
     * @param status
     * @return
     */
    Long countByStatus(@Param("status") Integer status);

    /**
     * 根据用户名 and 状态统计
     *
     * @param userName
     * @param userStatus
     * @return
     */
    Long countByUserNameAndStatus(@Param("userName") String userName, @Param("status") Integer userStatus);
}

对应上面统计方法生成的SQL,如下所示:

-- countByStatus
SELECT COUNT(SU_ID) FROM IOT_SYSTEM_USER WHERE SU_STATUS = ?

-- countByUserNameAndStatus
SELECT COUNT(SU_ID) FROM IOT_SYSTEM_USER WHERE SU_USER_NAME = ? AND SU_STATUS = ?

16.4 规则条件删除数据

方法名称规则删除示例如下所示:

public interface SystemUserMapper extends EnhanceMapper<SystemUser, String> {
    /**
     * 根据状态删除
     *
     * @param status
     */
    void removeByStatus(@Param("status") Integer status);

    /**
     * 根据用户名 and 状态删除
     *
     * @param userName
     * @param userStatus
     */
    void removeByUserNameAndStatus(@Param("userName") String userName, @Param("status") Integer userStatus);
}

对应上面删除方法生成的SQL,如下所示:

-- removeByStatus
DELETE FROM IOT_SYSTEM_USER WHERE SU_STATUS = ?

-- removeByUserNameAndStatus
DELETE FROM IOT_SYSTEM_USER WHERE SU_USER_NAME = ? AND SU_STATUS = ?