# Provider 包说明文档 ## 概述 `com.poyee.base.mapper.provider` 包是一个用于动态构建 SQL 语句的工具包,它基于注解驱动,通过解析实体类和 DTO 上的注解来自动生成 SQL 查询语句。该包主要用于简化数据库操作,支持复杂的查询条件、连接查询、排序、分页等功能,使开发者能够以更加面向对象的方式编写数据库访问代码。 ## 核心类 ### BaseProvider `BaseProvider` 是整个包的核心抽象类,提供了基础的 SQL 构建功能,包括左连接和 WHERE 条件的处理。它维护了一系列线程安全的集合来存储 SQL 构建过程中的各种信息: ```java public abstract class BaseProvider { // 数据库操作类型 public DbMethodEnums dbMethod; // 表计数器 public int tableCount = 0; // 是否分页 public boolean isPage = false; // 存储 insert public Object insert; // 记录 UPDATE public Object update; // 记录 SELECT public Object select; // 主表信息 public TableInfo superTable; // 分页查询 public MPage mPage; // 标记是否前端传排序字段 public boolean isFrontSort = false; // 分页查询SQL public String pageCountSql; // 记录 INSERT key-value(线程安全) public final Map insertMap = new HashMap<>(); // 记录 UPDATE key-value(线程安全) public final Map updateMap = new HashMap<>(); // 存储表信息的映射(线程安全) public final Map tableInfos = new ConcurrentHashMap<>(); // 记录 WHERE 条件(线程安全) public final List whereInfos = new CopyOnWriteArrayList<>(); // 记录 SELECT 列(线程安全) public final List selectColumns = new CopyOnWriteArrayList<>(); // 记录 LEFT JOIN 信息(线程安全) public final List leftJoinInfos = new CopyOnWriteArrayList<>(); // 记录 ORDER BY 信息(线程安全) public final Map orderByMap = new HashMap<>(); // 各种SQL构建方法... } ``` `BaseProvider` 提供了以下主要方法: - `selectById`: 根据ID查询单条记录 - `selectOne`: 查询单条记录 - `selectSum`: 聚合查询(求和) - `save`: 插入记录 - `update`: 更新记录 - `pageWrapper`: 分页查询 - `leftJoinWrapper`: 左连接查询 - `mPage`: 分页查询(返回MPage对象) ### IBaseProvider `IBaseProvider` 是一个接口,定义了基础的数据库操作方法,并通过 MyBatis 的 `@SelectProvider`、`@InsertProvider` 和 `@UpdateProvider` 注解将这些方法与 `BaseProvider` 的实现关联起来。 ```java public interface IBaseProvider { @SelectProvider(type = FinalProviderSql.class, method = "leftJoinWrapper") List> selectListMap(@Param("req") T req, @Param("clazz") Class clazz); @SelectProvider(type = FinalProviderSql.class, method = "pageCountSql") long pageCount(String sql); @SelectProvider(type = FinalProviderSql.class, method = "pageSql") List> page(String sql); @SelectProvider(type = FinalProviderSql.class, method = "selectOne") Map selectOneMap(@Param("req") T req, @Param("clazz") Class clazz); @SelectProvider(type = FinalProviderSql.class, method = "selectById") Map selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class clazz); @SelectProvider(type = FinalProviderSql.class, method = "selectSum") Map selectSumMap(@Param("req") T req, @Param("clazz") Class clazz); @InsertProvider(type = FinalProviderSql.class, method = "save") @Options(useGeneratedKeys = true, keyProperty = "id") int save(T req); @UpdateProvider(type = FinalProviderSql.class, method = "update") int update(T req); // 默认方法实现... } ``` `IBaseProvider` 还提供了一些默认方法,如 `selectByPrimaryKey`、`selectOne`、`sum`、`selectList` 和 `selectPage` 等,这些方法封装了对 MyBatis 返回结果的处理逻辑。 ### 工具类 #### SqlUtil `SqlUtil` 是 SQL 生成工具类,负责将 `BaseProvider` 中收集的各种信息转换为最终的 SQL 字符串。它提供了以下主要方法: - `buildPage`: 构建分页查询 - `buildOne`: 构建单条记录查询 - `buildSum`: 构建聚合查询 - `toSql`: 生成最终的 SQL 字符串 #### SelectUtil `SelectUtil` 负责处理 SELECT 语句的构建,包括解析实体类上的注解、处理字段映射、构建查询条件等。 #### UpdateUtil `UpdateUtil` 负责处理 UPDATE 语句的构建,包括解析实体类上的注解、处理字段映射等。 #### InsertUtil `InsertUtil` 负责处理 INSERT 语句的构建,包括解析实体类上的注解、处理字段映射等。 #### JoinUtil `JoinUtil` 负责处理 JOIN 语句的构建,包括解析 `@LeftJoin` 注解、构建连接条件等。 #### WhereUtil `WhereUtil` 负责处理 WHERE 条件的构建,包括解析 `@Where` 注解、构建查询条件等。 #### TableInfoUtil `TableInfoUtil` 负责处理表信息的初始化,包括解析 `@TableName` 注解、构建表信息对象等。 ### 领域模型 #### TableInfo `TableInfo` 表示数据库表的信息,包括表名、别名、主键等。 ```java public class TableInfo { // 实体类名 private String className; // 实体类 private Class clazz; // 表名 private String tableName; // 别名 private String alias; // 索引 private Integer idx; // 主键 private String primaryKey; } ``` #### WhereInfo `WhereInfo` 表示 WHERE 条件的信息,包括表信息、字段名、操作符、值等。 ```java public class WhereInfo { // 表信息 private TableInfo tableInfo; // 字段 private String column; // or 条件 private List orColumn; // 值 private Object value; // 结束值 (between 时使用) private Object endValue; // 操作符 private FieldOperator operator; // 字段类型 private FieldType fieldType; } ``` #### LeftJoinInfo `LeftJoinInfo` 表示左连接的信息,包括表信息、关联表信息、关联条件等。 ```java public class LeftJoinInfo { // 表信息 private TableInfo tableInfo; // 关联表信息 private TableInfo leftTableInfo; // 关联条件 private String fieldName; // 关联字段 private String leftFieldName; // 别名 private String alias; } ``` #### MPage `MPage` 表示分页查询的信息,包括当前页、每页大小、总记录数等。 ```java public class MPage { // 当前页 private long current; // 每页大小 private long size; // 总记录数 private long total; // 记录列表 private List records; // 分页SQL private String pageSql; // 计数SQL private String countSql; // 排序信息 private List orders; // 是否优化计数SQL private boolean optimizeCountSql; // 是否查询总记录数 private boolean searchCount; // 是否优化连接查询的计数SQL private boolean optimizeJoinOfCountSql; } ``` ## 注解 ### @Select `@Select` 注解用于指定查询字段,可以应用在实体类或 DTO 的字段上。 ```java public @interface Select { // 表名 Class table(); // 别名 String alias() default ""; // 字段名 String fieldName() default ""; // 聚合等条件取值 CaseWhen[] caseWhen() default {}; // 是否去重 boolean distinct() default false; } ``` ### @Where `@Where` 注解用于指定查询条件,可以应用在实体类或 DTO 的字段上。 ```java public @interface Where { // 表名 Class table(); // 别名 String alias() default ""; // 字段名 String field() default ""; // 字段名数组(用于 OR 条件) String[] orFields() default {}; // 字段类型 FieldType columnType() default FieldType.STRING; // 操作符 FieldOperator operator() default FieldOperator.EQ; // 是否为空 boolean isNull() default false; // 是否结束(用于 BETWEEN 条件) boolean isEnd() default false; } ``` ### @LeftJoin `@LeftJoin` 注解用于指定左连接,可以应用在实体类或 DTO 的字段上。 ```java public @interface LeftJoin { // 关联表 Class table(); // 关联表(左表,主表) Class leftTable() default void.class; // 关联字段 String fieldName() default ""; // 关联字段(左表关联字段) String leftFieldName() default ""; } ``` ### @OrderBy `@OrderBy` 注解用于指定排序,可以应用在实体类或 DTO 的字段上。 ```java public @interface OrderBy { // 排序方式 OrderBySortEnums sortType() default OrderBySortEnums.ASC; // 顺序 int index() default 1; } ``` ### @Count `@Count` 注解用于指定计数查询,可以应用在实体类或 DTO 的字段上。 ```java public @interface Count { // 是否去重 boolean distinct() default false; // 条件 CaseWhen[] caseWhen() default {}; } ``` ### @Sum `@Sum` 注解用于指定求和查询,可以应用在实体类或 DTO 的字段上。 ```java public @interface Sum { // 条件 CaseWhen[] caseWhen() default {}; } ``` ## 使用示例 ### 1. 定义实体类和DTO ```java // 实体类 @Data @TableName("user") public class User { @TableId(value = "id", type = IdType.AUTO) private Long id; @TableField("name") private String name; @TableField("age") private Integer age; @TableField("email") private String email; @TableField("create_time") private Date createTime; } // 请求DTO @Data public class UserReq extends BaseReq { @Where(table = User.class, field = "name") private String name; @Where(table = User.class, field = "age", operator = FieldOperator.GE) private Integer minAge; @Where(table = User.class, field = "age", operator = FieldOperator.LE) private Integer maxAge; @Where(table = User.class, field = "email", operator = FieldOperator.LIKE) private String email; @LeftJoin(table = Department.class, leftTable = User.class, fieldName = "id", leftFieldName = "dept_id") private String departmentJoin; } // 响应DTO @Data public class UserDto extends BaseDto { private Long id; private String name; private Integer age; private String email; private Date createTime; @Select(table = Department.class, fieldName = "name") private String departmentName; @Count(distinct = true) @TableField("id") private Long userCount; @Sum @TableField("age") private Long totalAge; } ``` ### 2. 定义Mapper接口 ```java @Mapper public interface UserMapper extends IBaseMapper { // 可以添加自定义方法 } ``` ### 3. 使用示例 ```java @Service public class UserService extends BaseServiceImpl { @Autowired private UserMapper userMapper; // 查询单条记录 public UserDto getUser(Long id) { return userMapper.selectByPrimaryKey(id, UserDto.class); } // 条件查询 public List getUserList(UserReq req) { return userMapper.selectList(req, UserDto.class); } // 分页查询 public Page getUserPage(UserReq req) { return userMapper.selectPage(req, UserDto.class); } // 统计查询 public UserDto getUserStats(UserReq req) { return userMapper.sum(req, UserDto.class); } // 保存 public int saveUser(UserReq req) { return userMapper.save(req); } // 更新 public int updateUser(UserReq req) { return userMapper.update(req); } } ``` ## 高级用法 ### 1. 复杂条件查询 ```java @Data public class ComplexUserReq extends BaseReq { // 多字段OR条件 @Where(table = User.class, orFields = {"name", "email"}, operator = FieldOperator.LIKE) private String keyword; // BETWEEN条件 @Where(table = User.class, field = "create_time", operator = FieldOperator.BETWEEN) private Date startTime; @Where(table = User.class, field = "create_time", operator = FieldOperator.BETWEEN, isEnd = true) private Date endTime; // IN条件 @Where(table = User.class, field = "status", operator = FieldOperator.IN) private List statusList; } ``` ### 2. 多表关联查询 ```java @Data public class UserWithDeptReq extends BaseReq { @Where(table = User.class, field = "name", operator = FieldOperator.LIKE) private String userName; @LeftJoin(table = Department.class, leftTable = User.class, fieldName = "id", leftFieldName = "dept_id") private String deptJoin; @Where(table = Department.class, field = "name", operator = FieldOperator.LIKE) private String deptName; @LeftJoin(table = Company.class, leftTable = Department.class, fieldName = "id", leftFieldName = "company_id") private String companyJoin; @Where(table = Company.class, field = "name", operator = FieldOperator.LIKE) private String companyName; } @Data public class UserWithDeptDto extends BaseDto { private Long id; private String name; private Integer age; @Select(table = Department.class, fieldName = "name") private String departmentName; @Select(table = Company.class, fieldName = "name") private String companyName; } ``` ### 3. 聚合查询 ```java @Data public class UserStatsDto extends BaseDto { @Count(distinct = true) @TableField("id") private Long userCount; @Sum @TableField("age") private Long totalAge; @Select(table = Department.class, fieldName = "name") private String departmentName; // CASE WHEN 条件 @Sum(caseWhen = { @CaseWhen(when = "age > 30", then = "1", elseThen = "0") }) @TableField("id") private Long seniorUserCount; } ``` ## 注意事项 1. 所有的查询都需要传入 `Class clazz` 参数,用于指定返回的 DTO 类型。 2. 使用 `@Where` 注解时,需要指定 `table` 属性,用于确定查询条件所属的表。 3. 使用 `@LeftJoin` 注解时,需要指定 `table` 和 `leftTable` 属性,用于确定连接的两个表。 4. 使用 `@Select` 注解时,需要指定 `table` 和 `fieldName` 属性,用于确定查询的字段。 5. 使用 `@OrderBy` 注解时,可以指定 `sortType` 和 `index` 属性,用于确定排序方式和顺序。 6. 使用 `@Count` 和 `@Sum` 注解时,需要与 `@TableField` 注解一起使用,用于确定聚合的字段。 7. 使用 `@CaseWhen` 注解时,需要指定 `when` 和 `then` 属性,用于构建 CASE WHEN 语句。 ## 总结 `com.poyee.base.mapper.provider` 包提供了一种基于注解的方式来构建 SQL 语句,使开发者能够以更加面向对象的方式编写数据库访问代码。通过使用这些注解,开发者可以轻松实现复杂的查询条件、连接查询、排序、分页等功能,而无需手动编写 SQL 语句。 这种方式的优点是: 1. 代码更加简洁、易读、易维护 2. 减少了 SQL 注入的风险 3. 支持复杂的查询条件和连接查询 4. 与 MyBatis 和 MyBatis-Plus 无缝集成 缺点是: 1. 学习成本较高 2. 对于非常复杂的查询,可能不如直接编写 SQL 语句灵活 3. 性能可能略低于手写 SQL 语句 总体来说,这个包是一个非常有用的工具,特别适合于需要频繁进行数据库操作的项目。 ## 注意事项 1. **学习成本**:使用该包需要了解各种注解的用法和 SQL 构建的原理,学习成本较高。 2. **性能考虑**:动态构建 SQL 可能会带来一定的性能开销,对于性能要求极高的场景,可能需要直接使用原生 SQL。 3. **复杂查询**:对于非常复杂的查询,可能不如直接编写 SQL 语句灵活。 4. **调试难度**:动态构建的 SQL 可能难以调试,建议在开发环境中开启 SQL 日志。 5. **版本兼容性**:在升级 MyBatis 或数据库版本时,需要注意兼容性问题。 ## 性能优化建议 1. **表信息缓存**:实现表结构信息的缓存,避免重复解析表结构。 2. **SQL片段缓存**:对于频繁使用的SQL片段,可以实现缓存机制。 3. **批量操作**:使用批量插入和更新操作,减少数据库交互次数。 4. **索引优化**:确保在WHERE条件和JOIN条件中使用的字段上创建了适当的索引。 5. **查询优化**: - 只查询需要的列,避免使用`SELECT *` - 使用分页查询时,将COUNT查询和数据查询分离 - 对于大数据量的表,考虑使用分区表或分表策略 6. **连接查询优化**: - 减少连接表的数量,必要时使用子查询或分批查询 - 确保连接条件字段上有索引 7. **代码优化**: - 减少反射操作,可以使用缓存存储字段和注解信息 - 优化字符串拼接操作,使用StringBuilder而不是String连接 - 使用线程安全的集合类,避免并发问题 8. **SQL执行计划分析**: - 定期分析生成的SQL执行计划,找出性能瓶颈 - 对于复杂查询,考虑使用数据库视图或存储过程 9. **监控和日志**: - 实现SQL执行时间的监控和日志记录 - 对于执行时间超过阈值的SQL,进行告警和优化 10. **使用连接池**: - 配置合适的数据库连接池参数 - 监控连接池的使用情况,避免连接泄漏