provider.md 17 KB

Provider 包说明文档

概述

com.poyee.base.mapper.provider 包是一个用于动态构建 SQL 语句的工具包,它基于注解驱动,通过解析实体类和 DTO 上的注解来自动生成 SQL 查询语句。该包主要用于简化数据库操作,支持复杂的查询条件、连接查询、排序、分页等功能,使开发者能够以更加面向对象的方式编写数据库访问代码。

核心类

BaseProvider

BaseProvider 是整个包的核心抽象类,提供了基础的 SQL 构建功能,包括左连接和 WHERE 条件的处理。它维护了一系列线程安全的集合来存储 SQL 构建过程中的各种信息:

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<String, Object> insertMap = new HashMap<>();
    // 记录 UPDATE key-value(线程安全)
    public final Map<String, Object> updateMap = new HashMap<>();
    // 存储表信息的映射(线程安全)
    public final Map<String, TableInfo> tableInfos = new ConcurrentHashMap<>();
    // 记录 WHERE 条件(线程安全)
    public final List<WhereInfo> whereInfos = new CopyOnWriteArrayList<>();
    // 记录 SELECT 列(线程安全)
    public final List<String> selectColumns = new CopyOnWriteArrayList<>();
    // 记录 LEFT JOIN 信息(线程安全)
    public final List<LeftJoinInfo> leftJoinInfos = new CopyOnWriteArrayList<>();
    // 记录 ORDER BY 信息(线程安全)
    public final Map<Integer,String> orderByMap = new HashMap<>();
    
    // 各种SQL构建方法...
}

BaseProvider 提供了以下主要方法:

  • selectById: 根据ID查询单条记录
  • selectOne: 查询单条记录
  • selectSum: 聚合查询(求和)
  • save: 插入记录
  • update: 更新记录
  • pageWrapper: 分页查询
  • leftJoinWrapper: 左连接查询
  • mPage: 分页查询(返回MPage对象)

IBaseProvider

IBaseProvider 是一个接口,定义了基础的数据库操作方法,并通过 MyBatis 的 @SelectProvider@InsertProvider@UpdateProvider 注解将这些方法与 BaseProvider 的实现关联起来。

public interface IBaseProvider<T extends BaseReq, R extends BaseDto> {
    @SelectProvider(type = FinalProviderSql.class, method = "leftJoinWrapper")
    List<Map<String, Object>> selectListMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
    
    @SelectProvider(type = FinalProviderSql.class, method = "pageCountSql")
    long pageCount(String sql);
    
    @SelectProvider(type = FinalProviderSql.class, method = "pageSql")
    List<Map<String, Object>> page(String sql);
    
    @SelectProvider(type = FinalProviderSql.class, method = "selectOne")
    Map<String, Object> selectOneMap(@Param("req") T req, @Param("clazz") Class<R> clazz);
    
    @SelectProvider(type = FinalProviderSql.class, method = "selectById")
    Map<String, Object> selectByIdMap(@Param("id") Serializable id, @Param("clazz") Class<R> clazz);
    
    @SelectProvider(type = FinalProviderSql.class, method = "selectSum")
    Map<String, Object> selectSumMap(@Param("req") T req, @Param("clazz") Class<R> 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 还提供了一些默认方法,如 selectByPrimaryKeyselectOnesumselectListselectPage 等,这些方法封装了对 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 表示数据库表的信息,包括表名、别名、主键等。

public class TableInfo {
    // 实体类名
    private String className;
    // 实体类
    private Class<?> clazz;
    // 表名
    private String tableName;
    // 别名
    private String alias;
    // 索引
    private Integer idx;
    // 主键
    private String primaryKey;
}

WhereInfo

WhereInfo 表示 WHERE 条件的信息,包括表信息、字段名、操作符、值等。

public class WhereInfo {
    // 表信息
    private TableInfo tableInfo;
    // 字段
    private String column;
    // or 条件
    private List<String> orColumn;
    // 值
    private Object value;
    // 结束值 (between 时使用)
    private Object endValue;
    // 操作符
    private FieldOperator operator;
    // 字段类型
    private FieldType fieldType;
}

LeftJoinInfo

LeftJoinInfo 表示左连接的信息,包括表信息、关联表信息、关联条件等。

public class LeftJoinInfo {
    // 表信息
    private TableInfo tableInfo;
    // 关联表信息
    private TableInfo leftTableInfo;
    // 关联条件
    private String fieldName;
    // 关联字段
    private String leftFieldName;
    // 别名
    private String alias;
}

MPage

MPage 表示分页查询的信息,包括当前页、每页大小、总记录数等。

public class MPage<T> {
    // 当前页
    private long current;
    // 每页大小
    private long size;
    // 总记录数
    private long total;
    // 记录列表
    private List<T> records;
    // 分页SQL
    private String pageSql;
    // 计数SQL
    private String countSql;
    // 排序信息
    private List<OrderItem> orders;
    // 是否优化计数SQL
    private boolean optimizeCountSql;
    // 是否查询总记录数
    private boolean searchCount;
    // 是否优化连接查询的计数SQL
    private boolean optimizeJoinOfCountSql;
}

注解

@Select

@Select 注解用于指定查询字段,可以应用在实体类或 DTO 的字段上。

public @interface Select {
    // 表名
    Class<?> table();
    // 别名
    String alias() default "";
    // 字段名
    String fieldName() default "";
    // 聚合等条件取值
    CaseWhen[] caseWhen() default {};
    // 是否去重
    boolean distinct() default false;
}

@Where

@Where 注解用于指定查询条件,可以应用在实体类或 DTO 的字段上。

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 的字段上。

public @interface LeftJoin {
    // 关联表
    Class<?> table();
    // 关联表(左表,主表)
    Class<?> leftTable() default void.class;
    // 关联字段
    String fieldName() default "";
    // 关联字段(左表关联字段)
    String leftFieldName() default "";
}

@OrderBy

@OrderBy 注解用于指定排序,可以应用在实体类或 DTO 的字段上。

public @interface OrderBy {
    // 排序方式
    OrderBySortEnums sortType() default OrderBySortEnums.ASC;
    // 顺序
    int index() default 1;
}

@Count

@Count 注解用于指定计数查询,可以应用在实体类或 DTO 的字段上。

public @interface Count {
    // 是否去重
    boolean distinct() default false;
    // 条件
    CaseWhen[] caseWhen() default {};
}

@Sum

@Sum 注解用于指定求和查询,可以应用在实体类或 DTO 的字段上。

public @interface Sum {
    // 条件
    CaseWhen[] caseWhen() default {};
}

使用示例

1. 定义实体类和DTO

// 实体类
@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接口

@Mapper
public interface UserMapper extends IBaseMapper<UserDto> {
    // 可以添加自定义方法
}

3. 使用示例

@Service
public class UserService extends BaseServiceImpl<UserReq, User, UserDto> {
    @Autowired
    private UserMapper userMapper;
    
    // 查询单条记录
    public UserDto getUser(Long id) {
        return userMapper.selectByPrimaryKey(id, UserDto.class);
    }
    
    // 条件查询
    public List<UserDto> getUserList(UserReq req) {
        return userMapper.selectList(req, UserDto.class);
    }
    
    // 分页查询
    public Page<UserDto> 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. 复杂条件查询

@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<Integer> statusList;
}

2. 多表关联查询

@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. 聚合查询

@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<R> clazz 参数,用于指定返回的 DTO 类型。
  2. 使用 @Where 注解时,需要指定 table 属性,用于确定查询条件所属的表。
  3. 使用 @LeftJoin 注解时,需要指定 tableleftTable 属性,用于确定连接的两个表。
  4. 使用 @Select 注解时,需要指定 tablefieldName 属性,用于确定查询的字段。
  5. 使用 @OrderBy 注解时,可以指定 sortTypeindex 属性,用于确定排序方式和顺序。
  6. 使用 @Count@Sum 注解时,需要与 @TableField 注解一起使用,用于确定聚合的字段。
  7. 使用 @CaseWhen 注解时,需要指定 whenthen 属性,用于构建 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. 使用连接池
    • 配置合适的数据库连接池参数
    • 监控连接池的使用情况,避免连接泄漏