{"id":13544960,"url":"https://github.com/querystream/querystream","last_synced_at":"2025-12-26T07:38:46.326Z","repository":{"id":28396203,"uuid":"118291728","full_name":"querystream/querystream","owner":"querystream","description":"Build JPA Criteria queries using a Stream-like API","archived":false,"fork":false,"pushed_at":"2023-11-10T17:25:49.000Z","size":2308,"stargazers_count":17,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-03T12:31:53.736Z","etag":null,"topics":["java","jpa","jpa-criteria-api","persistence","streams"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/querystream.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.txt","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2018-01-21T00:19:07.000Z","updated_at":"2024-09-05T18:32:39.000Z","dependencies_parsed_at":"2023-11-10T18:42:00.910Z","dependency_job_id":null,"html_url":"https://github.com/querystream/querystream","commit_stats":null,"previous_names":["archiecobbs/querystream"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/querystream%2Fquerystream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/querystream%2Fquerystream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/querystream%2Fquerystream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/querystream%2Fquerystream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/querystream","download_url":"https://codeload.github.com/querystream/querystream/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246841556,"owners_count":20842613,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","jpa","jpa-criteria-api","persistence","streams"],"created_at":"2024-08-01T11:00:55.405Z","updated_at":"2025-12-24T18:27:58.250Z","avatar_url":"https://github.com/querystream.png","language":"HTML","readme":"### QueryStream\n\nQueryStream allows you to perform JPA queries using a `Stream`-like API.\n\nJust like a Java `Stream`, a `QueryStream` is built up in a pipeline, using methods like `map()`, `flatMap()`, `filter()`, etc.\n\nEach step in a `QueryStream` pipeline modifies the construction of an internal [JPA Criteria query](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/criteria/package-summary.html).\n\nWhen you're ready to execute the pipeline:\n\n  1. Invoke `QueryStream.toCriteriaQuery()` to extract the [CriteriaQuery](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/criteria/CriteriaQuery.html); or\n  1. Invoke `QueryStream.toQuery()` to do #1 and also create a [TypedQuery](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/TypedQuery.html); or\n  1. Invoke `QueryStream.getResultList()` or `QueryStream.getResultStream()` to do #1 and #2 execute the query\n\n## Example\n\nPayroll costs are getting out of control. You need a list of all managers whose direct reports have an average salary above $50,000, ordered from highest to lowest.\n\nHere's how you'd build a Criteria query the usual way:\n\n```java\n    public List\u003cEmployee\u003e getHighPayrollManagers(EntityManager entityManager) {\n\n        // Construct query\n        final CriteriaQuery criteriaQuery = cb.createQuery();\n        final Root\u003cEmployee\u003e manager = criteriaQuery.from(Employee.class);\n        final SetJoin\u003cEmployee, Employee\u003e directReports = manager.join(Employee_.directReports);\n        final Expression\u003cDouble\u003e avgSalary = cb.avg(directReports.get(Employee_.salary));\n        criteriaQuery.where(cb.greaterThan(avgSalary, 50000.0))\n        criteriaQuery.groupBy(manager);\n        criteriaQuery.orderBy(avgSalary);\n\n        // Execute query\n        final TypedQuery\u003cEmployee\u003e typedQuery = entityManager.createQuery(criteriaQuery);\n        return typedQuery.getResultList();\n    }\n\n    @PersistenceContext\n    private EntityManager entityManager;\n    private CriteriaBuilder cb;\n\n    @PostConstruct\n    private void setupCriteriaBuilder() {\n        this.cb = this.entityManager.getCriteriaBuilder();\n    }\n```\n\nWith QueryStream, your code becomes more succint and visually intuitive:\n\n```java\n    public List\u003cEmployee\u003e getHighPayrollManagers() {\n\n        // Create a couple of references\n        final RootRef\u003cEmployee\u003e manager = new RootRef\u003c\u003e();\n        final ExprRef\u003cDouble\u003e avgSalary = new ExprRef\u003c\u003e();\n\n        // Build and execute query\n        return qb.stream(Employee.class)\n          .bind(manager)\n          .flatMap(Employee_.directReports)\n          .mapToDouble(Employee_.salary)\n          .average()\n          .filter(v -\u003e qb.greaterThan(v, 50000))\n          .bind(avgSalary)\n          .groupBy(manager)\n          .orderBy(avgSalary, false)\n          .getResultList();\n    }\n\n    @PersistenceContext\n    private EntityManager entityManager;\n    private QueryStream.Builder qb;\n\n    @PostConstruct\n    private void setupBuilders() {\n        this.qb = QueryStream.newBuilder(this.entityManager);\n    }\n```\n\n[See below](#references) for more information about references.\n\n## Bulk Updates and Deletes\n\nBulk deletes and updates are also supported.\n\nThe [QueryStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/QueryStream.html) interface has three subinterfaces for searches, bulk deletes, and bulk updates; these are [SearchStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/SearchStream), [DeleteStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/DeleteStream), and [UpdateStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/UpdateStream).\n\n * A [SearchStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/SearchStream.html) builds an internal [CriteriaQuery](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/criteria/CriteriaQuery.html) instance\n * A [DeleteStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/DeleteStream.html) builds an internal [CriteriaDelete](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/criteria/CriteriaDelete.html) instance\n * An [UpdateStream](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/UpdateStream.html) builds an internal [CriteriaUpdate](https://jakarta.ee/specifications/platform/10/apidocs/jakarta/persistence/criteria/CriteriaUpdate.html) instance\n\n## Single Values\n\nSome queries are known to return a single value. The [SearchValue](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/SearchValue.html) and its subinterfaces represent streams for which it is known that at most one result will be found. These interfaces have a `value()` method, which executes the query and returns the single value:\n\n```java\n    public double getAverageSalary(Employee manager) {\n        return qb.stream(Employee.class)\n          .filter(e -\u003e qb.equal(e, manager))\n          .flatMap(Employee_.directReports)\n          .mapToDouble(Employee_.salary)\n          .average()\n          .value();\n    }\n```\n\nOther similar methods are `min()`, `max()`, `sum()`, and `findFirst()`.\n\nValue queries can be converted to `Optional`s and have several related convenience methods like `orElse()`, `isPresent()`, etc.\n\n## References\n\n[Ref](http://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/Ref.html) objects give you a way to refer to items in the stream pipeline at a later step, by `bind()`'ing the reference at an earlier step.\n\nReferences also help code clarity, because they provide a way to give meaningful names to important expressions.\n\nSee the `getHighPayrollManagers()` example above for how it works. The main thing to remember is that the `bind()` must occur prior to the use of the reference in the pipeline.\n\n## Subqueries\n\nQueryStream makes using subqueries easier. A stream can be used as a subquery via `asSubquery()` or `exists()`.\n\nTo find all managers for whom there exists a direct report making over $100,000:\n\n```java\n    public List\u003cEmployee\u003e findManagersWithSixFigureDirectReport() {\n        return qb.stream(Employee.class)\n          .filter(manager -\u003e qb.stream(Employee.class)\n                .filter(report -\u003e qb.and(\n                    qb.equal(report.get(Employee_.manager), manager),\n                    qb.greaterThan(report.get(Employee_.salary), 100000.0)))\n                .exists());\n    }\n```\n\nNote the subquery correlation was done \"manually\" using `CriteriaBuilder.equal()`; you can clean this up a bit with an explicit correlation using `substream()`:\n\n```java\n    public List\u003cEmployee\u003e findManagersWithSixFigureDirectReport() {\n        return qb.stream(Employee.class)\n          .filter(manager -\u003e qb.substream(manager)\n            .flatMap(Employee_.directReports)\n            .filter(report -\u003e qb.greaterThan(report.get(Employee_.salary), 100000.0))\n            .exists());\n    }\n```\n\nTo find all employees with salary greater than the average of their manager's direct reports:\n\n```java\n    public List\u003cEmployee\u003e findEmployeesWithAboveAverageSalaries() {\n        return qb.stream(Employee.class)\n          .filter(employee -\u003e qb.greaterThan(\n              employee.get(Employee_.salary),\n              qb.stream(Employee.class)\n                .filter(coworker -\u003e\n                  qb.equal(\n                    coworker.get(Employee_.manager),\n                    employee.get(Employee_.manager)))\n                .mapToDouble(Employee_.salary)\n                .average()\n                .asSubquery()))\n          .getResultList();\n    }\n```\n\nHmmm, that's a lot of nesting. You could make the code clearer by building the subquery separately, and using a reference for the correlation:\n\n```java\n    public DoubleValue avgCoworkerSalary(RootRef\u003cEmployee\u003e employee) {\n        return qb.stream(Employee.class)\n          .filter(coworker -\u003e qb.equal(coworker.get(Employee_.manager), employee.get().get(Employee_.manager)))\n          .mapToDouble(Employee_.salary)\n          .average();\n    }\n\n    public List\u003cEmployee\u003e findEmployeesWithAboveAverageSalaries() {\n        final RootRef\u003cEmployee\u003e employee = new RootRef\u003c\u003e();\n        return qb.stream(Employee.class)\n          .bind(employee)\n          .filter(e -\u003e qb.greaterThan(e.get(Employee_.salary), this.avgCoworkerSalary(employee).asSubquery()))\n          .getResultList();\n    }\n```\nThat's really just regular Java refactoring.\n\n## Multiselect and Grouping\n\nTo select multiple items, or construct a Java instance, use `mapToSelection()`.\n\nFor grouping, use `groupBy()` and `having()`.\n\nHere's an example that finds all managers paired with the average salary of their direct reports, where that average salary is at least $50,000, sorted by average salary descending:\n\n```java\n    public List\u003cObject[]\u003e getHighPayrollManagers2() {\n\n        // Create references\n        final RootRef\u003cEmployee\u003e manager = new RootRef\u003c\u003e();\n        final ExprRef\u003cDouble\u003e avgSalary = new ExprRef\u003c\u003e();\n\n        // Build and execute stream\n        return qb.stream(Employee.class)\n          .bind(manager)\n          .flatMap(Employee_.directReports)\n          .mapToDouble(Employee_.salary)\n          .average()\n          .bind(avgSalary)\n          .groupBy(manager)\n          .mapToSelection(Object[].class, e -\u003e qb.array(manager.get(), avgSalary.get()))\n          .orderBy(avgSalary, false);\n          .having(avgSalary -\u003e qb.gt(avgSalary, 50000.0))\n          .getResultList();\n    }\n```\n\n## Offset and Limit\n\nUse `skip()` and `limit()` to set the row offset and the maximum number of results.\n\n## `CriteriaQuery` vs `TypedQuery` Operations\n\nNormally with JPA you first configure a `CriteriaQuery`, then use it to create a `TypedQuery`.\n\nBoth the `CriteriaQuery` and the `TypedQuery` have their own configuration.\n\nFor example, `where()` is a `CriteriaQuery` method, but `setLockMode()` is a `TypedQuery` method.\n\nSo at the end of the day, we must apply configuration to the appropriate object for that configuration.\n\n`QueryStream` pipelines allow you to configure both `CriteriaQuery` and `TypedQuery` information, but you should be aware of these caveats:\n\n  * Any `TypedQuery` configuration must come at the end of the pipeline (after any subqueries or joins)\n  * If you invoke `QueryStream.toCriteriaQuery()`, the returned object does not capture any `TypedQuery` configuration\n  * To capture the `TypedQuery` configuration as well, use `QueryStream.toQuery()`.\n\nThe `QueryStream` methods that configure the `TypedQuery` are:\n\n * `QueryStream.skip()`\n * `QueryStream.limit()`\n * `QueryStream.withFlushMode()`\n * `QueryStream.withLockMode()`\n * `QueryStream.withFetchGraph()`\n * `QueryStream.withLoadGraph()`\n * `QueryStream.withHint()` and `QueryStream.withHints()`\n\n## Unsupported Operations\n\nIn some cases, limitations in the JPA Criteria API impose certain restrictions on what you can do.\n\nFor example, `skip()` and `limit()` must be at the end of your pipeline, because the JPA Criteria API doesn't allow setting row offset or the maximum results on a subquery or prior to a join.\n\nFor another example, you can't sort in a subquery.\n\n### Installation\n\nQueryStream is available from [Maven Central](http://search.maven.org/#search|ga|1|a%3Aquerystream-jpa):\n\n```xml\n    \u003cdependency\u003e\n        \u003cgroupId\u003eorg.dellroad\u003c/groupId\u003e\n        \u003cartifactId\u003equerystream-jpa\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n```\n\n### API Javadocs\n\nLocated [here](https://querystream.github.io/querystream/site/apidocs/index.html?org/dellroad/querystream/jpa/QueryStream.Builder.html).\n","funding_links":[],"categories":["Projects","项目"],"sub_categories":["Database","数据库"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquerystream%2Fquerystream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fquerystream%2Fquerystream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fquerystream%2Fquerystream/lists"}