From 897b23811e0c40589cbc8edb373cfc20aaa118a8 Mon Sep 17 00:00:00 2001 From: blinkfox Date: Sat, 2 Jan 2021 20:50:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=92=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E4=BA=86=E9=83=A8=E5=88=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/CHANGELOG.md | 39 ++--- docs/_coverpage.md | 2 +- docs/_sidebar.md | 5 +- docs/cud/fast-batch-cud.md | 14 +- docs/cud/id-generator.md | 12 +- docs/cud/not-null-update.md | 19 +-- docs/more-features.md | 37 ++++- docs/more/custom-entity.md | 153 ++++++++++++++++++ docs/more/debug-mode.md | 26 +++ docs/more/other-features.md | 124 ++++++++++++++ docs/quick-install.md | 17 +- docs/xml/logic-control.md | 22 ++- docs/xml/xml-tags.md | 2 + .../fenix/core/FenixXmlBuilderTest.java | 10 ++ src/test/resources/fenix/fenix.xml | 12 ++ 15 files changed, 438 insertions(+), 56 deletions(-) create mode 100644 docs/more/custom-entity.md create mode 100644 docs/more/debug-mode.md create mode 100644 docs/more/other-features.md diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d12565a..f9725a1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,70 +1,71 @@ -# 🍹 版本更新记录 +# 🍹 版本更新记录 :id=title -## v2.4.1 计划 +## ⛳ v2.4.1 新增了 debug 模式和 bug 修复 🆕 (2021-01-02) :id=v241 -- 新增回了 `debug` 模式; -- 修复 `` 标签中的一些 bug;(完成) +- 新增了 `debug` 模式,开启之后,可以在不重启服务的情况下,实时读取和解析 XML 文件中的 SQL; +- 修复 `` 标签中在混合使用了逻辑控制语法和 XML 标签语法时,去除 `AND` 前缀的一些 bug; +- 升级了 `mvel` 的依赖到最新小版本; -## 🥊 v2.4.0 增强 JPA 的增删改功能 🆕 (2020-12-09) +## 🥊 v2.4.0 增强 JPA 的增删改功能 (2020-12-09) :id=v240 - 新增了更快速高效的 JPA 批量“增删改”的支持; - 新增了增量更新的功能,即更新时只更新实体类中属性值不为 `null` 的字段; - 新增了三种主键 ID 生成策略类; - 修改或升级了相关依赖包的版本,如:去除了 `dom4j` 的显示依赖,因为 JPA Hibernate 中已经传递依赖了它; -## 🏓 v2.3.6 小功能改进版本 (2020-08-27) +## 🏓 v2.3.6 小功能改进版本 (2020-08-27) :id=v236 - 新增了 `@EnableFenix` 注解中更多的配置信息,与 `@EnableJpaRepositories` 注解相对应; - 修改了求 `COUNT` 的 SQL 不支持 `DISTINCT` 的问题; -## 🎳 v2.3.5 修复了在老版本 JPA 中某些情况下的 bug (2020-07-31) +## 🎳 v2.3.5 修复了在老版本 JPA 中某些情况下的 bug (2020-07-31) :id=v235 - 修复了在老版本 JPA 中,某些情况下出现 Javaassist 的 `ClassNotFoundException` 的问题; -## 🥏 v2.3.4 升级了相关依赖包的版本 (2020-07-26) +## 🥏 v2.3.4 升级了相关依赖包的版本 (2020-07-26) :id=v234 - 升级了相关依赖包的版本,如:`dom4j`、`mvel` 等; -## 🏈 v2.3.3 修复多线程下同一个接口方法的线程安全问题(强烈推荐升级) (2020-07-03) +## 🏈 v2.3.3 修复多线程下同一个接口方法的线程安全问题(强烈推荐升级) (2020-07-03) :id=v233 - 修复了在多线程情况下执行同一个 `repository` 接口方法时,可能出现参数混淆的线程安全问题; -## 🏀 v2.3.2 修复某些情况下 JDBC 连接未释放的问题(强烈推荐升级) (2020-05-28) +## 🏀 v2.3.2 修复某些情况下 JDBC 连接未释放的问题(强烈推荐升级) (2020-05-28) :id=v232 - 修复了在异步多线程情况下,返回自定义实体 Bean 类型时,JDBC 连接未释放的问题,老版本可以使用 `@Transactional` 注解解决; -## 🥎 v2.3.1 兼容支持最新 v2.3.0 版本的 Spring Data JPA (2020-05-18) +## 🥎 v2.3.1 兼容支持最新 v2.3.0 版本的 Spring Data JPA (2020-05-18) :id=v231 - 支持最新的 Spring Data JPA 版本(`v2.3.0`),同时也能兼容之前的 Spring Data JPA 版本; -## ⚽ v2.3.0 新增了基于 JPQL 方式时自定义命名参数名称的相关 API 和属性 (2020-05-09) +## ⚽ v2.3.0 新增了基于 JPQL 方式时自定义命名参数名称的相关 API 和属性 (2020-05-09) :id=v230 - 新增了基于 JPQL 的 XML 方式中的多个 XML 标签中,可以自定义命名参数名称的 `name` 属性; - 新增了基于 JPQL 的 Java API 方式中可以自定义命名参数名称的 `name` 参数相关的 API 方法; -## ⚾ v2.2.0 新增了基于 Specification 的动态注解和 Java 链式 API (2020-02-02) +## ⚾ v2.2.0 新增了基于 Specification 的动态注解和 Java 链式 API (2020-02-02) :id=v220 - 新增了基于 `Specification` 的动态条件注解来动态查询数据; - 新增了基于 `Specification` 的 Java 链式 API 来动态查询数据; - 新增了 `@EnableFenix` 注解来配置 JPA 可以使用 Fenix 的相关 API; -## 🏅 v2.1.0 新增 标签和对应的 Java API (2019-11-21) +## 🏅 v2.1.0 新增 标签和对应的 Java API (2019-11-21) :id=v210 - 新增了 `` 标签和动态 `where` 的 Java API,用来消除在全动态 SQL 中场景中,`WHERE` 关键字后的 `AND` 或者 `OR` 关键字; -## 🏆 v2.0.0 支持 Spring Boot 的 2.2.0.RELEASE 版本 (2019-10-10) +## 🏆 v2.0.0 支持 Spring Boot 的 2.2.0.RELEASE 版本 (2019-10-10) :id=v200 - 支持 Spring Boot 和 Spring Data JPA 的 `2.2.0.RELEASE`及以上的版本,同时也兼容之前的版本; -## 🎖️ v1.1.1 求分页 count 小调整 (2019-10-10) +## 🎖️ v1.1.1 求分页 count 小调整 (2019-10-10) :id=v111 - **修改**了默认求总记录数 `count(*)` 时的 SQL 为 `count(*) as count`,即增加了 `as` 列; -## 🎗️ v1.1.0 新增返回任意实体对象或集合 (2019-10-10) +## 🎗️ v1.1.0 新增返回任意实体对象或集合 (2019-10-10) :id=v110 - **新增**了**返回任意实体对象**或集合的功能,相比使用投影的方式更为简单和自然; -## 🤹‍♂️ v1.0.1 bug 修复及小功能调整 (2019-09-01) +## 🤹‍♂️ v1.0.1 bug 修复及小功能调整 (2019-09-01) :id=v101 - **新增**了可以使用 `spring.jpa.show-sql` 的配置来作为是否打印 SQL 信息的依据之一; - **新增**了部分类的单元测试,提高了单元测试覆盖率; @@ -72,6 +73,6 @@ - **修复**了 Spring Boot web 项目打成 `jar` 包时读取不到 Fenix `XML` 文件的问题; - **删除**了 Fenix `debug` 模式的功能; -## 🎪 v1.0.0 第一个里程碑正式版 (2019-08-19) +## 🎪 v1.0.0 第一个里程碑正式版 (2019-08-19) :id=v100 - **新增**了 `Fenix` JPA 扩展库的核心功能; diff --git a/docs/_coverpage.md b/docs/_coverpage.md index dff2905..a9ceb7b 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -1,6 +1,6 @@ ![logo](assets/images/logo.png) -# Fenix 2.4.0 +# Fenix 2.4.1 > 为解决复杂动态 SQL 而生的 Spring Data JPA 扩展库 diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 81e4a37..41163e5 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -23,5 +23,8 @@ - [🚀 更高效的【批量增删改】](cud/fast-batch-cud) - [🚁 非【null】属性的增量更新](cud/not-null-update) - [✈️ 更多的主键 ID 生成策略](cud/id-generator) -- [🍬 更多功能](more-features) +- 🍬 更多功能 + - [🦋 返回自定义的实体对象](more/custom-entity) + - [🐛 调试 (debug) 模式](more/debug-mode) + - [🐝 其他功能](more/other-features) - [🍹 版本更新记录](CHANGELOG) diff --git a/docs/cud/fast-batch-cud.md b/docs/cud/fast-batch-cud.md index ca2d832..15150f2 100644 --- a/docs/cud/fast-batch-cud.md +++ b/docs/cud/fast-batch-cud.md @@ -1,6 +1,6 @@ -# 🚀 更高效的【批量增删改】 +# 🚀 更高效的【批量增删改】 :id=more-efficient-cud -## 💐 一、JPA 中自带的优化配置 +## 💐 一、JPA 中自带的优化配置 :id=jpa-builtin-optimization 在 Spring Data JPA 中批量保存或更新的 `saveAll` 方法性能较低。但是,Spring Data JPA(或者说 `Hibernate`)中也自带了关于批量操作的优化配置,通常默认没有开启,我建议你开启这些配置项。 @@ -19,9 +19,9 @@ spring: batch_versioned_data: true ``` -## 🌻 二、Fenix 中【批量增删改】的增强 +## 🌻 二、Fenix 中【批量增删改】的增强 :id=fenix-cud-enhance -### 🍺 1. 如何使用 +### 🍺 1. 如何使用 :id=how-to-use Fenix 在 `2.4.0` 版本中提供了相比 `saveAll` 更高效的**批量新增**、**批量更新**和**批量删除**的方法。但是,Fenix 中并没有提供类似于 `saveAll` 这种能同时做到批量新增或者更新的方法,而是为了提高效率,对批量新增和批量更新的方法单独进行了实现和优化。这样即能保证对 JPA 的兼容性,又能提高各自处理场景的效率。需要开发人员可以根据自己的业务场景选择对应的方法。 @@ -42,7 +42,7 @@ public interface SchoolRepository extends FenixJpaRepository { } ``` -### 🥤 2. 批量新增(saveBatch) +### 🥤 2. 批量新增(saveBatch) :id=save-batch **批量新增**有两个重载方法: @@ -68,7 +68,7 @@ public void testSaveBatch() { } ``` -### 🍹 3. 批量更新(updateBatch) +### 🍹 3. 批量更新(updateBatch) :id=update-batch **批量更新**也有两个重载方法: @@ -94,7 +94,7 @@ public void testUpdateBatch() { } ``` -### 🧃 4. 批量删除(deleteBatchByIds) +### 🧃 4. 批量删除(deleteBatchByIds) :id=delete-batch Spring Data JPA 中已经有相关批量删除的方法了,比如: diff --git a/docs/cud/id-generator.md b/docs/cud/id-generator.md index 08a7197..872d573 100644 --- a/docs/cud/id-generator.md +++ b/docs/cud/id-generator.md @@ -1,6 +1,6 @@ -# ✈️ 更多的主键 ID 生成策略 +# ✈️ 更多的主键 ID 生成策略 :id=title -## 🆔 一、简介 +## 🆔 一、简介 :id=introduction Fenix 从 `2.4.0` 版本开始新增了三种主键 `ID` 的生产策略类供你选择和使用,同时也支持你通过 Java API 去调用生成 `ID`: @@ -8,7 +8,7 @@ Fenix 从 `2.4.0` 版本开始新增了三种主键 `ID` 的生产策略类供 - **☃️ 62 进制雪花算法 ID** (`String` 字符串型) - **🌟 62 进制 UUID** (`String` 字符串型) -## ❄️ 二、雪花算法的 ID 生成策略 +## ❄️ 二、雪花算法的 ID 生成策略 :id=snowflake 雪花算法 (`snowflake`) 已经是现在使用比较广泛的 ID 生成算法,其避免了 `UUID` 的冗长无序的缺点,生成的 ID 是**整体有序**的**长整型**数据,Fenix 中也默认做了集成和支持。 @@ -54,7 +54,7 @@ public class MyEntity { } ``` -## ☃️ 三、62 进制雪花算法的 ID 生成策略 +## ☃️ 三、62 进制雪花算法的 ID 生成策略 :id=snowflake-base62 如果你的 ID 不是长整型(`Long`)的,是字符串类型(`String`)的,为了能缩短雪花算法 ID 字符串的长度,可以将原来长度为 `16` 位的雪花算法 ID 的转换为 `62` 进制,能大幅度缩短 `ID` 的长度为 `9` 位,且依然能保证**唯一性**和**整体有序性**。 @@ -110,7 +110,7 @@ String uuid = IdWorker.getUuid(); String uuid2 = IdWorker.get62RadixUuid(); ``` -## 🌟 四、62 进制 UUID 生成策略 +## 🌟 四、62 进制 UUID 生成策略 :id=uuid-base62 鉴于 `UUID` 本质上是 `16` 进制的字符串,字符串长度为 `32` 位,依然可以通过进制转换,将其转换为 `62` 进制,能大幅度缩短 `UUID` 的字符串长度为 `19` 位,且依然能保证**唯一性**和**无序性**。 @@ -156,7 +156,7 @@ public class MyEntity { } ``` -## ☕ 五、通过 Java API 获取 ID +## ☕ 五、通过 Java API 获取 ID :id=java-api 在 Fenix 中,你也可以通过 Java API 调用生成雪花算法的 ID 或 `UUID`。 diff --git a/docs/cud/not-null-update.md b/docs/cud/not-null-update.md index 887741f..b33bd93 100644 --- a/docs/cud/not-null-update.md +++ b/docs/cud/not-null-update.md @@ -1,12 +1,12 @@ -# 🚁 非【null】属性的增量更新 +# 🚁 非【null】属性的增量更新 :id=title -## 🍀 一、初衷 +## 🍀 一、初衷 :id=original-intention 在实际业务场景中,更新数据时,从前端得到的对象实体信息数据的参数中,部分信息是不为空,而我们业务上也只想更新这些不为空的字段到数据库中,而不是将空的数据也一起覆盖保存到数据库中,否则就会造成部分字段数据丢失的问题。 Fenix 在 `2.4.0` 版本中增加了这种常见业务操作的功能处理。 -## 🍁 二、如何使用 +## 🍁 二、如何使用 :id=how-to-use 首先,你须要让你业务功能的 `Repository` 接口继承自 `FenixJpaRepository` 接口。由于 `FenixJpaRepository` 接口继承自 `JpaRepository` 接口,所以 `FenixJpaRepository` 接口的功能完全兼容你以前所使用的 `JpaRepository` 的接口。 @@ -25,16 +25,17 @@ public interface SchoolRepository extends FenixJpaRepository { } ``` -### 🍂 三、方法介绍 +### 🍂 三、方法介绍 :id=method-introduction **非 `null` 属性的增量更新**有两个方法: -- `S saveOrUpdateByNotNullProperties(S entity)`: 新增实体或更新实体类中非 `null` 属性的字段值。本方法保存每条数据时会先查询判断是否存在,再进行插入或者更新。 -- `void saveOrUpdateAllByNotNullProperties(Iterable entities)`: 新增或更新所有实体类中非 `null` 属性的字段值,本方法仅仅是上面方法的 `for` 循环版本,提供了循环处理的功能。 +- 🔹 `S saveOrUpdateByNotNullProperties(S entity)`: 新增实体或更新实体类中非 `null` 属性的字段值。本方法保存每条数据时会先查询判断是否存在,再进行插入或者更新。 +- 🔹 `void saveOrUpdateAllByNotNullProperties(Iterable entities)`: 新增或更新所有实体类中非 `null` 属性的字段值,本方法仅仅是上面方法的 `for` 循环版本,提供了循环处理的功能。 -> **💡 注**: -> 1. 上面两个方法的主要判断处理逻辑为:如果传入的实体主键 `ID` 为空,说明是新增的情况,就插入一条新的数据;如果实体的主键 `ID` 不为空,会先判断是否存在该 `ID` 的数据,如果不存在,也会新增插入一条数据;否则说明是更新的情况,会仅更新保存实体类属性中不为 `null` 值的属性字段到数据库中; -> 2. 由于实际场景中难以区分空和 `null` 的关系,为了统一处理以及不和一些业务场景产生冲突,Fenix 中约定仅更新字段值不为 `null` 的值,如果实体对象中的属性值是空字符串`""`或者 `0` 等等,那么也认为是不为 `null` 的,视为业务上就是要保存空字符串值或者 `0`。所以,Fenix 中也会将该空字符串的值更新到数据库中。所以,**建议你的实体类中所有属性的类型都是对象包装类型,不要用 Java 中的 8 种原生基础类型**。 +**💡 注意事项**: + +- 🔸 上面两个方法的主要判断处理逻辑为:如果传入的实体主键 `ID` 为空,说明是新增的情况,就插入一条新的数据;如果实体的主键 `ID` 不为空,会先判断是否存在该 `ID` 的数据,如果不存在,也会新增插入一条数据;否则说明是更新的情况,会仅更新保存实体类属性中不为 `null` 值的属性字段到数据库中; +- 🔸 由于实际场景中难以区分空和 `null` 的关系,为了统一处理以及不和一些业务场景产生冲突,Fenix 中约定仅更新字段值不为 `null` 的值,如果实体对象中的属性值是空字符串`""`或者 `0` 等等,那么也认为是不为 `null` 的,视为业务上就是要保存空字符串值或者 `0`。所以,Fenix 中也会将该空字符串的值更新到数据库中。所以,**建议你的实体类中所有属性的类型都是对象包装类型,不要用 Java 中的 8 种原生基础类型**。 简单的使用示例如下: diff --git a/docs/more-features.md b/docs/more-features.md index 4138993..d72043d 100644 --- a/docs/more-features.md +++ b/docs/more-features.md @@ -153,7 +153,34 @@ public final class BlogSqlInfoProvider { List queryUserBlogsWithFenixJava(@Param("userId") String userId, @Param("title") String title); ``` -## 🐛 二、从 XML 中获取 SQL 信息 +## 🐛 二、调试 (debug) 模式 + +从 `2.4.1` 版本开始,新增回了 debug 调试模式的功能,也就是可以在不重启服务的情况下,每一条对 XML SQL 的请求,都会实时从 XML 文件流中读取和解析 SQL 语句。 + +> **💡 注**:其实该功能在 `1.0.0` 版本中就有,只不过当时的 IDEA 在识别更新的文件时有些问题,导致该功能没有啥效果,所以就去掉了。现在又将此功能加了回来,更加方便开发人员快速、动态的调试 SQL 语句了。 + +### 📝 1. 开启 debug 模式 + +如果你是使用的 `fenix-spring-boot-starter`,那么只需要在你的 `application.yml` 或者 `application.properties` 文件中,将 `fenix.debug` 设置为 `true` 即可。 + +```yaml +fenix: + # v2.4.1 版本新增,表示是否开启 debug 调试模式,默认 false。 + # 当开启之后,对 XML 中的 SQL 会进行实时文件流的读取和解析,不需要重启服务。切记仅在开发环境中开启此功能. + debug: true +``` + +如果不是使用的 `fenix-spring-boot-starter`,那么需要在你构造的 `FenixConfig` 实例对象中设置 `setDebug` 为 `true` 即可。 + +### 🧬 2. IDEA 中开启热更新策略 + +当然,还需要你开发使用的 IDE 能在服务运行期间动态识别到文件资源的更新,不同的 IDE 工具支持可能有差别。 + +如果你使用的是 Intellij IDEA,那么可以通过 `Edit Configurations` 编辑应用运行时的更新策略。你可以将 `On 'update' action` 和 `On frame deactivation` 的值设置为:`Update classes and resources`。设置的结果效果图如下: + +![IDEA 中的资源更新的设置](https://statics.sh1a.qingstor.com/2021/01/02/idea-update.png) + +## 🦗 三、从 XML 中获取 SQL 信息 Fenix 中会自动从 `XML` 中获取到 SQL 信息。如果你想手动从 `XML` 中获取到 SQL 信息(`SqlInfo`),也可以使用 `Fenix.java` 提供的 `API` 来获取。 @@ -165,7 +192,7 @@ Fenix.getXmlSqlInfo(String fullFenixId, Object context) Fenix.getXmlSqlInfo(String namespace, String fenixId, Object context) ``` -## 🐜 三、表达式、模版解析器 +## 🐜 四、表达式、模版解析器 在 Fenix 中解析 XML 标签中的表达式或者模版是通过 `Mvel` 表达式语言来实现的,主要方法解析方法是封装在了`ParseHelper.java` 的工具类中,通过该类让开发人员自己测试表达式也是极为方便的。以下作简要介绍。 @@ -227,11 +254,11 @@ public void testParseTemplate2() { } ``` -## 🐝 四、上下文参数包装类 +## 🐝 五、上下文参数包装类 Fenix 中提供了一个包装上下文参数为 `HashMap` 的包装器 `ParamWrapper` 工具类,其本质上就是对 `HashMap` 方法的一个**简单链式封装**。 -> **注**:提供该包装器类的主要目的是方便开发者封装较多的散参数或者多个 Java 对象为一个 `Map` 型的上下文参数。 +> **💡 注**:提供该包装器类的主要目的是方便开发者封装较多的散参数或者多个 Java 对象为一个 `Map` 型的上下文参数。 ### 🛏️ 1. ParamWrapper主要方法 @@ -259,7 +286,7 @@ Map context = ParamWrapper.newInstance("sex", "1").put("stuId", 前后对比来看,再仅仅只需要传入个别自定义参数时,能简化部分代码量和参数传递。 -## 🐞 五、表达式的真假判断 +## 🐞 六、表达式的真假判断 Fenix 中关于表达式字符串的真假判断在 `com.blinkfox.fenix.helper.ParseHelper` 类中提供了静态方法。 diff --git a/docs/more/custom-entity.md b/docs/more/custom-entity.md new file mode 100644 index 0000000..6a56805 --- /dev/null +++ b/docs/more/custom-entity.md @@ -0,0 +1,153 @@ +# 🦋返回自定义的实体对象 :id=return-custom-entity + +Fenix 中除了上面介绍的一些功能之外,还有其他额外的辅助、简化开发的功能,以下对返回自定义实体对象的功能作简要介绍。 + +## 📡 一、初衷 :id=intention + +JPA 本身支持通过“[投影](https://docs.spring.io/spring-data/jpa/docs/2.2.0.RELEASE/reference/html/#projections)”的方式来返回自定义的实体对象,但使用起来不那么“自然”。主要原因是: + +- **基于接口的投影**需要创建的是查询结果的接口,接口中的方法是各个结果列属性的 `Getter` 方法,这样查询的结果就是这个接口的匿名实例或实例的集合,并非真正意义上的 `Java Bean`。 +- **基于类的投影**创建的是一个实体类(`Java Bean`),但必须保证该类中含有查询结果列的构造方法,且还比须保证查询结果列与构造方法参数的顺序要一一对应,增加了后期维护的难度。而且该方式的 JPQL 语句必须使用 `new com.xxx.BlogDto(...)` 形式,比较奇怪,而且不能用于原生 SQL 的场景。 + +基于以上原因,Fenix 从 `v1.1.0` 版本开始新增了更加简单、自然的方式来返回自定义的实体对象(`Java Bean`)。下面依然通过 XML 和 Java 两种情况来做示例演示和说明。 + +## 🩸 二、XML 中的使用示例 :id=used-in-xml + +首先,定义一个自定义的数据传输实体用户博客信息类(DTO) `UserBlogInfo.java`,用来作为查询的返回结果,各属性请保证必须至少含有可公开访问的 `Setter` 方法: + +```java +package com.blinkfox.fenix.vo; + +import lombok.Getter; +import lombok.Setter; + +/** + * 用户博客信息的自定义业务实体类,用于测试 JPA 返回自定义实体的使用. + * + * @author blinkfox on 2019/8/9. + */ +@Getter +@Setter +public class UserBlogInfo { + + /** + * 用户 ID. + */ + private String userId; + + /** + * 用户名称. + */ + private String name; + + /** + * 用户博客 ID. + */ + private String blogId; + + /** + * 博客标题. + */ + private String title; + + /** + * 博客原作者. + */ + private String author; + + /** + * 博客内容. + */ + private String content; + +} +``` + +在 `BlogRepository.java` 接口中定义查询的接口方法,接口返回的是我们刚才定义的 `UserBlogInfo` 分页信息: + +```java +/** + * 使用 {@link QueryFenix} 注解来连表模糊查询自定义的用户博客实体分页信息. + * + * @param userId 用户ID + * @param blog 博客实体信息 + * @return 用户博客信息集合 + */ +@QueryFenix("BlogRepository.queryUserBlogsWithFenixResultType") +Page queryUserBlogPageWithFenixResultType(@Param("userId") String userId, @Param("blog") Blog blog, Pageable pageable); +``` + +然后,在 `BlogRepository.xml` 的 XML 文件中,书写 SQL 语句,通过 `resultType` 来额外表明返回的结果为我们刚才自定义的实体类: + +```xml + + + SELECT + u.id as userId, + u.name as name, + b.id as blogId, + b.title as title, + b.author as author, + b.content as content + FROM + Blog as b, + User as u + WHERE + u.id = b.userId + + + + +``` + +**💡 注意事项**: + +- 🔸 上面的代码关键就在 fenix 节点中配置了 `resultType` 属性的值为我们定义的**实体类的全路径名** `com.blinkfox.fenix.vo.UserBlogInfo`。这样查询结果返回的时候就能自动识别并返回了。 +- 🔸 另一个要点是所有查询列**都必须使用 as**来返回一个“别名”,且**这个“别名”必须跟实体类的属性名一致,不区分大小写**。 +- 🔸 此种方式再运行时可能会与 `spring-boot-devtools.jar` 冲突,报 `No converter found capable of converting from type [xxx] to type [xxx]` 错误。建议不使用 `spring-boot-devtools.jar`。 +- 🔸 在 Fenix `v2.3.1` 及之前的版本,**在异步多线程情况下,可能出现 JDBC 连接无法释放的问题**,强烈建议你升级 Fenix 版本为 `v2.3.3` 及之后的版本。 + +## 💊 三、Java 中的使用示例 :id=used-in-java + +在 Java 中的使用示例同 XML 中相似,只不过是将 SQL 写到了 Java 代码中了而已,且通过 `setResultTypeClass` 方法来设置返回的结果类型。 + +书写 SQL 的 Provider 类代码如下: + +```java +public final class BlogSqlInfoProvider { + + /** + * 使用 Java 拼接 SQL 的方式来拼接查询用户博客信息的 SQL 信息. + * + * @param userId 用户 ID + * @param title 标题 + * @return SQL 信息 + */ + public SqlInfo queryUserBlogsWithFenixJava(@Param("userId") String userId, @Param("title") String title) { + return Fenix.start() + .select("u.id AS userId, u.name AS name, b.id AS blogId, b.title AS title, b.author AS author, " + + "b.content AS content") + .from("Blog as b, User as u") + .where("u.id = b.userId") + .andEqual("b.userId", userId) + .andLike("b.title", title, StringHelper.isNotBlank(title)) + .end() + .setResultTypeClass(UserBlogInfo.class); + } + +} +``` + +`BlogRepository.java` 接口中定义查询方法如下: + +```java +/** + * 使用 {@link QueryFenix} 注解和 Java 拼接 SQL 的方式来连表模糊查询并返回自定义的用户博客信息. + * + * @param userId 用户 ID + * @param title 标题 + * @return 自定义的用户博客信息集合 + */ +@QueryFenix(provider = BlogSqlInfoProvider.class) +List queryUserBlogsWithFenixJava(@Param("userId") String userId, @Param("title") String title); +``` diff --git a/docs/more/debug-mode.md b/docs/more/debug-mode.md new file mode 100644 index 0000000..72902eb --- /dev/null +++ b/docs/more/debug-mode.md @@ -0,0 +1,26 @@ +# 🐛 调试 (debug) 模式 :id=debug-mode + +Fenix 从 `2.4.1` 版本开始,新增回了 debug 调试模式的功能,也就是可以在不重启服务的情况下,每一条对 XML SQL 的请求,都会实时从 XML 文件流中读取和解析 SQL 语句。 + +> **💡 注**:其实该功能在 `1.0.0` 版本中就有,只不过当时的 IDEA 在识别更新的文件时有些问题,导致该功能没有啥效果,所以就去掉了。现在又将此功能加了回来,更加方便开发人员快速、动态的调试 SQL 语句了。 + +## 📝 一、开启 debug 模式 :id=enable-debug + +如果你是使用的 `fenix-spring-boot-starter` 组件,那么只需要在你的 `application.yml` 或者 `application.properties` 文件中,将 `fenix.debug` 设置为 `true` 即可。 + +```yaml +fenix: + # v2.4.1 版本新增,表示是否开启 debug 调试模式,默认 false。 + # 当开启之后,对 XML 中的 SQL 会进行实时文件流的读取和解析,不需要重启服务。切记仅在开发环境中开启此功能. + debug: true +``` + +如果不是使用的 `fenix-spring-boot-starter` 组件,那么需要在你构造的 `FenixConfig` 实例对象中设置 `setDebug` 为 `true` 即可。 + +## 🧬 二、设置 IDEA 中的热更新策略 :id=idea-settings + +当然,还需要你开发使用的 IDE 能在服务运行期间动态识别到文件资源的更新,不同的 IDE 工具支持可能有差别。 + +如果你使用的是 Intellij IDEA,那么可以通过 `Edit Configurations` 编辑应用运行时的更新策略。你可以将 `On 'update' action` 和 `On frame deactivation` 的值设置为:`Update classes and resources`。设置的结果效果图如下: + +![IDEA 中的资源更新的设置](https://statics.sh1a.qingstor.com/2021/01/02/idea-update.png) diff --git a/docs/more/other-features.md b/docs/more/other-features.md new file mode 100644 index 0000000..0f0a7f0 --- /dev/null +++ b/docs/more/other-features.md @@ -0,0 +1,124 @@ +# 🐝 其他功能 :id=others + +## 🦗 一、从 XML 中获取 SQL 信息 :id=get-xml-sql + +Fenix 中会自动从 `XML` 中获取到 SQL 信息。如果你想手动从 `XML` 中获取到 SQL 信息(`SqlInfo`),也可以使用 `Fenix.java` 提供的 `API` 来获取。 + +```java +// 通过传入完整的 fullFenixId(命名空间、'.'号和 Fenix 节点的 ID)和上下文参数,来简单快速的生成和获取 SqlInfo 信息. +Fenix.getXmlSqlInfo(String fullFenixId, Object context) + +// 通过传入 Fenix XML 文件对应的命名空间、Fenix 节点的 ID 以及上下文参数对象,来生成和获取 SqlInfo 信息. +Fenix.getXmlSqlInfo(String namespace, String fenixId, Object context) +``` + +## 🐜 二、表达式、模版解析器 :id=express-template-parser + +在 Fenix 中解析 XML 标签中的表达式或者模版是通过 `Mvel` 表达式语言来实现的,主要方法解析方法是封装在了`ParseHelper.java` 的工具类中,通过该类让开发人员自己测试表达式也是极为方便的。以下作简要介绍。 + +### 💉 1. 解析表达式 :id=parse-express + +#### 主要方法 + +```java +// 解析出表达式的值,如果解析出错则不抛出异常,但会输出 error 级别的异常,返回 null. +Object parseExpress(String exp, Object paramObj); + +// 解析出表达式的值,如果解析出错则抛出异常. +Object parseExpressWithException(String exp, Object paramObj); +``` + +#### 使用示例 + +```java +@Test +public void testParseWithMvel() { + // 构造上下文参数 + Map context = new HashMap(); + context.put("foo", "Hello"); + context.put("bar", "World"); + + // 解析得到 'HelloWorld' 字符串,断言为: true. + String result = (String) ParseHelper.parseExpressWithException("foo + bar", context); + assertEquals("HelloWorld", result); +} + +@Test +public void testParseStr2() { + Boolean result = (Boolean) ParseHelper.parseExpress("sex == 1", ParamWrapper.newInstance("sex", "1").toMap()); + + // 断言为: true. + assertEquals(true, result); +} +``` + +### 🩺 2. 解析模版 :id=parse-template + +#### 主要方法 + +```java +// 解析出模板字符串中的值,如果解析出错则抛出异常. +String parseTemplate(String template, Object context) +``` + +#### 使用示例 + +```java +@Test +public void testParseTemplate2() { + String result = ParseHelper.parseTemplate("@if{?foo != empty}@{foo} World!@end{}", + ParamWrapper.newInstance("foo", "Hello").toMap()); + + // 解析得到 'Hello World!' 字符串,断言为:true. + assertEquals("Hello World!", result); +} +``` + +## 🦗 三、上下文参数包装类 :id=param-wrapper + +Fenix 中提供了一个包装上下文参数为 `HashMap` 的包装器 `ParamWrapper` 工具类,其本质上就是对 `HashMap` 方法的一个**简单链式封装**。 + +> **💡 注**:提供该包装器类的主要目的是方便开发者封装较多的散参数或者多个 Java 对象为一个 `Map` 型的上下文参数。 + +### 🛏️ 1. ParamWrapper 主要方法 :id=main-methods + +- `newInstance()`,创建新的`ParamWrapper`实例。 +- `newInstance(Map paramMap)`,传入已有的`Map`型对象,并创建新的`ParamWrapper`实例。 +- `newInstance(String key, Object value)`,创建新的`ParamWrapper`实例,并创建一对key和value的键值对。 +- `put(String key, Object value)`,向参数包装器中,`put`对应的key和value值。 +- `toMap()`,返回填充了key、value后的Map对象。 + +### 🪑 2. 对比的示例 :id=compare-demo + +以前需要开发者自己封装Map: + +```java +Map context = new HashMap(); +context.put("sex", "1"); +context.put("stuId", "123"); +``` + +现在的使用方式: + +```java +Map context = ParamWrapper.newInstance("sex", "1").put("stuId", "123").toMap()); +``` + +前后对比来看,再仅仅只需要传入个别自定义参数时,能简化部分代码量和参数传递。 + +## 🐞 四、表达式的真假判断 :id=true-or-false + +Fenix 中关于表达式字符串的真假判断在 `com.blinkfox.fenix.helper.ParseHelper` 类中提供了静态方法。 + +**📌 主要方法**: + +```java +// 是否匹配,常用于标签中的 match 值的解析,即如果 match 不填写,或者内容为空,或者解析出为正确的值,都视为true. +ParseHelper.isMatch(String match, Object context) + +// 是否不匹配,同 isMatch 相反,只有解析到的值是 false 时,才认为是 false. +ParseHelper.isNotMatch(String match, Object context) + +// 是否为 true,只有当解析值确实为 true 时,才为 true. +ParseHelper.isTrue(String exp, Object context) +``` diff --git a/docs/quick-install.md b/docs/quick-install.md index e8326eb..862f9c3 100644 --- a/docs/quick-install.md +++ b/docs/quick-install.md @@ -14,14 +14,14 @@ com.blinkfox fenix-spring-boot-starter - 2.4.0 + 2.4.1 ``` ### 🌵 2. Gradle ```bash -compile 'com.blinkfox:fenix-spring-boot-starter:2.4.0' +compile 'com.blinkfox:fenix-spring-boot-starter:2.4.1' ``` ### 🏕️ 3. 激活 Fenix (@EnableFenix) @@ -51,7 +51,7 @@ public class DemoApplication { > **💡 注**: > 1. `@EnableFenix` 注解中实质上是使用的是 `FenixJpaRepositoryFactoryBean`。而 `FenixJpaRepositoryFactoryBean` 继承自 Spring Data JPA 默认的 `JpaRepositoryFactoryBean`。所以,Fenix 与 JPA 的各种注解和特性完全兼容,并提供了更加强大的 `@QueryFenix` 注解和其他更多动态的能力。 -> 2. 如果你是多数据源,则你可以根据自身情况,在需要的数据源中的 `@EnableJpaRepositories` 注解中单独设置 `repositoryFactoryBeanClass` 的值为:`FenixJpaRepositoryFactoryBean.class`。示例如:`@EnableJpaRepositories(repositoryFactoryBeanClass = FenixJpaRepositoryFactoryBean.class)`。 +> 2. 如果你是多数据源,则你可以根据自身情况,在需要的数据源中使用 `@EnableFenix` 注解即可。或者你也可以在 `@EnableJpaRepositories` 注解中单独设置 `repositoryFactoryBeanClass` 的值为:`FenixJpaRepositoryFactoryBean.class`。示例如:`@EnableJpaRepositories(repositoryFactoryBeanClass = FenixJpaRepositoryFactoryBean.class)`。 ### 🏝️ 4. application.yml 配置(可选的) @@ -64,6 +64,9 @@ public class DemoApplication { ```yaml # Fenix 的几个配置项、默认值及详细说明,通常情况下你不需要填写这些配置信息(下面的配置代码也都可以删掉). fenix: + # v2.4.1 版本新增,表示是否开启 debug 调试模式,默认 false。 + # 当开启之后,对 XML 中的 SQL 会进行实时文件流的读取和解析,不需要重启服务。切记仅在开发环境中开启此功能. + debug: false # 成功加载 Fenix 配置信息后,是否打印启动 banner,默认 true. print-banner: true # 是否打印 Fenix 生成的 SQL 信息,默认为空. @@ -92,20 +95,19 @@ fenix: com.blinkfox fenix - 2.4.0 + 2.4.1 ``` ### 🌻 2. Gradle ```bash -compile 'com.blinkfox:fenix:2.4.0' +compile 'com.blinkfox:fenix:2.4.1' ``` ### 🏔️ 3. 激活 Fenix -跟前面 Spring Boot 激活 Fenix FactoryBean 一样,需要在启动类中使用 `@EnableFenix` 激活 Fenix,也可以直接在 `@EnableJpaRepositories` 注解中,配置 -`repositoryFactoryBeanClass` 的属性值为 `FenixJpaRepositoryFactoryBean.class`。 +跟前面 Spring Boot 激活 Fenix FactoryBean 一样,需要在启动类中使用 `@EnableFenix` 激活 Fenix,也可以直接在 `@EnableJpaRepositories` 注解中,配置 `repositoryFactoryBeanClass` 的属性值为 `FenixJpaRepositoryFactoryBean.class`。 主要注解配置的代码如下: @@ -155,6 +157,7 @@ public void init() { // 加载 FenixConfig 实例,配置是否打印 banner、SQL 信息、XML 文件位置和自定义的 XML 语义标签处理器的位置. FenixConfigManager.getInstance().initLoad(new FenixConfig() + .setDebug(false) .setPrintBanner(true) .setPrintSqlInfo(true) .setXmlLocations(xmlLocations) diff --git a/docs/xml/logic-control.md b/docs/xml/logic-control.md index aa026b3..8ab792f 100644 --- a/docs/xml/logic-control.md +++ b/docs/xml/logic-control.md @@ -99,4 +99,24 @@ SELECT u FROM User AS u WHERE u.id = #{user.id} @end{} ``` -以上只是几个你可能会用到的逻辑控制标签。当然,还有其他[更多的 `MVEL` 模版语法](http://mvel.documentnode.com/#mvel-2.0-templating-guide) 你可以参考。 +**多重迭代**: + +你也可以通过逗号(`,`)分割,一次迭代多个集合: + +```java +@foreach{var1 : set1, var2 : set2} + @{var1}-@{var2} +@end{} +``` + +**分隔符**: + +您可以通过在 `@end{}` 标记中指定迭代器来自动向迭代添加文本分割符。 + +```java +@foreach{item : people}@{item.name}@end{', '} +``` + +会得到类似这样的结果:`John, Mary, Joseph`。 + +> **💡 总结**:以上只是几个你可能会用到的逻辑控制标签。当然,还有其他[更多的 `MVEL` 模版语法](http://mvel.documentnode.com/#mvel-2.0-templating-guide) 供你参考。 diff --git a/docs/xml/xml-tags.md b/docs/xml/xml-tags.md index b2efd14..09f1821 100644 --- a/docs/xml/xml-tags.md +++ b/docs/xml/xml-tags.md @@ -517,6 +517,8 @@ AND u.n_age IS NULL `set` 标签主要用于动态生成 `update` 语句中的 SQL 片段。 +> **💡 注**:从 `2.4.0` 版本之后,增加了 `saveOrUpdateByNotNullProperties` 的**增量更新非`null`字段**的方法。大多数情况下,你可以不使用 `set` 标签了。 + ### 📻 1. 标签 ```xml diff --git a/src/test/java/com/blinkfox/fenix/core/FenixXmlBuilderTest.java b/src/test/java/com/blinkfox/fenix/core/FenixXmlBuilderTest.java index f801eea..811c55f 100644 --- a/src/test/java/com/blinkfox/fenix/core/FenixXmlBuilderTest.java +++ b/src/test/java/com/blinkfox/fenix/core/FenixXmlBuilderTest.java @@ -432,4 +432,14 @@ public void testWhere6() { assertTrue(sqlInfo.getParams().isEmpty()); } + /** + * 测试 where 标签的情况7. + */ + @Test + public void testWhere7() { + SqlInfo sqlInfo = Fenix.getXmlSqlInfo("fenix.testWhere7", context); + assertEquals(SELECT_QUERY, sqlInfo.getSql()); + assertTrue(sqlInfo.getParams().isEmpty()); + } + } diff --git a/src/test/resources/fenix/fenix.xml b/src/test/resources/fenix/fenix.xml index 021774e..0d43036 100644 --- a/src/test/resources/fenix/fenix.xml +++ b/src/test/resources/fenix/fenix.xml @@ -286,4 +286,16 @@ + + + SELECT u FROM @{entityName} + + @if{?user.name == 'Lisi'} + AND u.name LIKE '%@{user.name}%' + @end{} + @if{?user.status == 'null'} + AND u.status = @{user.status} + @end{} + +