{"id":37435071,"url":"https://github.com/blinkfox/fenix","last_synced_at":"2026-01-16T06:39:14.539Z","repository":{"id":40625916,"uuid":"200255059","full_name":"blinkfox/fenix","owner":"blinkfox","description":"This is an extension library to the Spring Data JPA complex or dynamic SQL query. 这是一个比 MyBatis 更加强大的 Spring Data JPA 扩展库，为解决复杂动态 JPQL (或 SQL) 而生。https://blinkfox.github.io/fenix","archived":false,"fork":false,"pushed_at":"2025-11-28T05:08:09.000Z","size":1200,"stargazers_count":355,"open_issues_count":23,"forks_count":78,"subscribers_count":13,"default_branch":"develop","last_synced_at":"2025-11-30T15:58:18.348Z","etag":null,"topics":["dynamic-sql","fenix","fenix-spring-boot-starter","jpa-extension","jpa-plus","mybatis","spring-data-jpa"],"latest_commit_sha":null,"homepage":"https://blinkfox.github.io/fenix","language":"Java","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/blinkfox.png","metadata":{"files":{"readme":"README.md","changelog":null,"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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-08-02T15:08:37.000Z","updated_at":"2025-10-13T12:52:50.000Z","dependencies_parsed_at":"2025-07-20T04:29:33.218Z","dependency_job_id":null,"html_url":"https://github.com/blinkfox/fenix","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/blinkfox/fenix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blinkfox%2Ffenix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blinkfox%2Ffenix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blinkfox%2Ffenix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blinkfox%2Ffenix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/blinkfox","download_url":"https://codeload.github.com/blinkfox/fenix/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/blinkfox%2Ffenix/sbom","scorecard":{"id":243447,"data":{"date":"2025-08-11","repo":{"name":"github.com/blinkfox/fenix","commit":"3ad2857caddb0f9b3ecaf1d1bca36cd1e301f6cb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 2/24 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":8,"reason":"9 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 8","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T07:03:36.233Z","repository_id":40625916,"created_at":"2025-08-17T07:03:36.233Z","updated_at":"2025-08-17T07:03:36.233Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28477913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["dynamic-sql","fenix","fenix-spring-boot-starter","jpa-extension","jpa-plus","mybatis","spring-data-jpa"],"created_at":"2026-01-16T06:39:13.952Z","updated_at":"2026-01-16T06:39:14.532Z","avatar_url":"https://github.com/blinkfox.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🔥 Fenix\n\n\u003cdiv align=\"center\"\u003e\u003cimg style=\"display: block; margin: 0 auto;\" src=\"https://blinkfox.github.io/fenix/assets/images/logo.png\" alt=\"fenix logo\" /\u003e\u003c/div\u003e\n\n[![Javadocs](http://www.javadoc.io/badge/com.blinkfox/fenix.svg)](http://www.javadoc.io/doc/com.blinkfox/fenix) [![GitHub license](https://img.shields.io/github/license/blinkfox/fenix.svg)](https://github.com/blinkfox/fenix/blob/develop/LICENSE) [![fenix](https://img.shields.io/badge/fenix-v4.0.0-blue)](https://search.maven.org/artifact/com.blinkfox/fenix/4.0.0/jar) [![fenix starter](https://img.shields.io/badge/fenix%20spring%20boot%20starter-v4.0.0-blue)](https://search.maven.org/artifact/com.blinkfox/fenix-spring-boot-starter/4.0.0/jar) [![codecov](https://codecov.io/gh/blinkfox/fenix/branch/develop/graph/badge.svg)](https://codecov.io/gh/blinkfox/fenix)\n\n\u003e [🔥 Fenix](https://github.com/blinkfox/fenix)（菲尼克斯）是一个为了解决复杂动态 SQL (`JPQL`) 而生的 `Spring Data JPA` 扩展库，能辅助开发者更方便快捷的书写复杂、动态且易于维护的 SQL，支持 ActiveRecord 模式和多种查询方式。\n\n[📖 使用文档](https://blinkfox.github.io/fenix) | [✨ Intellij lIDEA 插件](https://plugins.jetbrains.com/plugin/17158-fenix) | [🍉 示例项目 (fenix-example)](https://github.com/blinkfox/fenix-example)\n\n## 💎 一、特性\n\n- 简单、轻量级、无副作用的集成和使用，jar 包仅 `224 KB`；\n- 作为 JPA 的扩展和增强，兼容 Spring Data JPA 原有功能和各种特性；\n- 提供了 `XML`、Java 链式 `API` 和动态条件注解等四种方式来书写动态 SQL；\n- 支持 `ActiveRecord` 模式；\n- `XML` 的方式功能强大，让 SQL 和 Java 代码解耦，易于维护；\n- 可以采用 Java 链式 `API` 来书写动态 SQL；\n- 可以采用动态条件注解和Java 链式 `API` 来书写出动态的 `Specification`；\n- 增强了更快速高效的 JPA 批量“增删改”的支持，支持非 `null` 属性的增量更新；\n- 支持**雪花算法**和 `NanoId` 的主键 ID 生成策略；\n- SQL 执行结果可返回任意自定义的实体对象，支持多种结果转换方式，比使用 JPA 自身的投影方式更加简单；\n- 具有可扩展性，如：可自定义 `XML` 语义标签和对应的标签处理器来生成自定义逻辑的 SQL 片段和参数；\n\n## 🏖️️ 二、支持场景\n\n适用于 Java `Spring Data JPA` 项目，`JDK 8` 及以上（高版本 SpringBoot 要求 `JDK17`），Spring Data JPA 的版本须保证 `2.1.8.RELEASE` 及以上；如果你是 Spring Boot 项目，则 Spring Boot 的版本须保证 `2.1.5.RELEASE` 及以上。\n\n## ☘️ 三、Spring Boot 项目集成\n\n如果你是 Spring Boot 项目，那么直接集成 `fenix-spring-boot-starter` 库，并使用 `@EnableFenix` 激活 Fenix 的相关配置信息。\n\n如果你**不是 Spring Boot 项目**，请参看[这里](https://blinkfox.github.io/fenix/#/quick-install?id=not-spring-boot-project) 的配置方式。\n\n\u003e **注**：请确保你使用的 Spring Boot 版本是 **`v2.1.5.RELEASE` 及以上**。\n\u003e - 如果 Spring Boot 版本是 `v2.2.x.RELEASE` 及以上，则 Fenix 版本必须是 `v2.x` 的版本。\n\u003e - 如果 Spring Boot 版本是 `v3.x` 的版本，则 Fenix 版本必须是 `v3.x` 的版本。\n\u003e - 如果 Spring Boot 版本是 `v4.x` 的版本，则 Fenix 版本必须是 `v4.x` 的版本。\n\n### 🌾 1. Maven\n\n```xml\n\u003c!-- Spring Boot 版本要求 4.0 及以上. --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.blinkfox\u003c/groupId\u003e\n    \u003cartifactId\u003efenix-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e4.0.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Spring Boot 版本要求 3.x 版本. --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.blinkfox\u003c/groupId\u003e\n    \u003cartifactId\u003efenix-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e3.1.0\u003c/version\u003e\n\u003c/dependency\u003e\n\n\u003c!-- Spring Boot 版本要求 2.x 版本. --\u003e\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.blinkfox\u003c/groupId\u003e\n    \u003cartifactId\u003efenix-spring-boot-starter\u003c/artifactId\u003e\n    \u003cversion\u003e2.7.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### 🌵 2. Gradle\n\n```bash\n// Spring Boot 版本要求 4.x 版本.\nimplementation(\"com.blinkfox:fenix-spring-boot-starter:4.0.0\")\n\n// Spring Boot 版本要求 3.x 版本.\nimplementation(\"com.blinkfox:fenix-spring-boot-starter:3.1.0\")\n\n// Spring Boot 版本要求 2.x 版本.\nimplementation(\"com.blinkfox:fenix-spring-boot-starter:2.7.0\")\n```\n\n### 🏕️ 3. 激活 Fenix (@EnableFenix)\n\n然后需要在你的 Spring Boot 应用中使用 `@EnableFenix` 激活 Fenix 的相关配置信息。\n\n```java\n/**\n * 请在 Spring Boot 应用中标注 {code @EnableFenix} 注解.\n *\n * @author blinkfox on 2020-02-01.\n */\n@EnableFenix\n@SpringBootApplication\npublic class DemoApplication {\n\n    public static void main(String[] args) {\n        SpringApplication.run(DemoApplication.class, args);\n    }\n}\n```\n\n\u003e **💡 注**： \n\u003e 1. `@EnableFenix` 注解中实质上是使用的是 `FenixJpaRepositoryFactoryBean`。而 `FenixJpaRepositoryFactoryBean` 继承自 Spring Data JPA 默认的 `JpaRepositoryFactoryBean`。所以，Fenix 与 JPA 的各种注解和特性完全兼容，并提供了更加强大的 `@QueryFenix` 注解和其他更多动态的能力。\n\u003e 2. 如果你是多数据源，则你可以根据自身情况，在需要的数据源中的 `@EnableJpaRepositories` 注解中单独设置 `repositoryFactoryBeanClass` 的值为：`FenixJpaRepositoryFactoryBean.class`。示例如：`@EnableJpaRepositories(repositoryFactoryBeanClass = FenixJpaRepositoryFactoryBean.class)`。\n\n### 🏝️ 4. application.yml 配置（可选的）\n\n\u003e **注**：Fenix 采用了**约定优于配置**的方式，所以通常情况下，你可以不用做任何的 Fenix 配置。\n\n如果你要修改 Fenix 的配置信息，你需要在你的 Spring Boot 项目中，在 `application.yml` 或者 `application.properties` 中去修改配置信息。\n\n以下通过 `application.yml` 文件来展示 Fenix 中的几个配置项、默认值和说明信息，供你参考。\n\n```yaml\n# Fenix 的几个配置项、默认值及详细说明，通常情况下你不需要填写这些配置信息（下面的配置代码也都可以删掉）.\nfenix:\n  # 成功加载 Fenix 配置信息后，是否打印启动 banner，默认 true.\n  print-banner: true\n  # 是否打印 Fenix 生成的 SQL 信息，默认为空.\n  # 当该值为空时，会读取 'spring.jpa.show-sql' 的值，为 true 就打印 SQL 信息，否则不打印.\n  # 当该值为 true 时，就打印 SQL 信息，否则不打印. 生产环境不建议设置为 true.\n  print-sql:\n  # 扫描 Fenix XML 文件的所在位置，默认是 fenix 目录及子目录，可以用 yaml 文件方式配置多个值.\n  xml-locations: fenix\n  # 扫描你自定义的 XML 标签处理器的位置，默认为空，可以是包路径，也可以是 Java 或 class 文件的全路径名\n  # 可以配置多个值，不过一般情况下，你不自定义自己的 XML 标签和处理器的话，不需要配置这个值.\n  handler-locations:\n  # v2.2.0 版本新增的配置项，表示自定义的继承自 AbstractPredicateHandler 的子类的全路径名\n  # 可以配置多个值，通常情况下，你也不需要配置这个值.\n  predicate-handlers:\n  # v2.7.0 新增的配置项，表示带前缀下划线转换时要移除的自定义前缀，多个值用英文逗号隔开，通常你不用配置这个值.\n  underscore-transformer-prefix:\n```\n\n## 🍔 四、示例概览\n\nFenix 中支持四种方式书写动态 SQL，分别是：\n\n- 基于 JPQL (或 SQL) 的 XML 方式\n- 基于 JPQL (或 SQL) 的 Java API 方式\n- 基于 `Specification` 的 Java API 方式\n- 基于 `Specification` 的 Java Bean 注解方式\n\n以下的四种方式的示例均以博客信息数据作为示例，你可以根据自己的场景或喜欢的方式来选择动态查询的方式。关于详细的使用文档可以[参看文档](https://blinkfox.github.io/fenix/#/)。\n\n### 1. 🍖 基于 JPQL (或 SQL) 的 XML 方式\n\n在 `BlogRepository` 中的查询方法使用 `QueryFenix` 注解，用来分页查询博客信息数据：\n\n```java\n/**\n * BlogRepository.\n *\n * @author blinkfox on 2019-08-16.\n */\npublic interface BlogRepository extends JpaRepository\u003cBlog, String\u003e {\n\n    /**\n     * 使用 {@link QueryFenix} 注解来演示根据散参数、博客信息Bean(可以是其它Bean 或者 Map)来多条件模糊分页查询博客信息.\n     *\n     * @param ids 博客信息 ID 集合\n     * @param blog 博客信息实体类，可以是其它 Bean 或者 Map.\n     * @param pageable JPA 分页排序参数\n     * @return 博客分页信息\n     */\n    @QueryFenix\n    Page\u003cBlog\u003e queryMyBlogs(@Param(\"ids\") List\u003cString\u003e ids, @Param(\"blog\") Blog blog, Pageable pageable);\n\n}\n```\n\n在 `BlogRepository.xml` 文件中，定义一个跟查询方法同名的 fenix 节点，内容如下：\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003c!-- 这是用来操作博客信息的 Fenix XML 文件，请填写 namespace 命名空间. --\u003e\n\u003cfenixs namespace=\"com.blinkfox.fenix.example.repository.BlogRepository\"\u003e\n\n    \u003c!-- 这是一条完整的 Fenix 查询语句块，必须填写 fenix 标签的 id 属性. --\u003e\n    \u003cfenix id=\"queryMyBlogs\"\u003e\n        SELECT\n            b\n        FROM\n            Blog AS b\n        WHERE\n        \u003cin field=\"b.id\" value=\"ids\" match=\"ids != empty\"/\u003e\n        \u003candLike field=\"b.author\" value=\"blog.author\" match=\"blog.author != empty\"/\u003e\n        \u003candLike field=\"b.title\" value=\"blog.title\" match=\"blog.title != empty\"/\u003e\n        \u003candBetween field=\"b.createTime\" start=\"blog.createTime\" end=\"blog.updateTime\" match=\"(?blog.createTime != empty) || (?blog.updateTime != empty)\"/\u003e\n    \u003c/fenix\u003e\n\n\u003c/fenixs\u003e\n```\n\n下面是 `queryMyBlogs` 接口方法的单元测试：\n\n```java\n/**\n * 测试使用 {@link QueryFenix} 注解根据任意参数多条件模糊分页查询博客信息.\n */\n@Test\npublic void queryMyBlogs() {\n    // 模拟构造查询的相关参数.\n    List\u003cString\u003e ids = Arrays.asList(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\");\n    Blog blog = new Blog().setAuthor(\"ZhangSan\").setUpdateTime(new Date());\n    Pageable pageable = PageRequest.of(0, 3, Sort.by(Sort.Order.desc(\"createTime\")));\n\n    // 查询并断言查询结果的正确性.\n    Page\u003cBlog\u003e blogs = blogRepository.queryMyBlogs(ids, blog, pageable);\n    Assert.assertEquals(4, blogs.getTotalElements());\n    Assert.assertEquals(3, blogs.getContent().size());\n}\n```\n\n### 2. 🍟 基于 JPQL (或 SQL) 的 Java API 方式\n\n在 `BlogRepository` 中的查询方法使用 `QueryFenix` 注解，用来查询所有符合条件的博客信息数据：\n\n```java\npublic interface BlogRepository extends JpaRepository\u003cBlog, String\u003e {\n\n    /**\n     * 使用 {@link QueryFenix} 注解和 Java API 来拼接 SQL 的方式来查询博客信息.\n     *\n     * @param blog 博客信息实体\n     * @param startTime 开始时间\n     * @param endTime 结束时间\n     * @param blogIds 博客 ID 集合\n     * @return 用户信息集合\n     */\n    @QueryFenix(provider = BlogSqlProvider.class)\n    List\u003cBlog\u003e queryBlogsWithJava(@Param(\"blog\") Blog blog, @Param(\"startTime\") Date startTime,\n            @Param(\"endTime\") Date endTime, @Param(\"blogIds\") String[] blogIds);\n\n}\n```\n\n创建 `BlogSqlProvider` 类，定义一个与查询方法同名的方法 `queryBlogsWithJava` 方法，用来使用 Java 的方式来动态拼接 JPQL (或 SQL) 语句。\n\n```java\npublic class BlogSqlProvider {\n\n    /**\n     * 通过 Java API 来拼接得到 {@link SqlInfo} 的方式来查询博客信息.\n     *\n     * @param blogIds 博客 ID 集合\n     * @param blog 博客信息实体\n     * @param startTime 开始时间\n     * @param endTime 结束时间\n     * @return {@link SqlInfo} 示例\n     */\n    public SqlInfo queryBlogsWithJava(@Param(\"blogIds\") String[] blogIds, @Param(\"blog\") Blog blog,\n            @Param(\"startTime\") Date startTime, @Param(\"endTime\") Date endTime) {\n        return Fenix.start()\n                .select(\"b\")\n                .from(\"Blog\").as(\"b\")\n                .where()\n                .in(\"b.id\", blogIds, CollectionHelper.isNotEmpty(blogIds))\n                .andLike(\"b.title\", blog.getTitle(), StringHelper.isNotBlank(blog.getTitle()))\n                .andLike(\"b.author\", blog.getAuthor(), StringHelper.isNotBlank(blog.getAuthor()))\n                .andBetween(\"b.createTime\", startTime, endTime, startTime != null || endTime != null)\n                .end();\n    }\n\n}\n```\n\n下面是 `queryBlogsWithJava` 接口方法的单元测试：\n\n```java\n/**\n * 测试使用 {@link QueryFenix} 注解和 Java API 来拼接 SQL 的方式来查询博客信息.\n */\n@Test\npublic void queryBlogsWithJava() {\n    // 构造查询的相关参数.\n    String[] ids = new String[]{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"};\n    Blog blog = new Blog().setAuthor(\"ZhangSan\");\n    Date startTime = Date.from(LocalDateTime.of(2019, Month.APRIL, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant());\n    Date endTime = Date.from(LocalDateTime.of(2019, Month.OCTOBER, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant());\n\n    // 查询并断言查询结果的正确性.\n    List\u003cBlog\u003e blogs = blogRepository.queryBlogsWithJava(blog, startTime, endTime, ids);\n    Assert.assertEquals(3, blogs.size());\n}\n```\n\n### 3. 🍭 基于 Specification 的 Java API 方式\n\n基于 `Specification` 的方式，只须要 `BlogRepository` 接口继承 `FenixJpaSpecificationExecutor` 接口即可。\n\n```java\n// JpaRepository\u003cBlog, String\u003e 和 FenixJpaSpecificationExecutor\u003cBlog\u003e 可以混用，也可以只使用某一个.\npublic interface BlogRepository extends JpaRepository\u003cBlog, String\u003e, FenixJpaSpecificationExecutor\u003cBlog\u003e {\n\n}\n```\n\n基于 `Specification` 的方式，不需要定义额外的查询方法，也不需要写 `JPQL` (或 SQL) 语句，简单直接。下面是通过 Java 链式的 API 方式来做单元测试的使用方式示例：\n\n```java\n/**\n * 测试使用 Fenix 中的  {@link FenixSpecification} 的链式 Java API 来动态查询博客信息.\n */\n@Test\npublic void queryBlogsWithSpecifition() {\n    // 这一段代码是在模拟构造前台传递查询的相关 map 型参数，当然也可以使用其他 Java 对象，作为查询参数.\n    Map\u003cString, Object\u003e params = new HashMap\u003c\u003e();\n    params.put(\"ids\", new String[]{\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"});\n    params.put(\"author\", \"ZhangSan\");\n    params.put(\"startTime\", Date.from(LocalDateTime.of(2019, Month.APRIL, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant()));\n    params.put(\"endTime\", Date.from(LocalDateTime.of(2019, Month.OCTOBER, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant()));\n\n    // 开始真正的查询，使用.\n    Object[] ids = (Object[]) params.get(\"ids\");\n    List\u003cBlog\u003e blogs = blogRepository.findAll(builder -\u003e\n            builder.andIn(\"id\", ids, ids != null \u0026\u0026 ids.length \u003e 0)\n                    .andLike(\"title\", params.get(\"title\"), params.get(\"title\") != null)\n                    .andLike(\"author\", params.get(\"author\"))\n                    .andBetween(\"createTime\", params.get(\"startTime\"), params.get(\"endTime\"))\n            .build());\n\n    // 单元测试断言查询结果的正确性.\n    Assert.assertEquals(3, blogs.size());\n    blogs.forEach(blog -\u003e Assert.assertTrue(blog.getAuthor().endsWith(\"ZhangSan\")));\n}\n```\n\n### 4. 🥯 基于 Specification 的 Java Bean 注解方式\n\n本方式是指通过将 Java Bean 作为参数传递，在 Java Bean 对象的属性中通过查询的条件注解来表明是何种查询匹配方式。当然，同第三种方式一样，`BlogRepository` 接口也须要继承 `FenixJpaSpecificationExecutor` 接口。\n\n```java\n// JpaRepository\u003cBlog, String\u003e 和 FenixJpaSpecificationExecutor\u003cBlog\u003e 可以混用，也可以只使用某一个.\npublic interface BlogRepository extends JpaRepository\u003cBlog, String\u003e, FenixJpaSpecificationExecutor\u003cBlog\u003e {\n\n}\n```\n\n然后，定义一个用于表示各种查询条件的普通 Java Bean 类 `BlogParam`，当然该类也可以是前台传递过来的对象参数，也可以单独定义。该类的各个属性对应某个查询字段，属性上的注解对应查询的匹配方式，某个字段是否生成查询条件的默认判断依据是该属性值是否为空。\n\n```java\nimport com.blinkfox.fenix.specification.annotation.Between;\nimport com.blinkfox.fenix.specification.annotation.In;\nimport com.blinkfox.fenix.specification.annotation.Like;\nimport com.blinkfox.fenix.specification.handler.bean.BetweenValue;\n\nimport java.util.Date;\nimport java.util.List;\n\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.experimental.Accessors;\n\n/**\n * 用于测试 {@code FenixSpecification} 注解动态查询的博客 VO 类.\n *\n * @author blinkfox on 2020-01-28.\n */\n@Getter\n@Setter\n@Accessors(chain = true)\npublic class BlogParam {\n\n    /**\n     * 用于 IN 范围查询的 ID 集合，{@link In} 注解的属性值可以是数组，也可以是 {@link java.util.Collection} 集合数据.\n     */\n    @In(\"id\")\n    private List\u003cString\u003e ids;\n\n    /**\n     * 模糊查询博客信息的作者名称关键字内容的字符串.\n     */\n    @Like\n    private String author;\n\n    /**\n     * 用于根据博客创建时间 {@link Between} 区间查询博客信息的开始值和介绍值，\n     * 区间查询的值类型建议是 {@link BetweenValue} 类型的.\n     * 当然值类型也可以是二元数组，也可以是 {@link List} 集合，如果是这两种类型的值，元素的顺序必须是开始值和结束值才行.\n     */\n    @Between(\"createTime\")\n    private BetweenValue\u003cDate\u003e createTime;\n\n}\n```\n\n下面是单元测试的使用方式示例：\n\n```java\n/**\n * 测试使用 Fenix 中的  {@link FenixSpecification} 的 Java Bean 条件注解的方式来动态查询博客信息.\n */\n@Test\npublic void queryBlogsWithAnnotaion() {\n    // 这一段代码是在模拟构造前台传递的或单独定义的 Java Bean 对象参数.\n    Date startTime = Date.from(LocalDateTime.of(2019, Month.APRIL, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant());\n    Date endTime = Date.from(LocalDateTime.of(2019, Month.OCTOBER, 8, 0, 0, 0)\n            .atZone(ZoneId.systemDefault()).toInstant());\n    BlogParam blogParam = new BlogParam()\n            .setIds(Arrays.asList(\"1\", \"2\", \"3\", \"4\", \"5\", \"6\", \"7\", \"8\"))\n            .setAuthor(\"ZhangSan\")\n            .setCreateTime(BetweenValue.of(startTime, endTime));\n\n    // 开始真正的查询.\n    List\u003cBlog\u003e blogs = blogRepository.findAllOfBean(blogParam);\n\n    // 单元测试断言查询结果的正确性.\n    Assert.assertEquals(3, blogs.size());\n    blogs.forEach(blog -\u003e Assert.assertTrue(blog.getAuthor().endsWith(\"ZhangSan\")));\n}\n```\n\n## 🙋 五、其他贡献者 :id=contributor\n\n感谢如下贡献者，没有他们， `Fenix` 不会如此完美。\n\n- [@pengten](https://github.com/pengten)\n- [@jgaybjone](https://github.com/jgaybjone)\n- [@imhansai](https://github.com/imhansai)\n\n## 🙏 六、鸣谢\n\n感谢 [JetBrains 公司](https://www.jetbrains.com/?from=fenix) 为本开源项目提供的免费正版 Intellij IDEA 的 License 支持。\n\n## 📝 七、开源许可证\n\n本 `Fenix` 的 Spring Data JPA 扩展库遵守 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) 许可证。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblinkfox%2Ffenix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fblinkfox%2Ffenix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fblinkfox%2Ffenix/lists"}