| name | ez-m-querydsl-sql-ext |
| description | querydsl-sql-extension 框架编程指南。提供初始化、实体定义、CRUD 等常见业务操作的代码模板。触发词:querydsl、querydsl-sql-extension、SQLQueryFactory、GenericRepository、ConfigurationEx。 |
| version | 1.0.0 |
| inclusion | manual |
querydsl-sql-extension 编程指南
querydsl-sql-extension 是基于 QueryDSL SQL 的增强框架,提供无反射高性能访问、注解驱动元数据、纯 POJO 模式(无需 Q 类)、Spring 集成等能力。
<dependency>
<groupId>io.github.xuse</groupId>
<artifactId>querydsl-sql-extension</artifactId>
<version>5.0.0-r172</version>
</dependency>
<dependency>
<groupId>io.github.xuse</groupId>
<artifactId>querydsl-sql-extension-spring</artifactId>
<version>5.0.0-r172</version>
</dependency>
1. 初始化
Spring 环境
@Bean
public com.github.xuse.querydsl.sql.SQLQueryFactory factory(DataSource ds) {
return QueryDSLSqlExtension.createSpringQueryFactory(ds, querydslConfiguration());
}
private ConfigurationEx querydslConfiguration() {
SQLTemplates templates = new MySQLWithJSONTemplates();
ConfigurationEx configuration = new ConfigurationEx(templates);
configuration.addListener(new QueryDSLSQLListener(QueryDSLSQLListener.FORMAT_DEBUG));
configuration.addListener(new UpdateDeleteProtectListener());
configuration.setSlowSqlWarnMillis(200);
configuration.scanPackages("com.example.entity");
return configuration;
}
非 Spring 环境
ConfigurationEx configuration = new ConfigurationEx(SQLQueryFactory.calcSQLTemplate(jdbcUrl));
configuration.addListener(new QueryDSLSQLListener(QueryDSLSQLListener.FORMAT_COMPACT));
SQLQueryFactory factory = new SQLQueryFactory(configuration, dataSource, true);
SQLQueryFactory 是线程安全的,作为单例使用。生产环境日志用 FORMAT_COMPACT。
2. 实体定义
直接在 POJO 上加注解,无需生成 Q 类:
@Data
@TableSpec(name = "user_info", primaryKeys = "id",
keys = { @Key(path = {"email"}, type = ConstraintType.UNIQUE) })
@Comment("用户信息表")
public class UserInfo {
@ColumnSpec(autoIncrement = true, unsigned = true, nullable = false)
private int id;
@ColumnSpec(size = 64, nullable = false)
private String name;
@ColumnSpec(size = 128, nullable = false, defaultValue = "''")
private String email;
@AutoGenerated(GeneratedType.CREATED_TIMESTAMP)
private Date created;
@AutoGenerated(GeneratedType.UPDATED_TIMESTAMP)
private Date updated;
@UnsavedValue(UnsavedValue.ZeroAndMinus)
@ColumnSpec(nullable = false, defaultValue = "0")
private int status;
}
常用注解速查
| 注解 | 位置 | 用途 |
|---|
@TableSpec | 类 | 表名、主键、索引、约束 |
@ColumnSpec | 字段 | 列名、类型、长度、nullable、默认值、自增 |
@Comment | 类/字段 | 表或列注释 |
@AutoGenerated | 字段 | 自动填充(CREATED_TIMESTAMP / UPDATED_TIMESTAMP / SNOWFLAKE) |
@UnsavedValue | 字段 | 原始类型无效值判定(ZeroAndMinus / Zero / MinusNumber) |
@CustomType | 字段 | 自定义序列化(需包扫描),如 JSON、加密、枚举映射 |
@CustomType 示例
@CustomType(JSONObjectType.class)
private MyObject data;
@CustomType(AESEncryptedField.class)
private String phone;
@CustomType(EnumByCodeType.class)
private Gender gender;
3. CRUD 操作
3.1 Repository 风格(推荐日常业务使用)
CRUDRepository<UserInfo, Integer> repo = factory.asRepository(() -> UserInfo.class);
基本操作
repo.insert(user);
repo.insertBatch(userList);
UserInfo user = repo.load(1);
List<UserInfo> users = repo.loadBatch(Arrays.asList(1, 2, 3));
repo.update(1, user);
repo.delete(1);
Lambda 查询
List<UserInfo> list = repo.query()
.eq(UserInfo::getName, "John")
.between(UserInfo::getCreated, startTime, endTime)
.orderByAsc(UserInfo::getId)
.fetch();
Pair<Integer, List<UserInfo>> page = repo.query()
.eq(UserInfo::getStatus, 1)
.findAndCount(20, 0);
@ConditionBean 查询(Web 分页场景)
@Data
@ConditionBean(limitField = "limit", offsetField = "offset")
public class UserQuery {
@Condition(Ops.STRING_CONTAINS)
private String name;
@Condition(Ops.BETWEEN)
private Date[] created;
@Condition(Ops.EQ)
private Integer status;
private Integer limit;
private Integer offset;
}
UserQuery params = new UserQuery();
params.setName("John");
params.setLimit(20);
Pair<Integer, List<UserInfo>> result = factory.findByCondition(params);
3.2 SQLQueryFactory 风格(复杂查询 / 多表关联)
需要 Q 类。Q 类继承 RelationalPathBaseEx,构造器中调用 scanClassMetadata()。
QUserInfo t = QUserInfo.userInfo;
Integer id = factory.insert(t).populate(bean).executeWithKey(Integer.class);
UserInfo user = factory.selectFrom(t).where(t.id.eq(id)).fetchOne();
factory.update(t)
.set(t.name, "Jane")
.set(t.updated, Expressions.currentTimestamp())
.where(t.id.eq(id))
.execute();
factory.update(t).populateWithCompare(newBean, oldBean).where(t.id.eq(id)).execute();
factory.delete(t).where(t.id.eq(id)).execute();
QDepartment t2 = QDepartment.department;
List<Tuple> rows = factory.select(t.name, t2.deptName)
.from(t).innerJoin(t2).on(t.deptId.eq(t2.id))
.where(t.status.eq(1))
.fetch();
大数据量查询调优
List<Integer> ids = factory.select(t.id).from(t)
.setFetchSize(10000)
.setMaxRows(1_000_000)
.setQueryTimeout(15)
.fetch();
4. 自定义 Repository
@Repository
public class UserRepository extends GenericRepository<UserInfo, Integer> {
public List<UserInfo> findActiveByDept(int deptId) {
QUserInfo t = QUserInfo.userInfo;
return getFactory().selectFrom(t)
.where(t.status.eq(1).and(t.deptId.eq(deptId)))
.orderBy(t.created.desc())
.fetch();
}
}
5. 从数据库生成实体映射
需要额外引入 codegen 模块:
<dependency>
<groupId>io.github.xuse</groupId>
<artifactId>querydsl-sql-extension-codegen</artifactId>
<version>5.0.0-r172</version>
</dependency>
生成代码
import io.github.xuse.querydsl.sql.code.generate.DbSchemaGenerator;
import io.github.xuse.querydsl.sql.code.generate.model.OutputDir;
import io.github.xuse.querydsl.sql.code.generate.model.MetafieldGenerationType;
DataSource ds = createDataSource();
DbSchemaGenerator.from(ds)
.packageName("com.example.entity")
.output(OutputDir.DIR_MAIN)
.metafields(MetafieldGenerationType.LAMBDA)
.useLombokAnnotation(true)
.overwriteFiles(false)
.generateTables(null, "%");
API 说明
| 方法 | 用途 |
|---|
packageName(String) | 生成类的 Java 包名 |
output(OutputDir) | 输出目录:DIR_MAIN(src/main/java)、DIR_TEST(src/test/java)、DIR_TARGET(target/generated-sources) |
metafields(MetafieldGenerationType) | NONE=仅 POJO,QCLASS=生成 Q 类,LAMBDA=在 POJO 中生成静态列引用常量 |
useLombokAnnotation(boolean) | 是否使用 Lombok @Data |
overwriteFiles(boolean) | 是否覆盖已有文件 |
generateTable(String) | 生成单张表 |
generateTables(namespace, pattern) | 按模式匹配生成,如 "user%" 生成 user 开头的表 |
generateAll(databaseName) | 生成整个库/Schema 的所有表 |
autoFieldOfCreateTime(String...) | 自动识别为创建时间的字段名(默认 created, createTime) |
autoFieldOfUpdateTime(String...) | 自动识别为更新时间的字段名(默认 updated, updateTime) |
交互场景
场景:用户希望从数据库生成实体映射
当用户表达"生成数据库映射"、"从数据库生成实体"、"生成表对应的 Java 类"等意图时,按以下流程操作:
-
收集连接信息:向用户询问以下信息(如未提供):
- JDBC URL(如
jdbc:mysql://localhost:3306/mydb)
- 数据库用户名
- 数据库密码
- 要生成的表(全部表 or 指定表名/模式)
-
确定输出位置:
- 检查当前工程的 Maven 模块结构,找到合适的实体包路径
- 如果工程中已有实体类,使用相同的包名
- 如果没有,建议使用
{项目基础包}.entity 作为包名
- 确认输出到
src/main/java/ 下
-
确定生成选项:
- 默认使用
MetafieldGenerationType.LAMBDA(纯 POJO + 静态列引用)
- 默认启用 Lombok
- 默认不覆盖已有文件
-
生成代码:在工程的 src/test/java 下创建一个一次性的生成器类并运行:
public class CodeGenerator {
public static void main(String[] args) {
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("{用户提供的 JDBC URL}");
ds.setUsername("{用户提供的用户名}");
ds.setPassword("{用户提供的密码}");
DbSchemaGenerator.from(ds)
.packageName("{确定的包名}")
.output(OutputDir.DIR_MAIN)
.metafields(MetafieldGenerationType.LAMBDA)
.useLombokAnnotation(true)
.overwriteFiles(false)
.generateTables(null, "%");
ds.close();
}
}
- 后续引导:生成完成后,提醒用户:
- 检查生成的实体类,按需调整字段类型和注解
- 确保
ConfigurationEx.scanPackages() 包含生成类的包路径
- 如果工程 pom.xml 中尚未引入
querydsl-sql-extension,协助添加依赖
代码生成规则
- 优先纯 POJO + Lambda 风格,除非用户要求 Q 类
- 初始化必须包含
scanPackages(),否则 @CustomType 等注解不生效
- Spring 项目用
QueryDSLSqlExtension.createSpringQueryFactory() 初始化
- NOT NULL 列映射原始类型,配合
@UnsavedValue
- SQLQueryFactory 作为单例,不要每次请求创建