1. 概述

之前的文章中,我们重点介绍了如何快速上手 Apache Cayenne ORM。本文将深入探讨如何使用该 ORM 框架编写简单到复杂的查询操作。

2. 环境准备

测试环境配置与前一篇文章保持一致。为便于测试,我们在每个测试用例执行前预置三条作者数据,测试结束后清理:

  • Paul Xavier
  • pAuL Smith
  • Vicky Sarra

3. ObjectSelect 查询

3.1 基础查询

先从简单查询开始,获取所有名称包含"Paul"的作者:

@Test
public void whenContainsObjS_thenWeGetOneRecord() {
    List<Author> authors = ObjectSelect.query(Author.class)
      .where(Author.NAME.contains("Paul"))
      .select(context);

    assertEquals(authors.size(), 1);
}

3.2 不区分大小写查询

使用 likeIgnoreCase 实现不区分大小写的 LIKE 查询:

@Test
void whenLikeObjS_thenWeGetTwoAuthors() {
    List<Author> authors = ObjectSelect.query(Author.class)
      .where(Author.NAME.likeIgnoreCase("Paul%"))
      .select(context);

    assertEquals(authors.size(), 2);
}

3.3 后缀匹配查询

endsWith() 方法返回名称以"Sarra"结尾的作者:

@Test
void whenEndsWithObjS_thenWeGetOrderedAuthors() {
    List<Author> authors = ObjectSelect.query(Author.class)
      .where(Author.NAME.endsWith("Sarra"))
      .select(context);
    Author firstAuthor = authors.get(0);

    assertEquals(authors.size(), 1);
    assertEquals(firstAuthor.getName(), "Vicky Sarra");
}

3.4 IN 条件查询

查询名称在指定列表中的作者:

@Test
void whenInObjS_thenWeGetAuthors() {
    List names = Arrays.asList(
      "Paul Xavier", "pAuL Smith", "Vicky Sarra");
 
    List<Author> authors = ObjectSelect.query(Author.class)
      .where(Author.NAME.in(names))
      .select(context);

    assertEquals(authors.size(), 3);
}

3.5 NOT IN 条件查询

nin(NOT IN)操作符排除指定名称:

@Test
void whenNinObjS_thenWeGetAuthors() {
    List names = Arrays.asList(
      "Paul Xavier", "pAuL Smith");
    List<Author> authors = ObjectSelect.query(Author.class)
      .where(Author.NAME.nin(names))
      .select(context);
    Author author = authors.get(0);

    assertEquals(authors.size(), 1);
    assertEquals(author.getName(), "Vicky Sarra");
}

3.6 表达式工厂对比

以下两种写法效果完全相同:

Expression qualifier = ExpressionFactory
  .containsIgnoreCaseExp(Author.NAME.getName(), "Paul");
Author.NAME.containsIgnoreCase("Paul");

3.7 常用表达式列表

ExpressionExpressionFactory 类提供的核心表达式:

  • likeExp: 构建 LIKE 表达式
  • likeIgnoreCaseExp: 构建不区分大小写的 LIKE 表达式
  • containsExp: 字符串任意位置匹配的 LIKE 查询
  • containsIgnoreCaseExp: 不区分大小写的字符串匹配
  • startsWithExp: 匹配字符串开头
  • startsWithIgnoreCaseExp: 不区分大小写的开头匹配
  • endsWithExp: 匹配字符串结尾
  • endsWithIgnoreCaseExp: 不区分大小写的结尾匹配
  • expTrue: 布尔 true 表达式
  • expFalse: 布尔 false 表达式
  • andExp: AND 操作符连接表达式
  • orExp: OR 操作符连接表达式

更多测试用例请参考 GitHub 仓库

4. SelectQuery 查询

4.1 基础用法

这是应用中最常用的查询类型,提供类 SQL 语法的 API,通过构建器模式组合复杂表达式。

4.1.1 LIKE 查询示例

@Test
void whenLikeSltQry_thenWeGetOneAuthor() {
    Expression qualifier 
      = ExpressionFactory.likeExp(Author.NAME.getName(), "Paul%");
    SelectQuery query 
      = new SelectQuery(Author.class, qualifier);
    
    List<Author> authorsTwo = context.performQuery(query);

    assertEquals(authorsTwo.size(), 1);
}

⚠️ 注意:未提供表达式时将返回全表数据。

4.1.2 不区分大小写包含查询

@Test
void whenCtnsIgnorCaseSltQry_thenWeGetTwoAuthors() {
    Expression qualifier = ExpressionFactory
      .containsIgnoreCaseExp(Author.NAME.getName(), "Paul");
    SelectQuery query 
      = new SelectQuery(Author.class, qualifier);
    
    List<Author> authors = context.performQuery(query);

    assertEquals(authors.size(), 2);
}

4.1.3 复合条件查询

组合包含"Paul"且以"h"结尾的条件:

@Test
void whenCtnsIgnorCaseEndsWSltQry_thenWeGetTwoAuthors() {
    Expression qualifier = ExpressionFactory
      .containsIgnoreCaseExp(Author.NAME.getName(), "Paul")
      .andExp(ExpressionFactory
        .endsWithExp(Author.NAME.getName(), "h"));
    SelectQuery query = new SelectQuery(
      Author.class, qualifier);
    List<Author> authors = context.performQuery(query);

    Author author = authors.get(0);

    assertEquals(authors.size(), 1);
    assertEquals(author.getName(), "pAuL Smith");
}

4.2 排序功能

4.2.1 升序排序

@Test
void whenAscOrdering_thenWeGetOrderedAuthors() {
    SelectQuery query = new SelectQuery(Author.class);
    query.addOrdering(Author.NAME.asc());
 
    List<Author> authors = query.select(context);
    Author firstAuthor = authors.get(0);

    assertEquals(authors.size(), 3);
    assertEquals(firstAuthor.getName(), "Paul Xavier");
}

等价写法:

query.addOrdering(Author.NAME.getName(), SortOrder.ASCENDING);

4.2.2 降序排序

@Test
void whenDescOrderingSltQry_thenWeGetOrderedAuthors() {
    SelectQuery query = new SelectQuery(Author.class);
    query.addOrdering(Author.NAME.desc());

    List<Author> authors = query.select(context);
    Author firstAuthor = authors.get(0);

    assertEquals(authors.size(), 3);
    assertEquals(firstAuthor.getName(), "pAuL Smith");
}

等价写法:

query.addOrdering(Author.NAME.getName(), SortOrder.DESCENDING);

5. SQLTemplate 查询

当需要直接操作原生 SQL 时,SQLTemplate 是最佳选择。

5.1 清理数据示例

@After
void deleteAllAuthors() {
    SQLTemplate deleteAuthors = new SQLTemplate(
      Author.class, "delete from author");
    context.performGenericQuery(deleteAuthors);
}

5.2 全量查询

@Test
void givenAuthors_whenFindAllSQLTmplt_thenWeGetThreeAuthors() {
    SQLTemplate select = new SQLTemplate(
      Author.class, "select * from Author");
    List<Author> authors = context.performQuery(select);

    assertEquals(authors.size(), 3);
}

5.3 条件查询

@Test
void givenAuthors_whenFindByNameSQLTmplt_thenWeGetOneAuthor() {
    SQLTemplate select = new SQLTemplate(
      Author.class, "select * from Author where name = 'Vicky Sarra'");
    List<Author> authors = context.performQuery(select);
    Author author = authors.get(0);

    assertEquals(authors.size(), 1);
    assertEquals(author.getName(), "Vicky Sarra");
}

6. EJBQLQuery 查询

这是 Cayenne 对 JPA 查询语言的实验性实现,采用参数化对象风格。

6.1 全量查询

@Test
void givenAuthors_whenFindAllEJBQL_thenWeGetThreeAuthors() {
    EJBQLQuery query = new EJBQLQuery("select a FROM Author a");
    List<Author> authors = context.performQuery(query);

    assertEquals(authors.size(), 3);
}

6.2 条件查询

@Test
void givenAuthors_whenFindByNameEJBQL_thenWeGetOneAuthor() {
    EJBQLQuery query = new EJBQLQuery(
      "select a FROM Author a WHERE a.name = 'Vicky Sarra'");
    List<Author> authors = context.performQuery(query);
    Author author = authors.get(0);

    assertEquals(authors.size(), 1);
    assertEquals(author.getName(), "Vicky Sarra");
}

6.3 更新操作

@Test
void whenUpdadingByNameEJBQL_thenWeGetTheUpdatedAuthor() {
    EJBQLQuery query = new EJBQLQuery(
      "UPDATE Author AS a SET a.name "
      + "= 'Vicky Edison' WHERE a.name = 'Vicky Sarra'");
    QueryResponse queryResponse = context.performGenericQuery(query);

    EJBQLQuery queryUpdatedAuthor = new EJBQLQuery(
      "select a FROM Author a WHERE a.name = 'Vicky Edison'");
    List<Author> authors = context.performQuery(queryUpdatedAuthor);
    Author author = authors.get(0);

    assertNotNull(author);
}

更多示例见 GitHub 源码

7. SQLExec 查询

Cayenne M4 版本引入的流式查询 API,适合简单粗暴的 SQL 操作。

7.1 插入操作

@Test
void whenInsertingSQLExec_thenWeGetNewAuthor() {
    int inserted = SQLExec
      .query("INSERT INTO Author (name) VALUES ('Baeldung')")
      .update(context);

    assertEquals(inserted, 1);
}

7.2 更新操作

@Test
void whenUpdatingSQLExec_thenItsUpdated() {
    int updated = SQLExec.query(
      "UPDATE Author SET name = 'Baeldung' "
      + "WHERE name = 'Vicky Sarra'")
      .update(context);

    assertEquals(updated, 1);
}

详细用法参考 官方文档

8. 总结

本文系统介绍了 Cayenne ORM 的多种查询方式,从简单的对象查询到原生 SQL 操作,覆盖了实际开发中的常见场景。选择合适的查询方式能有效提升开发效率,避免踩坑。

完整示例代码请访问 GitHub 仓库


原始标题:Advanced Querying in Apache Cayenne