AGENTS.md — Huiche 数据访问框架 AI 使用指南
本文件供 AI 编程助手阅读。目标:让 AI 在不了解 huiche 的情况下,能快速、正确地调用 huiche 提供的功能和方法完成业务开发(增删改查、查询、数据填充、扩展等),少踩坑、不臆造 API。
- 官网指南:https://huiche.org/guide
- 参考文档:https://huiche.org/reference
- Javadocs:https://javadocs.huiche.org
- 源码:https://gitee.com/parabola1307/huiche
- License:Apache License 2.0
AI 核心原则:huiche 的 API 表面不大但重载很多。遇到不确定的方法签名,优先查 Javadocs 或本文件,不要凭名字臆测参数顺序。 本文件已收录高频 API,但重载组合无法穷尽。
0. TL;DR — 最短上手路径
- 引入依赖
org.huiche:huiche-spring-boot-starter(需 Java 21+、Spring Boot 4.x,并提供数据库驱动 + jdbc-url)。 - 实体类用普通 class 或 record,按命名策略自动映射表/列;标识主键,给属性加
@Column(primaryKey = true)。 - 为每个实体类写一个
@Repository class XxxDao extends EntityDao<Xxx> {}(空类即可)。 - 在业务类里
@Autowired private XxxDao dao;,调用dao.insert/update/deleteById/list/getById/page/count/exists/...。 - 复杂查询用带
Consumer<Select> customizer参数的重载,链式.select().join().on().where().groupBy().orderBy()。 - 把查询条件封装成对象时,实现
Searchable接口 +@Search注解,或实现Search接口手写conditions()。 - 仍不够用 → 注入
JdbcExecutor,传原生 SQL 或Sql.select(...)构造的 AST 语句。
// 最小可运行示例
@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 统一版本)
<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-ddl | DDL 生成与比较 | 一般仅开发期生成/同步表结构,线上不需要 |
huiche-apt 用法见 §13;optional=true 且需在 maven-compiler-plugin 的 annotationProcessorPaths 中声明。
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 / Dao | dao.insert/list/page/... | 90% 业务 CRUD |
2. Consumer<Select> customizer | dao.list(select -> select.join(...).where(...)) | 关联、分组、指定列、子查询等"稍高级"查询 |
3. JdbcExecutor | executor.sql(sql, params).query(...) | 原生 SQL 或 AST 语句,自定义行映射 |
关键类型定位
| 类型 | 全限定名 | 作用 |
|---|---|---|
Dao | org.huiche.dao.Dao | 通用 DAO(ActiveRecord 风格,首参传 Class<T>) |
EntityDao<T> | org.huiche.dao.EntityDao | 绑定单个实体类的 DAO,无需传 Class |
JdbcExecutor | org.huiche.jdbc.JdbcExecutor | 底层执行器,starter 自动注册为 bean |
Select/Insert/Update/Delete | org.huiche.ast.statement.* | SQL 语句 AST |
Column/Table | org.huiche.ast.metadata.* | 列/表元数据,最常用 Column.of(...) |
Expression/Condition | org.huiche.ast.expression.* | 表达式/条件 |
Sql | org.huiche.ast.support.Sql | 静态工具类,建语句/调函数/拼条件(可 import static) |
Pageable/PageReq/Page | org.huiche.dao.*(参考 Javadocs) | 分页参数与结果,base-1(首页=1) |
Searchable | org.huiche.core.support.Searchable | 标记接口,注解/规则式筛选对象 |
Search(接口) | org.huiche.ast.support.Search | 手写条件式筛选对象 |
Operator | org.huiche.core.support.Operator | 筛选操作符枚举(EQ/LT/IN/BETWEEN...) |
Generator | org.huiche.core.support.Generator | 值生成器,配合 @FillOnInsert/@FillOnUpdate |
ColumnMapper<T> | org.huiche.core.mapper.ColumnMapper | 自定义列类型映射 |
Listener | (参考 Javadocs huiche-jdbc) | SQL 执行监听器 |
DaoInterceptor | (参考 Javadocs huiche.dao) | DAO 方法拦截器 |
Configuration/ConfigurationBuilder | org.huiche.core.configuration.* | 配置项(仅由 Builder 创建) |
注解统一在
org.huiche.core.annotation包下:@Table、@Column、@TablePrefix、@Ref、@Search、@FillOnInsert、@FillOnUpdate、@EnumVal。
4. 定义实体类
基本规则
- 普通类或
record均可。record 不可变 → 无法回填自增值/填充值到对象(但数据库层照常生效)。 - 表名/列名默认按
DefaultNamingStrategy自动转换:ArtistInfo → artist_info,artistId → 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(查询/更新/删除都一样)。
// 普通类(可回填自增主键)
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
@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 更新 |
// 置空 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重载做高级查询。
| 方法族 | 返回 | 变体 |
|---|---|---|
count | long | count / countColumn(忽略该列 NULL)/ countColumnDistinct |
exists | boolean | exists(condition) / existsById(id) |
getColumn | 单列单值(支持泛型 TypeRef) | getColumn / getColumnById |
get | 单条实体 | get / getById / getAs(Class) / getByIdAs(Class) |
listColumn | List<列类型> | listColumn / listColumnByIds |
list | List<实体或Class> | list / listByIds / listAs(Class) / listByIdsAs(Class) |
optional | Optional<...> | optional/optionalById/optionalAs/optionalByIdAs/optionalColumn/optionalColumnById |
page | Page<...> | page(Pageable) / pageAs(Class, Pageable);内部先 count 后 list,count=0 不查 list |
stream | Stream<...> | stream / streamAs(Class);结果含 Reader/InputStream 等流时必须用 stream 并在流操作内读完 |
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)返回PageReq,base-1(首页是 1 不是 0)。要 base-0 须自行实现Pageable。- 官方建议:接口设计上分别提供
count和list而非page,由前端控制减少调用count次数(大表 count 慢,如换页时无需重新调用count),且便于 list 改用游标条件替代 offset。
7. 高级查询:Consumer<Select> customizer
大部分查询方法都有
xxx(Consumer<Select> customizer)重载,用于指定列、关联、分组、排序、子查询等。
// 多表关联,直接映射到 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、lombokSong.Fields.title、APTQArtist.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 处理同名列 / 嵌套结果
// 同名列用 @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 标记接口)
两种规则可混用:
@Search(operator = Operator.XX)标注属性;属性名当作字段名(按命名策略转换)。- 属性名后缀指定操作符:
age_LT、age_GTE、createTime_BETWEEN等,去掉后缀即字段名。
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/NE、IS_NULL/IS_NOT_NULL、IN/NOT_IN、BETWEEN/NOT_BETWEEN(取前 2 个)、LIKE/NOT_LIKE、START_WITH/END_WITH/CONTAINS 及其 NOT 变体、REGEXP_LIKE(不建议前端传)、IGNORED、AUTO(默认=EQ)。
IN/NOT_IN/BETWEEN要求值为Collection/数组或,分隔字符串(值含,不可用)。LIKE/CONTAINS系列用String.valueOf()读字符串。
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 接口)
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,运行时自动跳过。
应用到查询
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 实现。
步骤
- 实现生成器,注册为 bean(starter 自动加进配置):
@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(); }
}- 给实体属性标注:
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时无法回填到对象,必须把@FillOnInsert的populate设为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"):两者均不转换。
列上链式操作符(返回 Condition 或 Expression)
.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→NULL。select 里传普通字符串会被当列解析,要当字面量必须用 Literal.of() 包裹。
函数/操作符在各数据库行为可能不同(如
CONCAT在 Oracle 只支持 2 参),详见参考文档,自定义函数用模板表达式(§11)。
11. 自定义 SQL(Expression / Condition 模板)
当内置函数/操作符不够时:
// 静态表达式:原样放入 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)或 ASTStatement。
@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:
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。
- 加依赖(
optional=true)并在maven-compiler-plugin的annotationProcessorPaths声明;spring-boot 打包时排除。 - 实体类加
@Table(没有 @Table 不会被处理)。 mvn compile→target/generated-sources/annotations/生成QArtist。
@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 列名、primaryKey、autoIncrement(仅主键)、type/length/scale/unique/nullable/comment/defaultValue/definition(仅 DDL) | 主键声明、DDL 生成 |
@TablePrefix | package-info | value 前缀 | 包级表名前缀 |
@Ref | VO/DTO 属性 | entity/table(二选一)、field、nested | 多表同名列/嵌套结果映射 |
@Search | 筛选对象属性 | operator(必填)、entity/table、field、expression | 比较符/多表关联/自定义表达式 |
@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/setXxx;BigDecimal/BigInteger→getBigDecimal。 - 文本:
String/StringBuffer/StringBuilder/Currency/Locale/Path/ZoneId/UUID→getString(useNational=true时用getNString)。 - 时间:
Date/Instant/LocalDateTime/OffsetDateTime/ZonedDateTime/Timestamp→getTimestamp;LocalDate→getDate;LocalTime→getTime;Duration→getLong。 - 二进制/流:
byte[]→getBytes;InputStream→getBinaryStream;Reader→getCharacterStream。 - 枚举:
@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.yml 下 huiche.* 前缀:
| 参数 | 默认 | 说明 |
|---|---|---|
huiche.enum-by | null(自动识别) | NAME/ORDINAL/VALUE |
huiche.null-to-empty | true | 集合/Map/数组为 null 时转空 |
huiche.use-national | 取决于方言 | 影响 getNString/getString(一般不设) |
huiche.batch-size | 500 | 批量插入每批条数 |
huiche.enable-serialization-mapper | true | 需 classpath 有 jackson-databind(v3) 才生效 |
huiche.block-unconditional-delete | true | 拦截无条件删除(Condition.TRUE 可跳过) |
huiche.block-unconditional-update | true | 拦截无条件更新 |
huiche.result-initial-capacity | 20 | 集合结果初始容量(建议对齐最小每页数) |
huiche.result-initial-capacity-max | 2000 | 初始容量上限(超出回退自动扩容) |
huiche.slow-sql-threshold | 无 | 毫秒,≥阈值输出 WARN(logger=SlowSQLListener) |
huiche.enable-sql-log | false | sql=DEBUG / params=TRACE(logger=SQLLogListener) |
huiche.plain-sql-log | false | 输出明文 SQL(需先开 sql-log,开启后不再记 params) |
spring.jdbc.template.fetch-size / max-rows / query-timeout | -1 | spring-jdbc 配置,huiche 会读取 |
声明 bean 替代/扩展默认
注册为 bean 即自动生效:Dialect(替换方言)、Listener(追加监听器,不替代配置项加的)、SerializationMapper(替换默认 jackson)、ColumnMapper(追加类型映射)、Generator(启用填充)。 更细控制实现 ConfigurationCustomizer。
多数据源:
HuicheAutoConfiguration仅在单一DataSourcebean 时生效(@ConditionalOnSingleCandidate(DataSource.class))。多数据源需自行注册JdbcExecutor/Dao,并像EntityDaoBeanPostProcessor那样为每个EntityDao调setDao。
17. 扩展点:监听器与拦截器
SQL 监听器 Listener(监控执行流程)
@Component
public class CustomListener implements Listener {
// 复写需要的监听事件方法
}starter 中注册为 bean 即自动启用。可做日志、慢 SQL 记录等。
DAO 拦截器 DaoInterceptor(改写内置方法行为)
// 伪删除:把 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 易错点已标注。
xxxById/ByIds需要单一主键:联合主键(多个primaryKey=true)下不可用,会报错。生成delete/update/query的 ById 方法前,先确认实体有单一主键。- record 实体不回填:自增值、
@FillOnInsert值不会写回 record 对象(DB 层照常)。需要回填主键/填充值时用普通类。 - VO/Req 写入不回填、忽略多余属性:
insertWith/updateWith不会回填任何值;多余属性被忽略;缺失且无填充的 NOT NULL 列会报错;不读 VO 上的@Fill*注解。 - 更新默认忽略 null 属性:要置空某列必须用
Consumer<Update>.setNull(...),不能靠传 null。 - 无条件更新/删除默认被拦截:
update(condition)/delete(condition)必须传条件;要强行全表操作传Condition.TRUE。 - 单条查询底层用了
setMaxRows(1):get/getColumn/optional/exists只取第一条,需自行确保条件至多一条,否则静默返回首条。 page是 base-1:Pageable.of(1, 10)是首页。别从 0 开始。- lambda 方法引用不支持父类属性:
BaseEntity::getId会错误把base_entity当表名。父类属性用Column.of包裹或用 APT/lombok 枚举(lombok 需父类也标注,但同样取不到正确表名——建议直接下沉到Column.of或Q文件)。 select里的字符串默认当列解析:要传字符串字面量必须用Literal.of()。- 流结果(
Reader/InputStream)必须用stream查询并在 stream 操作内读完,之后再读无效。 @FillOnUpdate默认强制覆盖原值(force=true),与@FillOnInsert(force=false)相反;想仅 null 时填充要改force。- 生成器返回
Expression无法回填:用generateOnInsert返回表达式时,必须把@FillOnInsert(populate=false)。 - 枚举取值:优先
@EnumVal> 全局enumBy> 自动识别;ORDINAL强烈不推荐(调整枚举顺序会错乱)。 - 多数据源需手动配:starter 自动配置仅在单一
DataSource时生效。 - 自定义 SQL/表达式需自行保证数据库兼容:
expression/静态 SQL 不做方言转换。 - 批量插入不回填自增主键:
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. 文档导航(按需深入)
- 指南:简介 · 快速开始 · 增删改查 · 数据操作 · 数据查询 · 高级查询 · 筛选对象 · 自定义查询 · 自定义SQL
- 进阶:数据填充 · 监听器 · 拦截器 · 命名策略 · 自定义列类型映射 · 注解处理器 · Native & AOT · 生成DDL
- 参考:模块说明 · 配置说明 · 内置注解 · 数据库兼容 · 命名策略 · 查询字段 · 筛选对象操作符 · 字段类型映射 · Spring集成 · SQL监听器 · DAO拦截器
- SQL AST:内置SQL语句 · 内置SQL函数 · 内置SQL操作符 · 内置SQL对象
- DDL:DDL说明 · DDL类型映射 · DDL语句
- API:Javadocs · 模块:
huiche.apthuiche.asthuiche.corehuiche.daohuiche.ddlhuiche.dialecthuiche.jdbchuiche.spring.boot.starter