【性能革命】告别复杂SQL:easy-query 4.10让数据库操作效率提升300%的秘密
你还在为ORM框架的冗余代码和低下性能而烦恼吗?还在手写复杂SQL处理联表查询和分表逻辑吗?本文将带你全面了解dromara/easy-query——这款支持强类型、轻量级、高性能的Java ORM框架如何通过五大隐式特性彻底改变数据库操作方式,让你用最少的代码实现最复杂的查询逻辑。
读完本文你将获得:
- 掌握5种隐式查询特性的实战应用
- 学会在Spring Boot和控制台环境快速集成easy-query
- 理解分库分表的优雅实现方式
- 获得处理加密字段查询的最佳实践
- 通过20+代码示例彻底搞懂ORM性能优化技巧
🌟 什么是easy-query?
easy-query是一款由Dromara社区维护的Java/Kotlin ORM框架,专为解决JDBC查询痛点而设计。它采用强类型API设计,支持对象模型筛选、隐式子查询和隐式关联查询,同时兼顾OLTP和OLAP场景需求。与传统ORM相比,其核心优势在于零XML配置、隐式查询优化和高性能分库分表支持。
// 核心依赖(最新版本请查阅Maven Central)
<dependency>
<groupId>com.easy-query</groupId>
<artifactId>sql-springboot-starter</artifactId>
<version>4.10.0</version>
</dependency>
框架架构概览
🔥 五大隐式特性深度解析
1. 隐式关联查询(Implicit Join)
传统ORM实现一对一/多对一关联查询时需要显式配置关联关系并手动编写JOIN条件,而easy-query通过@Navigate注解和属性代理自动实现关联查询。
// 实体定义
@Data
@Table("t_sys_user")
@EntityProxy
public class SysUser implements ProxyEntityAvailable<SysUser, SysUserProxy> {
@Column(primaryKey = true)
private String id;
private String username;
@Navigate(value = RelationTypeEnum.OneToOne) // 声明一对一关联
private Company company; // 关联属性
}
// 查询代码
List<SysUser> users = easyEntityQuery.queryable(SysUser.class)
.where(user -> user.company().name().like("科技公司")) // 直接访问关联对象属性
.orderBy(user -> user.company().registerTime().desc()) // 关联属性排序
.toList();
生成的SQL:
SELECT t.`id`,t.`username`,t.`company_id`
FROM `t_sys_user` t
LEFT JOIN `t_company` t1 ON t.`company_id` = t1.`id`
WHERE t1.`name` LIKE '%科技公司%'
ORDER BY t1.`register_time` DESC
2. 隐式子查询(Implicit Subquery)
一对多和多对多关系中,传统方式需要手动编写子查询,而easy-query通过any()、all()等方法自动生成子查询。
List<Company> companies = entityQuery.queryable(Company.class)
.where(company -> {
// 自动生成EXISTS子查询
company.users().any(u -> u.name().like("张%"));
// 子查询聚合函数
company.users().where(u -> u.age().gt(18))
.count().gt(10);
}).toList();
3. 隐式分组(Implicit Grouping)
当对一对多关系进行多次子查询时,easy-query会自动优化合并为分组查询,减少数据库交互次数。
// 未优化前:2次子查询
// 优化后:1次分组查询
List<CompanyVO> result = entityQuery.queryable(Company.class)
.subQueryToGroupJoin(company -> company.users()) // 启用分组优化
.where(company -> {
company.users().any(u -> u.department().eq("研发"));
company.users().avg(u -> u.salary()).gt(new BigDecimal("20000"));
})
.select(company -> Select.DRAFT.of(
company.id(),
company.name(),
company.users().count().as("userCount")
)).toList();
4. 隐式分区分组(Implicit Partition Grouping)
针对一对多关系的"获取每个分组的第一条记录"场景,传统实现需要复杂的窗口函数,而easy-query通过first()、last()等方法简化操作。
List<Company> companies = entityQuery.queryable(Company.class)
.where(company -> {
// 获取每个公司最新注册的用户
company.users()
.orderBy(u -> u.registerTime().desc())
.first() // 自动生成ROW_NUMBER()窗口函数
.department().eq("技术部");
}).toList();
5. 隐式CASE WHEN表达式(Implicit CASE WHEN)
通过filter()方法可以为聚合函数添加条件,自动生成CASE WHEN表达式,避免手动编写复杂条件。
List<StatsVO> stats = entityQuery.queryable(SysUser.class)
.select(user -> Select.DRAFT.of(
user.id().count().filter(() -> user.address().eq("北京")).as("bjCount"),
user.id().count().filter(() -> user.address().eq("上海")).as("shCount"),
user.age().avg().filter(() -> user.address().eq("广州")).as("gzAvgAge")
)).toList();
生成的SQL:
SELECT
COUNT(CASE WHEN t.`address` = '北京' THEN t.`id` END) AS `bjCount`,
COUNT(CASE WHEN t.`address` = '上海' THEN t.`id` END) AS `shCount`,
AVG(CASE WHEN t.`address` = '广州' THEN t.`age` END) AS `gzAvgAge`
FROM `t_sys_user` t
🚀 快速上手实战教程
环境准备
Spring Boot集成
<!-- Maven依赖 -->
<dependency>
<groupId>com.easy-query</groupId>
<artifactId>sql-springboot-starter</artifactId>
<version>4.10.0</version>
</dependency>
<dependency>
<groupId>com.easy-query</groupId>
<artifactId>sql-mysql</artifactId>
<version>4.10.0</version>
</dependency>
# 配置文件
easy-query:
database-type: mysql
jdbc:
url: jdbc:mysql://127.0.0.1:3306/easy-query-test?serverTimezone=GMT%2B8&characterEncoding=utf-8
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
pagination:
default-page-size: 20
max-page-size: 1000
控制台模式集成
// 数据源配置
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/easy-query-test");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 初始化客户端
EasyQueryClient client = EasyQueryBootstrapper.defaultBuilderConfiguration()
.setDataSource(dataSource)
.useDatabaseConfigure(new MySQLDatabaseConfiguration())
.build();
// 获取查询对象
EasyEntityQuery entityQuery = new DefaultEasyEntityQuery(client);
核心CRUD操作
1. 查询操作
基础查询:
// 单表查询
Topic topic = easyEntityQuery.queryable(Topic.class)
.where(o -> o.id().eq("1001"))
.firstOrNull();
// 分页查询
EasyPageResult<BlogEntity> page = easyEntityQuery.queryable(BlogEntity.class)
.where(b -> b.content().like("easy-query"))
.orderBy(b -> b.createTime().desc())
.toPageResult(1, 10); // 第1页,每页10条
复杂查询:
List<Draft3<String, Integer, LocalDateTime>> result = easyEntityQuery.queryable(BlogEntity.class)
.where(b -> b.status().eq(1))
.groupBy(b -> GroupKeys.of(b.authorId()))
.having(g -> g.star().sum().gt(100))
.select(g -> Select.DRAFT.of(
g.key1(), // authorId
g.star().sum().as("totalStar"),
g.createTime().max().as("lastPostTime")
))
.orderBy(g -> g.value2().desc())
.toList();
2. 插入操作
// 单条插入
Topic topic = new Topic();
topic.setId(UUID.randomUUID().toString());
topic.setTitle("easy-query教程");
topic.setStars(0);
topic.setCreateTime(LocalDateTime.now());
long insertRows = easyEntityQuery.insertable(topic).executeRows();
// 批量插入
List<Topic> topics = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Topic t = new Topic();
t.setId(UUID.randomUUID().toString());
t.setTitle("批量插入测试" + i);
t.setStars(i);
t.setCreateTime(LocalDateTime.now());
topics.add(t);
}
long batchRows = easyEntityQuery.insertable(topics).executeRows();
3. 更新操作
// 表达式更新
long updateRows = easyEntityQuery.updatable(Topic.class)
.setColumns(t -> t.stars().set(t.stars().add(1))) // stars = stars + 1
.where(t -> t.id().eq("1001"))
.executeRows();
// 实体更新(仅更新变化字段)
Topic topic = easyEntityQuery.queryable(Topic.class).whereId("1001").firstNotNull();
topic.setTitle("更新后的标题");
long entityRows = easyEntityQuery.updatable(topic).executeRows();
4. 删除操作
// 条件删除
long deleteRows = easyEntityQuery.deletable(Topic.class)
.where(t -> t.createTime().lt(LocalDateTime.now().minusYears(1)))
.executeRows();
// 实体删除
Topic topic = new Topic();
topic.setId("1001");
long entityDeleteRows = easyEntityQuery.deletable(topic).executeRows();
📊 分库分表实战
分表策略实现
easy-query通过注解配置和路由实现分表,支持按时间、哈希等多种分表策略。以时间分表为例:
// 分表实体定义
@Data
@Table(value = "t_topic_sharding_time",
shardingInitializer = TopicShardingTimeInitializer.class)
@EntityProxy
public class TopicShardingTime implements ProxyEntityAvailable<TopicShardingTime, TopicShardingTimeProxy> {
@Column(primaryKey = true)
private String id;
private String title;
private Integer stars;
@ShardingTableKey // 分表键
private LocalDateTime createTime; // 按创建时间分表
}
// 分表初始化器
public class TopicShardingTimeInitializer extends AbstractShardingMonthInitializer<TopicShardingTime> {
@Override
protected LocalDateTime getBeginTime() {
return LocalDateTime.of(2023, 1, 1, 0, 0); // 分表起始时间
}
@Override
protected LocalDateTime getEndTime() {
return LocalDateTime.of(2023, 12, 31, 23, 59); // 分表结束时间
}
}
// 分表路由规则
public class TopicShardingTimeTableRoute extends AbstractMonthTableRoute<TopicShardingTime> {
@Override
protected LocalDateTime convertLocalDateTime(Object shardingValue) {
return (LocalDateTime) shardingValue; // 将分表键转换为LocalDateTime
}
}
查询分表数据:
LocalDateTime begin = LocalDateTime.of(2023, 3, 1, 0, 0);
LocalDateTime end = LocalDateTime.of(2023, 5, 31, 23, 59);
List<TopicShardingTime> topics = easyEntityQuery.queryable(TopicShardingTime.class)
.where(t -> t.createTime().between(begin, end))
.toList();
上述代码会自动查询t_topic_sharding_time_202303、t_topic_sharding_time_202304、t_topic_sharding_time_202305三张表并合并结果。
分库策略实现
分库与分表配置类似,通过@ShardingDataSourceKey注解指定分库键,并实现数据源路由:
@Data
@Table(value = "t_topic_sharding_ds",
shardingInitializer = DataSourceShardingInitializer.class)
@EntityProxy
public class TopicShardingDataSource implements ProxyEntityAvailable<TopicShardingDataSource, TopicShardingDataSourceProxy> {
@Column(primaryKey = true)
private String id;
private String title;
@ShardingDataSourceKey // 分库键
private Integer userId; // 按用户ID哈希分库
}
// 数据源路由
public class TopicShardingDataSourceRoute extends AbstractDataSourceRoute<TopicShardingDataSource> {
@Override
protected RouteFunction<String> getRouteFilter(TableAvailable table, Object shardingValue,
ShardingOperatorEnum operator, boolean withEntity) {
int userId = (Integer) shardingValue;
String dsName = "ds_" + (userId % 4); // 哈希到4个数据源
return ds -> ds.equals(dsName); // 返回数据源匹配规则
}
}
🔒 加密字段查询
对于手机号、身份证等敏感信息,easy-query提供字段加密存储和查询支持:
@Data
@Table("t_sys_user")
@EntityProxy
public class SysUser extends BaseEntity implements ProxyEntityAvailable<SysUser, SysUserProxy> {
private String username;
@Encryption(strategy = DefaultAesEasyEncryptionStrategy.class,
supportQueryLike = true) // 声明加密字段
private String phone; // 加密存储的手机号
@Encryption(strategy = DefaultAesEasyEncryptionStrategy.class)
private String idCard; // 加密存储的身份证号
}
加密字段查询:
// 精确查询
List<SysUser> users = easyEntityQuery.queryable(SysUser.class)
.where(u -> u.phone().eq("13800138000")) // 自动解密查询
.toList();
// 模糊查询(需supportQueryLike=true)
List<SysUser> likeUsers = easyEntityQuery.queryable(SysUser.class)
.where(u -> u.phone().like("138%")) // 支持加密字段模糊查询
.toList();
⚡ 性能优化技巧
1. 按需加载与查询优化
// 仅查询需要的字段(避免SELECT *)
List<Draft2<String, Integer>> simpleUsers = easyEntityQuery.queryable(SysUser.class)
.select(u -> Select.DRAFT.of(u.id(), u.age())) // 仅查询id和age字段
.toList();
// 使用索引字段排序
List<SysUser> orderedUsers = easyEntityQuery.queryable(SysUser.class)
.where(u -> u.status().eq(1))
.orderBy(u -> u.createTime().desc()) // 假设createTime有索引
.toList();
2. 批量操作优化
// 批量插入优化
easyEntityQuery.insertable(topics)
.batchSize(1000) // 每批插入1000条
.useExecuteBatch(true) // 使用JDBC批量执行
.executeRows();
// 批量更新优化
easyEntityQuery.updatable(Topic.class)
.setColumns(t -> t.stars().set(0))
.where(t -> t.createTime().lt(LocalDateTime.now().minusMonths(3)))
.allowExecuteBatch(500) // 分批次执行更新
.executeRows();
3. 缓存策略
// 单条缓存
Topic topic = easyEntityQuery.queryable(Topic.class)
.withCache(10, TimeUnit.SECONDS) // 缓存10秒
.whereId("1001")
.firstOrNull();
// 查询结果缓存
List<Topic> hotTopics = easyEntityQuery.queryable(Topic.class)
.where(t -> t.stars().gt(1000))
.withCache(5, TimeUnit.MINUTES) // 热门数据缓存5分钟
.toList();
🆚 与主流ORM框架对比
| 特性 | easy-query | MyBatis-Plus | Spring Data JPA |
|---|---|---|---|
| 强类型API | ✅ 原生支持 | ❌ 需要插件 | ✅ 支持但语法繁琐 |
| 隐式关联查询 | ✅ 自动JOIN | ❌ 需手动编写 | ✅ 支持但性能差 |
| 分库分表 | ✅ 内置支持 | ❌ 需集成Sharding-JDBC | ❌ 需集成Sharding-JDBC |
| 加密字段查询 | ✅ 原生支持 | ❌ 需手动实现 | ❌ 需手动实现 |
| 动态SQL | ✅ 类型安全 | ✅ XML/注解 | ✅ JPQL/Criteria |
| 启动速度 | ⚡ 极快 | ⚡ 快 | 🐢 较慢(实体扫描) |
| 学习曲线 | 低 | 低 | 中高 |
📝 总结与展望
easy-query通过创新的隐式查询特性,将开发者从复杂的SQL编写中解放出来,同时保持了接近原生JDBC的性能。其核心优势在于:
- 强类型安全:编译期检查查询条件,减少运行时错误
- 隐式查询优化:自动处理关联查询、子查询和分组逻辑
- 轻量级设计:无冗余依赖,启动速度快,内存占用低
- 功能全面:内置分库分表、加密查询、缓存等企业级特性
随着4.10版本的发布,easy-query进一步提升了复杂查询性能和分表策略灵活性,未来还将支持更多数据库特性和查询优化。无论你是开发小型应用还是构建企业级系统,easy-query都能为你提供简洁而强大的数据库操作体验。
立即访问项目仓库开始使用:https://gitcode.com/dromara/easy-query
如果你觉得本文对你有帮助,请点赞收藏并关注项目更新! 下一篇我们将深入探讨easy-query的源码架构和扩展点实现,敬请期待。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



