Skip to content

AGENTS.md — Huiche 数据访问框架 AI 使用指南

本文件供 AI 编程助手阅读。目标:让 AI 在不了解 huiche 的情况下,能快速、正确地调用 huiche 提供的功能和方法完成业务开发(增删改查、查询、数据填充、扩展等),少踩坑、不臆造 API。

AI 核心原则:huiche 的 API 表面不大但重载很多。遇到不确定的方法签名,优先查 Javadocs 或本文件,不要凭名字臆测参数顺序。 本文件已收录高频 API,但重载组合无法穷尽。


0. TL;DR — 最短上手路径

  1. 引入依赖 org.huiche:huiche-spring-boot-starter(需 Java 21+、Spring Boot 4.x,并提供数据库驱动 + jdbc-url)。
  2. 实体类用普通 class 或 record,按命名策略自动映射表/列;标识主键,给属性加 @Column(primaryKey = true)
  3. 为每个实体类写一个 @Repository class XxxDao extends EntityDao<Xxx> {}(空类即可)。
  4. 在业务类里 @Autowired private XxxDao dao;,调用 dao.insert/update/deleteById/list/getById/page/count/exists/...
  5. 复杂查询用带 Consumer<Select> customizer 参数的重载,链式 .select().join().on().where().groupBy().orderBy()
  6. 把查询条件封装成对象时,实现 Searchable 接口 + @Search 注解,或实现 Search 接口手写 conditions()
  7. 仍不够用 → 注入 JdbcExecutor,传原生 SQL 或 Sql.select(...) 构造的 AST 语句。
java
// 最小可运行示例
@Autowired ArtistDao dao;

// 插入
dao.insert(new Artist().setName("张三"));
// 按主键查询
Artist a = dao.getById(1L);
// 条件查询 + 关联,直接映射到 VO
List<SongVO> vos = dao.listAs(SongVO.class, select -> select
    .select(Song::id, Song::title)
    .select(Column.of(Artist::name).as("artistName"))
    .join(Song.class).on(Song::artistId,Artist::id));

1. 项目身份

是什么huiche 是一个 Java ORM/数据访问框架,直接封装 JDBC,无外部依赖(反射用 MethodHandle + 缓存)
当前版本4.0.5(Maven Central:org.huiche:huiche
最低环境Java 21;Spring Boot 最低 4.x(可不依赖 Spring 独立使用)
历史版本1.x/2.x 基于 querydsl(已废弃);3.x/4.x 自研 AST,4.x 是 3.x 彻底重构
包坐标org.huiche 系列(core / ast / dao / dialect / jdbc / apt / ddl / spring-boot-starter / bom)
仓库Github(flagged,暂无法访问),Gitee(暂时为主),Maven Central 发布

2. 依赖与安装

Maven(Spring Boot 场景,推荐用 BOM 统一版本)

xml
<dependencies>
    <dependency>
        <groupId>org.huiche</groupId>
        <artifactId>huiche-spring-boot-starter</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.huiche</groupId>
            <artifactId>huiche-bom</artifactId>
            <version>${huiche.version}</version> <!-- 例如 4.0.5 -->
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

引入 starter 后仍需自行添加数据库驱动并配置 spring.datasource 的 jdbc-url 等。huiche 不含驱动。 若不用 Spring Boot,可单独依赖 huiche-dao(传递依赖 dialect/jdbc/ast/core),自行构造 Configuration

可选模块

模块用途何时引入
huiche-apt注解处理器,给 @Table 实体生成 Q 元数据文件想用列常量 QArtist.ARTIST.name 且不依赖 lombok 时
huiche-ddlDDL 生成与比较一般仅开发期生成/同步表结构,线上不需要

huiche-apt 用法见 §13;optional=true 且需在 maven-compiler-pluginannotationProcessorPaths 中声明。


3. 核心概念与模块边界

模块清单:huiche-core(注解/配置/支撑,用户直接用 org.huiche.core.annotation.*)、huiche-ast(SQL 语句/操作符/函数的 AST 封装)、huiche-dialect(把 AST 翻译为各数据库原生 SQL)、huiche-jdbc(底层 JDBC 执行器 JdbcExecutor)、huiche-dao(DAO 操作封装,用户调用最多)、huiche-spring-boot-starter(Spring 集成)。

三个 API 层次(自上而下,能解决就别下沉)

入口适用场景
1. EntityDao / Daodao.insert/list/page/...90% 业务 CRUD
2. Consumer<Select> customizerdao.list(select -> select.join(...).where(...))关联、分组、指定列、子查询等"稍高级"查询
3. JdbcExecutorexecutor.sql(sql, params).query(...)原生 SQL 或 AST 语句,自定义行映射

关键类型定位

类型全限定名作用
Daoorg.huiche.dao.Dao通用 DAO(ActiveRecord 风格,首参传 Class<T>
EntityDao<T>org.huiche.dao.EntityDao绑定单个实体类的 DAO,无需传 Class
JdbcExecutororg.huiche.jdbc.JdbcExecutor底层执行器,starter 自动注册为 bean
Select/Insert/Update/Deleteorg.huiche.ast.statement.*SQL 语句 AST
Column/Tableorg.huiche.ast.metadata.*列/表元数据,最常用 Column.of(...)
Expression/Conditionorg.huiche.ast.expression.*表达式/条件
Sqlorg.huiche.ast.support.Sql静态工具类,建语句/调函数/拼条件(可 import static
Pageable/PageReq/Pageorg.huiche.dao.*(参考 Javadocs)分页参数与结果,base-1(首页=1)
Searchableorg.huiche.core.support.Searchable标记接口,注解/规则式筛选对象
Search(接口)org.huiche.ast.support.Search手写条件式筛选对象
Operatororg.huiche.core.support.Operator筛选操作符枚举(EQ/LT/IN/BETWEEN...)
Generatororg.huiche.core.support.Generator值生成器,配合 @FillOnInsert/@FillOnUpdate
ColumnMapper<T>org.huiche.core.mapper.ColumnMapper自定义列类型映射
Listener(参考 Javadocs huiche-jdbc)SQL 执行监听器
DaoInterceptor(参考 Javadocs huiche.dao)DAO 方法拦截器
Configuration/ConfigurationBuilderorg.huiche.core.configuration.*配置项(仅由 Builder 创建)

注解统一在 org.huiche.core.annotation 包下:@Table@Column@TablePrefix@Ref@Search@FillOnInsert@FillOnUpdate@EnumVal


4. 定义实体类

基本规则

  • 普通类或 record 均可。record 不可变 → 无法回填自增值/填充值到对象(但数据库层照常生效)。
  • 表名/列名默认按 DefaultNamingStrategy 自动转换:ArtistInfo → artist_infoartistId → artist_id,并去掉 VO/DTO/PO/DO 等后缀。
  • 自定义:@Table(value="...") 改表名;@Column(value="...") 改列名;@TablePrefix("sys_") 标在 package-info 上加表名前缀。
  • xxxById/ByIds 系列方法依赖唯一主键:用 @Column(primaryKey = true) 标注,或列名恰为 id 且无其他 primaryKey 字段。多个 primaryKey=true 即联合主键 → 不支持 ById/ByIds(查询/更新/删除都一样)。
java
// 普通类(可回填自增主键)
public class Artist {
    @Column(primaryKey = true)   // 主键
    private Long id;
    private String name;
    private Long createTime;
    private Long updateTime;
    // getter/setter(lombok @Data 或手写)
}

// record(无需回填时)
public record Artist(Long id, String name, Long createTime, Long updateTime, Long version) {}

实体类 DAO

java
@Repository
public class ArtistDao extends EntityDao<Artist> {}
  • 空类即可继承全部内置方法,且无需每次传 Artist.class
  • 也可直接注入通用 Dao@Autowired private Dao dao; 然后 dao.list(Artist.class)

5. 增删改(CUD)

命名规律:<op> = 实体类操作;<op>With = 用 VO/Req/DTO 按属性名匹配写入;<op>WithMap = 用 Map 写入;<op>Batch[With] = 批量;...ById / ...ByIds = 按主键。每个方法都有多个重载。

5.1 插入

方法说明
insert(entity)插入实体;普通类会回填数据库自增值到对象;record/VO 不回填
insertWith(voOrReq)把 VO/Req 按属性名匹配插入实体表
insertWithMap(map)用 Map 插入
insertBatch(list) / insertBatchWith(list)批量插入,单条多值语句,默认 batchSize=500,不回填自增主键

VO/Req 写入限制(AI 必记)

  • 不会回填任何值到 Java 对象(含自增、含 @FillOnInsert 回填),但不影响数据库层值生成与填充。
  • 相比实体类多余的属性被忽略(即便非 null);缺少的属性若实体类有 @FillOnInsert 仍会插入,否则 NOT NULL 字段会报错。
  • 不读取 VO/Req 上的 @FillOnInsert,只认实体类上的。

5.2 更新

通用规则:

  • 默认忽略值为 null 的属性(不更新)。要显式置某列为 null → 用 Consumer<Update> 形式 .setNull(...)
  • 更新不回填到 Java 对象;但 @FillOnUpdate 照常填充到数据库。
  • update/updateWith/updateWithMap(无 ById)默认拦截无条件更新,必须传条件;特殊情况传 Condition.TRUE 跳过拦截(不会拼进 SQL)。
方法说明
update(entity 或 Consumer<Update>, condition)条件更新
updateById(entity 或 Consumer<Update>, id)按主键更新
updateByIds(..., ids...)多主键批量更新
updateWith / updateByIdWith / updateByIdsWith用 VO/Req 更新(同前述限制)
updateWithMap / updateByIdWithMap / updateByIdsWithMap用 Map 更新
java
// 置空 name
dao.update(update -> update.setNull(Artist::getName)
        .where(Column.of(Artist::getId).eq(1L)));
// 版本号 +1(数据库侧运算)
dao.update(update -> update
        .set(Artist::getVersion, Column.of(Artist::getVersion).plus(1))
        .where(Column.of(Artist::getId).eq(1L)));

5.3 删除

方法说明
delete(condition)条件删除,默认拦截无条件删除,传 Condition.TRUE 可跳过
deleteById(id)按主键删除
deleteByIds(ids...)多主键删除

拦截开关:配置项 huiche.block-unconditional-delete=true(默认 true),更新同理 block-unconditional-update


6. 查询(R)

命名规律:get/optional/getColumn/exists = 单条/单值(底层 setMaxRows(1),需自行确保至多一条);list/listColumn/page/stream = 多条。带 As 的可指定返回类型;带 ById/ByIds 的按主键。多数方法都有 Consumer<Select> customizer 重载做高级查询。

方法族返回变体
countlongcount / countColumn(忽略该列 NULL)/ countColumnDistinct
existsbooleanexists(condition) / existsById(id)
getColumn单列单值(支持泛型 TypeRefgetColumn / getColumnById
get单条实体get / getById / getAs(Class) / getByIdAs(Class)
listColumnList<列类型>listColumn / listColumnByIds
listList<实体或Class>list / listByIds / listAs(Class) / listByIdsAs(Class)
optionalOptional<...>optional/optionalById/optionalAs/optionalByIdAs/optionalColumn/optionalColumnById
pagePage<...>page(Pageable) / pageAs(Class, Pageable);内部先 count 后 list,count=0 不查 list
streamStream<...>stream / streamAs(Class);结果含 Reader/InputStream 等流时必须用 stream 并在流操作内读完
java
List<Artist> list   = dao.list();
long count          = dao.count();
Page<Artist> p      = dao.page(Pageable.of(1, 10));   // 首页=1,每页10
boolean has         = dao.existsById(3L);
Artist one          = dao.getById(4L);
Optional<Artist> o  = dao.optionalById(6L);
List<String> names  = dao.listColumn(Artist::getName);
String name5        = dao.getColumnById(Artist::getName, 5L);
Stream<Artist> st   = dao.stream();

分页约定(AI 注意)

  • Pageable.of(page, size) 返回 PageReqbase-1(首页是 1 不是 0)。要 base-0 须自行实现 Pageable
  • 官方建议:接口设计上分别提供 countlist 而非 page,由前端控制减少调用count次数(大表 count 慢,如换页时无需重新调用count),且便于 list 改用游标条件替代 offset。

7. 高级查询:Consumer<Select> customizer

大部分查询方法都有 xxx(Consumer<Select> customizer) 重载,用于指定列、关联、分组、排序、子查询等。

java
// 多表关联,直接映射到 VO
public record SongVO(Long id, String title, String artistName) {}

List<SongVO> vos = dao.listAs(SongVO.class, select -> select
    .select(Song::id, Song::title)                       // lambda 列(受语法限制,目前单次 select 只能同一实体,但可多次select来选取更多实体类的列)
    .select(Column.of(Artist::name).as("artistName"))    // 别名匹配 VO 属性
    .join(Artist.class)
    .on(Column.of(Song::artistId).eq(Column.of(Artist::id))));
    // .on(Song::artistId,Artist::id) // on提供一个传入2个lambda列的重载,当作eq的join条件

关键 API(链式)

  • .select(selection...):可传 Column、lambda 方法引用 Song::id、lombok Song.Fields.title、APT QArtist.ARTIST.name、实体 Class(等价 table.*)、SQL 函数 Sql.count()、子查询等。
  • .join / leftJoin / rightJoin / fullJoin / crossJoin(Class) + .on(condition):关联。
  • .where(condition...) / .where(searchObject) / .where(List<Condition>):传 null 会被忽略。
  • .groupBy(col...) + .having(condition...)
  • .orderBy(col.desc()) / .orderBy(col.asc()):可直接用 selections 中的别名,如 Column.of("songCount").desc()
  • .limit(n) / .offset(n):分页方言自动适配 LIMIT ? OFFSET ?OFFSET ... ROWS FETCH NEXT ...

@Ref 处理同名列 / 嵌套结果

java
// 同名列用 @Ref 指定来源
public record SongVO(Long id,
    @Ref(entity = Song.class) String title,
    @Ref(entity = Album.class, field = "title") String albumTitle,
    @Ref(entity = Artist.class, field = "name") String artistName) {}

// 嵌套对象
public record SongVO(Long id, String title,
    @Ref(nested = true) Album album,
    @Ref(nested = true) Artist artist) {}
// 对应 select(...).select(Album.class).select(Artist.class).join(...)

选中实体 Class(如 .select(Album.class))= table.*;一旦在 customizer 里调过 .select(...),dao 默认的 SELECT table.* 就不再自动追加。


8. 筛选对象(把查询条件封装成对象)

方式 A:注解/规则式(实现 org.huiche.core.support.Searchable 标记接口)

两种规则可混用:

  1. @Search(operator = Operator.XX) 标注属性;属性名当作字段名(按命名策略转换)。
  2. 属性名后缀指定操作符:age_LTage_GTEcreateTime_BETWEEN 等,去掉后缀即字段名。
java
public class UserSearch implements Searchable {
    @Search(operator = Operator.LT)   private Integer age;       // age < ?
    private Integer age_GTE;                                      // age >= ?
    // getter/setter
}
// 值 age=25, age_GTE=20 → age >= 20 AND age < 25

@Search 属性:

  • operator:必填,org.huiche.core.support.Operator 枚举。
  • entity / table:多表关联时指定表前缀(二选一,建议 entity)。生成的 SQL 变 table.column
  • field:指定真实字段名(应对"同字段多条件",如 createTimeMin/createTimeMax 都映射 createTime)。
  • expression:静态表达式(如 UPPER(name)),优先级最高,忽略 entity/table/field,需自行保证数据库兼容。
  • operator = Operator.IGNORED:跳过自动解析,由你手动读取该属性值并 .where(...) 拼条件。

操作符速查Operator 枚举):EQ/GT/GTE/LT/LTE/NEIS_NULL/IS_NOT_NULLIN/NOT_INBETWEEN/NOT_BETWEEN(取前 2 个)、LIKE/NOT_LIKESTART_WITH/END_WITH/CONTAINS 及其 NOT 变体、REGEXP_LIKE(不建议前端传)、IGNOREDAUTO(默认=EQ)。

  • IN/NOT_IN/BETWEEN 要求值为 Collection/数组, 分隔字符串(值含 , 不可用)。
  • LIKE/CONTAINS 系列用 String.valueOf() 读字符串。
java
public record ArtistSearch(Long id,
    @Search(operator = Operator.EQ) String title,
    @Search(operator = Operator.BETWEEN) List<Long> createTime) {}
// → id = ? AND title = ? AND create_time BETWEEN ? AND ?

方式 B:手写条件式(实现 org.huiche.ast.support.Search 接口)

java
public record ArtistSearch(Long id, String name, String keyword) implements Search {
    @Override public List<Condition> conditions() {
        return Arrays.asList(
            Sql.condition(id, QArtist.ARTIST.id::eq),          // 值无效返回 null,自动跳过
            Sql.condition(name, QArtist.ARTIST.name::contains),
            Sql.keyword(keyword, QArtist.ARTIST.name, QArtist.ARTIST.stageName, QArtist.ARTIST.bio)
        );
    }
}

Sql.condition(value, Function<Column, Condition>)Sql.keyword(keyword, columns...) 是内置工具:值无效(null/空串/空集合)返回 null,运行时自动跳过。

应用到查询

java
dao.list(search);                                 // 直接传
dao.list(select -> select.where(search));         // 在 customizer 里
List<Condition> cs = Sql.parseSearch(search);      // 注解式可解析为条件列表
dao.list(cs);
dao.list(select -> select.where(cs));

// 处理 IGNORED 字段:手动读取并拼条件
dao.list(select -> select
    .where(search)
    .where(Sql.keyword(search.keyword(), Artist::name, Artist::stageName, Artist::bio)));

9. 数据填充(自动填充创建时间/操作人/版本号等)

依赖 @FillOnInsert / @FillOnUpdate 注解 + org.huiche.core.support.Generator 实现。

步骤

  1. 实现生成器,注册为 bean(starter 自动加进配置):
java
@Component
public class TimeGenerator implements Generator {
    @Override public Object generateOnInsert(ColumnMetadata col, Object cur, Object src) { return System.currentTimeMillis(); }
    @Override public Object generateOnUpdate(ColumnMetadata col, Object cur, Object src) { return System.currentTimeMillis(); }
}
  1. 给实体属性标注:
java
public class Song {
    @Column(primaryKey = true)
    @FillOnInsert(UUIDGenerator.class)
    private String id;
    @FillOnInsert(TimeGenerator.class)
    private Long createTime;
    @FillOnInsert(TimeGenerator.class)
    @FillOnUpdate(TimeGenerator.class)
    private Long updateTime;
}

行为要点(AI 必记)

  • insert 系列:填充值到数据库并回填到实体对象(record/VO 例外不回填,但 DB 层照填)。
  • update 系列:填充到数据库,不回填到对象。
  • @FillOnInsert 默认 force=false(仅当属性为 null 时填充);@FillOnUpdate 默认 force=true(强制覆盖原值)。可改 force 调整。
  • 生成器返回 Object允许返回 Expression,如 generateOnUpdate 返回 Column.of(Song::getVersion).plus(1) 实现"每次更新版本 +1"。
  • 返回 Expression无法回填到对象,必须把 @FillOnInsertpopulate 设为 false
  • 只对实体类上的注解生效;VO/Req 上的同名注解被忽略。

10. SQL 构建工具 Sql(AST 层)

org.huiche.ast.support.Sql 全是 static 方法,建议 import static org.huiche.ast.support.Sql.*

语句构造

方法产出典型
Sql.select(selection...)Select.from(...).join(...).on(...).where(...).groupBy(...).having(...).orderBy(...).limit().offset()
Sql.insertInto(Class)Insert.columns(...).values(...);支持 INSERT ... SELECT
Sql.deleteFrom(Class)Delete.where(...)
Sql.update(Class)Update.set(col, val).where(...)
Sql.with(cte)CTE任意语句前加 WITH [RECURSIVE] cte AS (...)
集合运算Select 之间union() / intersect() / except() + all()

Column.of(...) 创建列(最常用)

  • Column.of("field"):字符串当属性解析(会做命名转换 artistName → artist_name)。
  • Column.of("table", "field")table 当表名(不转换),field 当属性(转换)。
  • Column.of(Entity::getField):lambda 方法引用,解析类/属性为表名/列名。
  • Column.of(Entity.Fields.field):lombok @FieldNameConstants(asEnum=true) 枚举,解析类/属性。
  • Column.ofStatic("table","col"):两者均不转换。

列上链式操作符(返回 ConditionExpression

.eq / .ne / .gt / .gte / .lt / .lte.between / .notBetween.isNull / .isNotNull.in / .notIn(支持传 Select 当子查询)、.like / .notLike.startWith / .endWith / .contains 及 NOT 变体、.plus / .minus / .mul / .div.and / .or.as("alias")

  • Sql.exists(select) / Sql.notExists(select) / Sql.not(condition)

内置函数(Sql.xxx,方言自动适配)

count / min / max / sum / avg / anyValue / stringAgg / stringAggDistinct(聚合); abs / ceiling / floor / round / mod(数值); concat / lower / upper / trim / replace / substring / charLength / octetLength / position(字符串); coalesce / nullIf(空值); jsonExists / jsonValue(JSON,$ 路径); regexpLike(正则,慎用); row()(行构造器)、paren()(括号)、case_(...).when().then().end() / caseWhen(...)(CASE)。

Literal.of(value) 字面量

明文替换进 SQL(不转 ?),支持基本类型/包装类/String/Number/null→NULLselect 里传普通字符串会被当列解析,要当字面量必须用 Literal.of() 包裹。

函数/操作符在各数据库行为可能不同(如 CONCAT 在 Oracle 只支持 2 参),详见参考文档,自定义函数用模板表达式(§11)。


11. 自定义 SQL(Expression / Condition 模板)

当内置函数/操作符不够时:

java
// 静态表达式:原样放入 SQL,不做处理
Expression e1 = Expression.of("UPPER(name)");

// 模板表达式:{} 占位,params 依次替换
Expression e2 = Expression.of("POSITION({} IN {})", sub, col);
// 动态不定个数参数:用 ListableSegment.of() 包裹
Expression e3 = Expression.of("{} IN({})",
    Column.of(Artist::name), ListableSegment.of("张三", "李四"));

// 静态/模板条件(可用于 where / on)
Condition c1 = Condition.of("a > b");
Condition c2 = Condition.of("{} > {}", colA, colB);

自定义 SQL 时用户需自行确保数据库兼容Function 本质就是 Expression,定义函数直接用模板表达式即可。


12. 自定义查询:JdbcExecutor

starter 自动注册 JdbcExecutor 为 bean,注入即用。支持原生 SQL(带 params)或 AST Statement

java
@Autowired private JdbcExecutor executor;

// 写
executor.sql(sql, params).update();
executor.sql(sql, params).updateAndPopulateKey();        // 多行自增
executor.sql(sql, params).updateAndPopulateKeySingle();   // 单行自增

// 存在查询
executor.sql(sql, params).queryExists();                  // 直接 rs.next()

// 读(返回 MappedQuery,惰性,进一步操作才真正查询)
MappedQuery<T> mq = executor.sql(sql, params).query(RowMapper<T> mapper);
MappedQuery<T> mq = executor.sql(sql, params).query(Class<T> targetClass);
MappedQuery<Map<String,Object>> mq = executor.sql(sql, params).query();        // key=列名转属性名
MappedQuery<Map<String,Object>> mq = executor.sql(sql, params).query(keyNamingStrategy);

List<T> list = mq.list();
Set<T> set   = mq.set();
Stream<T> st = mq.stream();   // 结果含流属性时必须用 stream 并读完
T first     = mq.first();     // 底层 setMaxRows(1)
Optional<T> o = mq.optional();

AST 语句同样可交给 JdbcExecutor

java
Insert insert = Sql.insertInto(Artist.class).columns(xxx).values(xxx);
Delete delete = Sql.deleteFrom(Artist.class).where(xxx);
Update update = Sql.update(Artist.class).set(xxx, xxx).where(xxx);
Select select = Sql.select(xxx).from(Artist.class).join(xxx).on(xxx).orderBy(xxx).limit(xxx);
executor.sql(insert).updateAndPopulateKeySingle();
executor.sql(select).query(Artist.class).list();

13. 注解处理器 huiche-apt(可选)

生成元数据 Q 文件(QArtist.ARTIST.name 等),不依赖 lombok。

  1. 加依赖(optional=true)并在 maven-compiler-pluginannotationProcessorPaths 声明;spring-boot 打包时排除。
  2. 实体类加 @Table没有 @Table 不会被处理)。
  3. mvn compiletarget/generated-sources/annotations/ 生成 QArtist
java
@Generated("org.huiche.apt.TableGenerator")
public class QArtist extends Table.Default {
    public static final QArtist ARTIST = new QArtist(Table.of(Artist.class).name());
    public final Column id;
    public final Column name;
    public QArtist(String name) { super(name); this.id = Column.of(name, "id"); this.name = Column.of(name, "name"); }
}

huiche-apt.config(resources 下,Properties 格式):package(默认 table,相对实体包父级)、prefix(默认 Q)、suffix(默认 "")。

用了自定义 NamingStrategy 时,需将其所在模块声明到 maven-compiler-plugin 的依赖里,APT 才能读到。


14. 注解速查(全在 org.huiche.core.annotation

注解位置关键属性用途
@Table实体类value 表名、database 上级、comment 注释自定义表名/启用 APT
@Column属性value 列名、primaryKeyautoIncrement(仅主键)、type/length/scale/unique/nullable/comment/defaultValue/definition(仅 DDL)主键声明、DDL 生成
@TablePrefixpackage-infovalue 前缀包级表名前缀
@RefVO/DTO 属性entity/table(二选一)、fieldnested多表同名列/嵌套结果映射
@Search筛选对象属性operator(必填)、entity/tablefieldexpression比较符/多表关联/自定义表达式
@FillOnInsert实体属性value=Generator、force(默认 false)、populate插入时填充(不覆盖非空值)
@FillOnUpdate实体属性value=Generator、force(默认 true)、populate更新时填充(默认覆盖)
@EnumVal枚举类value=EnumBy枚举取值方式(优先级最高)

15. 类型映射(java ↔ jdbc,单向)

单向读写:仅由 Java 侧类型决定读写方式,与数据库实际类型无关(只要支持对应读写)。 读取用 ResultSet 方法,写入用 PreparedStatement 方法。基本类型读 NULL 返回默认值(0/false/'\0')。

  • 数值:boolean/byte/short/int/long/float/double 及包装类 → getXxx/setXxxBigDecimal/BigIntegergetBigDecimal
  • 文本:String/StringBuffer/StringBuilder/Currency/Locale/Path/ZoneId/UUIDgetStringuseNational=true 时用 getNString)。
  • 时间:Date/Instant/LocalDateTime/OffsetDateTime/ZonedDateTime/TimestampgetTimestampLocalDategetDateLocalTimegetTimeDurationgetLong
  • 二进制/流:byte[]getBytesInputStreamgetBinaryStreamReadergetCharacterStream
  • 枚举:@EnumVal > 全局 enumBy > 自动识别(实现 IntSupplier 为 VALUE,否则 NAME)。NAME 用 String,VALUE 用 Int,ORDINAL 强烈不推荐。
  • 其他类型:兜底用序列化转换器(需启用,starter 默认用 jackson):先 getString 再反序列化,写入先序列化再 setString。可存 List<String>、JSON 等到 JSON/CLOB/VARCHAR
  • Optional<?>:按实际泛型类型读写。

自定义覆盖:实现 org.huiche.core.mapper.ColumnMapper<T>,注册为 bean(starter 自动加进配置)。


16. 配置项(Spring Boot)

application.ymlhuiche.* 前缀:

参数默认说明
huiche.enum-bynull(自动识别)NAME/ORDINAL/VALUE
huiche.null-to-emptytrue集合/Map/数组为 null 时转空
huiche.use-national取决于方言影响 getNString/getString(一般不设)
huiche.batch-size500批量插入每批条数
huiche.enable-serialization-mappertrue需 classpath 有 jackson-databind(v3) 才生效
huiche.block-unconditional-deletetrue拦截无条件删除(Condition.TRUE 可跳过)
huiche.block-unconditional-updatetrue拦截无条件更新
huiche.result-initial-capacity20集合结果初始容量(建议对齐最小每页数)
huiche.result-initial-capacity-max2000初始容量上限(超出回退自动扩容)
huiche.slow-sql-threshold毫秒,≥阈值输出 WARN(logger=SlowSQLListener)
huiche.enable-sql-logfalsesql=DEBUG / params=TRACE(logger=SQLLogListener)
huiche.plain-sql-logfalse输出明文 SQL(需先开 sql-log,开启后不再记 params)
spring.jdbc.template.fetch-size / max-rows / query-timeout-1spring-jdbc 配置,huiche 会读取

声明 bean 替代/扩展默认

注册为 bean 即自动生效:Dialect(替换方言)、Listener(追加监听器,不替代配置项加的)、SerializationMapper(替换默认 jackson)、ColumnMapper(追加类型映射)、Generator(启用填充)。 更细控制实现 ConfigurationCustomizer

多数据源HuicheAutoConfiguration 仅在单一 DataSource bean 时生效(@ConditionalOnSingleCandidate(DataSource.class))。多数据源需自行注册 JdbcExecutor/Dao,并像 EntityDaoBeanPostProcessor 那样为每个 EntityDaosetDao


17. 扩展点:监听器与拦截器

SQL 监听器 Listener(监控执行流程)

java
@Component
public class CustomListener implements Listener {
    // 复写需要的监听事件方法
}

starter 中注册为 bean 即自动启用。可做日志、慢 SQL 记录等。

DAO 拦截器 DaoInterceptor(改写内置方法行为)

java
// 伪删除:把 Delete 改写为 Update
@Component
public class LogicalDeleteInterceptor implements DaoInterceptor {
    @Override
    public Statement beforeDelete(Class<?> entityClass, Statement statement) {
        if (Artist.class.isAssignableFrom(entityClass) && statement instanceof Delete delete) {
            return Sql.update(entityClass).set("isDel", true).where(delete.wheres());
        }
        return statement;
    }
}

18. 数据库兼容

内置支持(以 LTS/最新版为主,EOL 版不额外支持,但多数语法仍可用):

  • 主流:MySQL 9.7 / PostgreSQL 18 / Oracle 26ai / SQL Server 2025 / MariaDB 12.3 / DB2 12.1。
  • 国产:OceanBase CE 4.4.2(仅 MySQL 模式)、达梦 v8。
  • 嵌入式:H2 2.4 / HSQL 2.7.3 / SQLite 3.53.2(Derby 已 EOL,不再支持)。
  • 兼容 MySQL/PostgreSQL/Oracle 语法的数据库大多可用;非内置方言且 jdbc-url 协议自定义时,需在 Configuration 指定 Dialect

LIMIT ? OFFSET ? 分页方言自动适配 OFFSET ... ROWS FETCH NEXT ...,不支持 ROW_NUMBER(),故部分老数据库不被支持。


19. 命名策略

DefaultNamingStrategy(可用 Java SPI 替换):

  • 类名→表名:PascalCase→snake_case(ArtistInfo → artist_info)。
  • 属性→列名:camelCase→snake_case(artistId → artist_id)。
  • 去掉 AO/BO/DO/DTO/PO/VO 等后缀(ArtistVO → artist)。
  • 连续大写:最后一位当下个单词,前面转小写(SQLServer → sql_server)。

20. AI 高频陷阱(务必遵守)

以下为基于官方文档明确的限制与默认行为,AI 易错点已标注。

  1. xxxById/ByIds 需要单一主键:联合主键(多个 primaryKey=true)下不可用,会报错。生成 delete/update/query 的 ById 方法前,先确认实体有单一主键。
  2. record 实体不回填:自增值、@FillOnInsert 值不会写回 record 对象(DB 层照常)。需要回填主键/填充值时用普通类。
  3. VO/Req 写入不回填、忽略多余属性insertWith/updateWith 不会回填任何值;多余属性被忽略;缺失且无填充的 NOT NULL 列会报错;不读 VO 上的 @Fill* 注解。
  4. 更新默认忽略 null 属性:要置空某列必须用 Consumer<Update>.setNull(...),不能靠传 null。
  5. 无条件更新/删除默认被拦截update(condition)/delete(condition) 必须传条件;要强行全表操作传 Condition.TRUE
  6. 单条查询底层用了 setMaxRows(1)get/getColumn/optional/exists 只取第一条,需自行确保条件至多一条,否则静默返回首条。
  7. page 是 base-1Pageable.of(1, 10) 是首页。别从 0 开始。
  8. lambda 方法引用不支持父类属性BaseEntity::getId 会错误把 base_entity 当表名。父类属性用 Column.of 包裹或用 APT/lombok 枚举(lombok 需父类也标注,但同样取不到正确表名——建议直接下沉到 Column.ofQ 文件)。
  9. select 里的字符串默认当列解析:要传字符串字面量必须用 Literal.of()
  10. 流结果(Reader/InputStream)必须用 stream 查询并在 stream 操作内读完,之后再读无效。
  11. @FillOnUpdate 默认强制覆盖原值force=true),与 @FillOnInsertforce=false)相反;想仅 null 时填充要改 force
  12. 生成器返回 Expression 无法回填:用 generateOnInsert 返回表达式时,必须把 @FillOnInsert(populate=false)
  13. 枚举取值:优先 @EnumVal > 全局 enumBy > 自动识别;ORDINAL 强烈不推荐(调整枚举顺序会错乱)。
  14. 多数据源需手动配:starter 自动配置仅在单一 DataSource 时生效。
  15. 自定义 SQL/表达式需自行保证数据库兼容expression/静态 SQL 不做方言转换。
  16. 批量插入不回填自增主键insertBatch 系列明确不回填;需回填用 insert 单条或 executor.sql(...).updateAndPopulateKey[Single]()

21. 决策树:遇到需求该用哪个 API

要写数据?
├─ 单条实体且要拿回主键 → dao.insert(entity)(普通类)
├─ 用 Req/VO/Map 写入   → dao.insertWith / insertWithMap / updateByIdWith...
├─ 批量                 → dao.insertBatch / insertBatchWith(不回填主键)
└─ 需要数据库侧运算(版本+1等) → dao.update(update -> update.set(col, expr).where(...))

要读数据?
├─ 单条/单值            → getById / get / getColumnById / optional / existsById
├─ 多条                 → list / listByIds / listColumn / stream
├─ 分页                 → page(Pageable)(base-1);或分开提供 count + list
├─ 关联/分组/指定列     → 带 Consumer<Select> 重载,listAs(VO.class, select -> ...)
├─ 条件要前端可配置     → Searchable + @Search(自动解析)或 Search 接口(手写)
└─ 上述都不够           → JdbcExecutor + 原生 SQL / AST Sql.xxx 语句

要自动填充(创建时间/操作人/版本)?
  → 实现 Generator 注册 bean + 实体属性标 @FillOnInsert/@FillOnUpdate

要改写/拦截 dao 行为(逻辑删除等)?
  → 实现 DaoInterceptor(beforeDelete/beforeUpdate...),注册 bean
要监控 SQL 执行(日志/慢SQL)?
  → 实现 Listener 注册 bean,或开 huiche.enable-sql-log / slow-sql-threshold

要列常量?
  → 三选一:huiche-apt 生成 Q 文件(@Table 必填)/ lombok @FieldNameConstants / lambda 方法引用

22. 文档导航(按需深入)

最后更新: