终于有人把 Java 动态 SQL 写舒服了!支持任意嵌套、分页、一对多,你还在手搓 SQL 吗?

Java 动态 SQL 构建新选择:告别繁琐 XML,轻量、强类型的 dynamic-sql2 框架来了!

在日常开发中,动态 SQL 一直是老生带的问题:

  • XML 拼接 SQL,开发体验差、易出错
  • 直接字符串拼接 SQL,存在严重安全障碍
  • ORM 框架虽好,但复杂查询时往往换不起来

有没有一种方案,既能:

✅ 保留原生 SQL 的灵活性
✅ 摆脱 XML、字符串的繁琐与不安全
✅ 提供类型安全、链式流畅的开发体验

答案是:dynamic-sql2,一款专注 SQL 构建的轻量级 Java 框架!


💡 dynamic-sql2 简介

dynamic-sql2 是我在实战项目中总结提炼的一套 SQL 动态构建方案,专注于:

DSL 风格链式 API,干净利落,无需 XML 配置
类型安全表达式,避免拼写错误,IDE 友好提示
多数据库方言支持,目前已兼容 MySQL、Oracle、DB2
子查询、窗口函数、CTE、递归查询 一应俱全
Spring Boot Starter 集成,开盘即用

目标是让开发者轻松、安全地构建复杂 SQL,同时不被 ORM 绑定,保持最大灵活性。


🛠 快速体验示例

dynamic-sql2 提供强类型、链式、安全、灵活的 SQL 构建能力,支持:

✅ 子查询、JSON_TABLE、窗口函数、动态排序
✅ 一对多结果映射、集合分页、Map结构输出
✅ 自定义 SQL 函数,任意嵌套组合拓展

以下通过典型案例,快速了解其强大特性。
添加Maven依赖

目前0.1.1是最新版,0.1.2正在开发中。

        <!-- springboot 引用-->
        <dependency>
            <groupId>com.dynamic-sql</groupId>
            <artifactId>dynamic-sql2-spring-boot-starter</artifactId>
            <version>0.1.1</version>
        </dependency>
        <!-- 单体项目引用-->
        <dependency>
            <groupId>com.dynamic-sql</groupId>
            <artifactId>dynamic-sql2</artifactId>
            <version>0.1.1</version>
        </dependency>

🔍 查询

本框架支持链式、类型安全、任意函数嵌套、分页、子查询、窗口函数、一对多映射等能力,适合复杂业务场景。


1. 子查询结合 JSON_TABLE 解析嵌套数据

适用场景:从订单JSON字段中解析商品信息,并进行联表查询。

List<UserOrderView> list = sqlContext.select()
    .column(User::getUserId)
    .column(User::getName)
    .column("total.total_amount")
    .column("p.product_name", "product")
    .from(User.class)
    .join(select -> select
            .column(Order::getUserId)
            .column(new Sum(Order::getTotalAmount), "total_amount")
            .from(Order.class)
            .groupBy(Order::getUserId),
        "total",
        on -> on.andEqualTo(User::getUserId, bindAlias("total", Order::getUserId)))
    .leftJoin(select -> select
            .column(Product::getProductId)
            .column("jt.order_id")
            .from(Product.class)
            .join(() -> new JsonTable("o", "order_details", "$.items[*]",
                    JsonColumn.builder().column("product").dataType("VARCHAR(100)").jsonPath("$.productName").build()),
                "jt",
                on -> on.andEqualTo(bindAlias("jt", "product"), Product::getProductName)),
        "p",
        on -> on.andEqualTo(Order::getOrderId, bindAlias("p", Order::getOrderId)))
    .fetch(UserOrderView.class)
    .toList();

2. 集合分页查询(子查询集合)

适用场景:针对集合类结果,进行分页截取,避免数据过大传输。

CollectionPage<List<ProductView>, ProductView> page = PageHelper.ofCollection(1, 10)
    .selectPage(() -> sqlContext.select()
        .column(Product::getProductId)
        .column(Product::getProductName)
        .from(Product.class)
        .fetch(ProductView.class)
        .toList());

3. 一对多集合映射

适用场景:将一对多关联结果,映射为集合字段返回,常见于主表附属数据展示。

List<CategoryView> list = sqlContext.select()
    .column(Category::getCategoryId)
    .column(Category::getCategoryName)
    .collectionColumn(
        KeyMapping.of(Category::getCategoryId, Product::getCategoryId),
        mapper -> mapper.column(Product::getProductId).column(Product::getProductName),
        "productList")
    .from(Category.class)
    .join(Product.class, on -> on.andEqualTo(Category::getCategoryId, Product::getCategoryId))
    .fetch(CategoryView.class)
    .toList();

4. 动态排序(前端字段控制)

适用场景:用户指定排序字段,安全构建 SQL,防止 SQL 注入。

String sortField = "createTime";
List<User> list = sqlContext.select()
    .allColumn()
    .from(User.class)
    .orderBy(sortField, SortOrder.DESC)
    .fetch()
    .toList();

5. 窗口函数 DenseRank 示例

适用场景:分组内排名,支持复杂统计分析。

List<Map<String, Object>> list = sqlContext.select()
    .column(Product::getProductId)
    .column(Product::getProductName)
    .column(new DenseRank(), over -> over
        .partitionBy(Product::getCategoryId)
        .orderBy(new Sum(Product::getPrice), SortOrder.DESC),
        "rank")
    .from(Product.class)
    .groupBy(Product::getProductId, Product::getCategoryId)
    .fetchOriginalMap()
    .toList();

6. 分页 + 聚合 + 窗口函数组合查询

适用场景:结合分页、统计、排名,适用于排行榜、分析报表。

PageInfo<UserView> page = PageHelper.of(1, 10).selectPage(() -> sqlContext.select()
    .column(User::getUserId)
    .column(User::getName)
    .column(new Sum(Order::getTotalAmount), "totalSpent")
    .column(new DenseRank(), over -> over
        .orderBy(new Sum(Order::getTotalAmount), SortOrder.DESC),
        "rank")
    .from(User.class)
    .join(Order.class, on -> on.andEqualTo(User::getUserId, Order::getUserId))
    .groupBy(User::getUserId)
    .fetch(UserView.class));

7. 日期格式化分组(年月聚合)

适用场景:基于日期字段,按指定格式聚合,适合按月统计。

List<UserDateView> list = sqlContext.select()
    .column(User::getUserId)
    .column(new DateFormat(User::getRegistrationDate, "%Y-%m"))
    .from(User.class)
    .groupBy(User::getUserId)
    .groupBy(new DateFormat(User::getRegistrationDate, "%Y-%m"))
    .limit(10)
    .fetch(UserDateView.class)
    .toList();

8. 算术函数任意嵌套拓展

适用场景:复杂计算需求,函数无限组合。

Map<String, Object> result = sqlContext.select()
    .column(new Round(new Sum(User::getUserId), 3).divide(2))
    .column(new Round(new Sum(User::getUserId).divide(2), 3))
    .column(new Round(new Sum(User::getUserId).divide(new Count(User::getUserId)), 3))
    .from(User.class)
    .fetchOriginalMap()
    .toOne();

💾 新增

1. 仅插入非空字段

适用场景:字段较多,部分字段可为 null,仅插入有值字段,避免空值写入。

Product product = new Product();
product.setProductName("菠萝手机-insertSelective");
product.setPrice(BigDecimal.valueOf(6.66));
product.setStock(666);
product.setCreatedAt(new Date());
product.setCategoryId(1);

// 仅插入非空字段,保持 SQL 简洁
int rows = sqlContext.insertSelective(product);
System.out.println("影响行数:" + rows);

2. 字段即使为空也要插入

适用场景:部分字段即使为空也要插入,即使该字段为null,这在特定场景下特别有用

Product product = new Product();
product.setProductName("菠萝手机-insertSelective2");
product.setPrice(BigDecimal.valueOf(6.66));
product.setStock(666);
product.setCreatedAt(new Date());
product.setCategoryId(1);

// 强制更新Attributes字段,即使该字段为null,这在特定场景下特别有用
int rows = sqlContext.insertSelective(product, Arrays.asList(Product::getAttributes, Product::getProductId));
System.out.println("影响行数:" + rows);

3. 全字段插入

适用场景:所有字段均已赋值,直接全字段写入,确保数据完整性。

Product product = new Product();
product.setProductName("菠萝手机-insert");
product.setPrice(BigDecimal.valueOf(6.66));
product.setStock(666);
product.setCreatedAt(new Date());
product.setCategoryId(1);

// 全字段插入
int rows = sqlContext.insert(product);
System.out.println("影响行数:" + rows);

4. 单条多次插入(分批发送)

适用场景:大批量数据写入,兼容不支持多值插入的数据库,适合海量数据分批。

List<Product> products = new ArrayList<>();
for (int i = 1; i <= 10_000; i++) {
    Product product = new Product();
    product.setProductName("菠萝手机-insertBatch/" + i);
    product.setPrice(BigDecimal.valueOf(6.66));
    product.setStock(666);
    product.setCreatedAt(new Date());
    product.setCategoryId(1);
    products.add(product);
}

long start = System.currentTimeMillis();
int rows = sqlContext.insertBatch(products);
long duration = System.currentTimeMillis() - start;

System.out.println("耗时:" + duration + "ms");
System.out.println("总插入行数:" + rows);

5. 单 SQL 多值插入(高性能)

适用场景:数据库支持多值插入,批量写入性能更优,减少网络和 SQL 解析开销。

List<Product> products = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
    Product product = new Product();
    product.setProductName("菠萝手机-insertMultiple/" + i);
    product.setPrice(BigDecimal.valueOf(6.66));
    product.setStock(666);
    product.setCreatedAt(new Date());
    product.setCategoryId(1);
    products.add(product);
}

long start = System.currentTimeMillis();
int rows = sqlContext.insertMultiple(products);
long duration = System.currentTimeMillis() - start;

System.out.println("耗时:" + duration + "ms");
System.out.println("总插入行数:" + rows);

6. 插入视图对象

适用场景:视图、联表映射对象存储,框架自动识别可插入字段,忽略虚拟属性。

UserAndOrderView view = new UserAndOrderView();
view.setPrice(BigDecimal.ONE);

// 插入映射视图对象,仅写入真实存在的物理字段
sqlContext.insertMultiple(Collections.singleton(view));

🔧 更新

1. 根据主键全字段更新

适用场景:明确指定主键,更新所有字段,确保数据完整覆盖。

Product product = new Product();
product.setProductId(20);
product.setProductName("New Coffee Maker");
product.setCategoryId(4);
product.setCreatedAt(new Date());
product.setPrice(BigDecimal.TEN);
product.setStock(123);

// 主键全字段更新
int rows = sqlContext.updateByPrimaryKey(product);
System.out.println(rows);

2. 主键更新,忽略 null 字段

适用场景:更新有值字段,指定字段即使为空也会强制更新为null。

Product product = new Product();
product.setProductId(20);
product.setProductName("New Coffee Maker3");
product.setCategoryId(4);
product.setCreatedAt(new Date());
product.setPrice(BigDecimal.valueOf(879));
product.setStock(222);

// 强制 attributes 字段更新,其他字段根据条件更新
int rows = sqlContext.updateSelective(product, Collections.singletonList(Product::getAttributes), 
    w -> w.andEqualTo(Product::getProductId, 20));
System.out.println(rows);

4. 自动插入或更新(全字段)

适用场景:数据存在则更新,不存在则新增,适合唯一约束表,防止重复数据。

Product product = new Product();
product.setProductName("New Coffee Maker");
product.setCategoryId(4);
product.setCreatedAt(new Date());
product.setPrice(BigDecimal.TEN);
product.setStock(123);

// 自动插入或更新
int rows = sqlContext.upsert(product);
System.out.println(rows);

5. 自动插入或更新,部分字段控制

适用场景:Upsert 同时,强制部分字段,灵活控制写入范围。

Product product = sqlContext.select().allColumn().from(Product.class)
    .where(w -> w.andEqualTo(Product::getProductId, 7))
    .fetch().toOne();

product.setProductName("MacBook Pro2");
product.setCreatedAt(new Date());
product.setAttributes("{\"a\":1}");
product.setProductId(null); // 强制模拟新增

// 强制 attributes 字段更新,其他字段根据条件更新
int rows = sqlContext.upsertSelective(product, Collections.singletonList(Product::getAttributes));
System.out.println(rows);

6. 自动插入或更新

适用场景:大批量数据同步,存在更新,不存在新增,提升效率。

List<Product> products = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
    Product product = new Product();
    product.setProductName("New Coffee Maker " + i);
    product.setCategoryId(4);
    product.setCreatedAt(new Date());
    product.setPrice(BigDecimal.TEN);
    product.setStock(123);
    products.add(product);
}

// 批量自动插入或更新
int rows = sqlContext.upsertMultiple(products);
System.out.println(rows);

⚙️ 删除

1. 按单个主键删除

测试通过 deleteByPrimaryKey 方法,删除 Product 表中指定主键的记录。

void deleteByPrimaryKey() {
    int pkValue = 5011;
    int i = sqlContext.deleteByPrimaryKey(Product.class, pkValue);
    System.out.println(i);
}

2. 按多个主键批量删除

测试通过 deleteByPrimaryKey 方法,删除 Product 表中指定主键集合的多条记录。

    void deleteByPrimaryKey2() {
        int i = sqlContext.deleteByPrimaryKey(Product.class, Arrays.asList(5001, 5002, 5003, 5004, 5005));
        System.out.println(i);
    }

3. 根据条件删除

根据条件删除,可组合任意条件

    void delete() {
        int i = sqlContext.delete(Product.class, where -> {
            where.andEqualTo(Product::getProductId, 1);
            where.orCondition(nestedWhere -> {
                nestedWhere.andEqualTo(Product::getProductId, 3);
                nestedWhere.orEqualTo(Product::getProductId, 4);
            });
        });
        System.out.println(i);
    }

4. 删除全部

直接删除表中所有的数据

    void deleteAll() {
        int i = sqlContext.delete(Product.class, null);
        System.out.println(i);
    }

这里列取了常见的操作,因篇幅过长不便书写,更多内容请直接访问 GitHub项目地址


🔥 为什么选择 dynamic-sql2?

相比传统拼接、XML 配置,dynamic-sql2 的优势十分明显:

特性说明
轻量级无需繁琐配置,纯 Java 链式调用,快速上手
强类型安全基于实体类属性表达,IDE 全程语法提示,避免拼写错误
多方言兼容内置 MySQL、Oracle、DB2,未来支持更多数据库
高级 SQL 支持子查询、CTE、递归、窗口函数等复杂 SQL 全面覆盖
良好扩展性内置拦截器机制,方便自定义 SQL 日志、权限控制
Spring Boot 适配Starter 集成,Spring 项目无缝接入

特别适合对 SQL 灵活性、安全性、数据库兼容性有较高要求的系统。


🏢 典型适用场景

✔ 金融系统:动态报表、权限控制、复杂统计 SQL 构建
✔ 数据中台:多数据源环境下灵活拼接 SQL、动态过滤条件
✔ 传统项目:不想写繁琐 XML,又想控制 SQL 细节的场景
✔ 需要支持递归、窗口函数、子查询的中大型系统


🗺️ 未来规划

项目仍在持续优化,规划包括:

  • PostgreSQL、SQL Server 等更多数据库支持
  • 泛型推导进一步优化,提升 IDE 语法提示体验
  • 官方文档、示例项目完善,降低学习成本
  • 提供性能基准测试与优化方案
  • 吸引更多社区开发者共同参与,打造国产开源 SQL 构建利器

📦 项目地址

欢迎试用、Star、Fork、提交反馈:

👉 GitHub(推荐): https://github.com/pengweizhong/dynamic-sql2
👉 Gitee(加速地址): https://gitee.com/pengweizhong/dynamic-sql2

项目适用于任何 Java 8+ 环境,Spring Boot 用户可通过 Starter 快速集成。


🗣️ 交流 & 加入社区

我已建立技术交流群,欢迎交流:

  • 实战经验分享
  • 动态 SQL 场景讨论
  • 需求建议、功能共建
  • 遇到 Bug,群内及时响应

扫码加群 / 私信我获取群二维码:

_(这里预留微信群二维码位置或联系信息)_TODO


💬 结语

SQL 构建应该是简单、安全、灵活的。

dynamic-sql2 正是为此而生,专注帮助 Java 开发者用更优雅、更可靠的方式管理动态 SQL,摆脱 XML、字符串拼接带来的痛点。

如果你也在为动态 SQL 头疼,欢迎体验 dynamic-sql2,让开发回归本质,让 SQL 更加清晰高效。


🎯 下一步推荐

如果你对 dynamic-sql2 感兴趣,可以继续关注:

  • 下一篇:《5分钟快速上手 dynamic-sql2 实战教程》
  • 后续更新:复杂分页、动态排序、子查询、窗口函数实战
  • 社区发布:定期分享技术干货,邀请你一起完善框架

欢迎评论、点赞、关注,一起打造更优秀的国产动态 SQL 框架!

声明:该文章由Chat GPT 自动生成!难免有疏漏之处,敬请见谅~
声明:该文章由Chat GPT 自动生成!难免有疏漏之处,敬请见谅~
声明:该文章由Chat GPT 自动生成!难免有疏漏之处,敬请见谅~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值