From bd1dfdca65c517ec4f0ac51a4580aec53e841bf3 Mon Sep 17 00:00:00 2001 From: wangyu <727842003@qq.com> Date: Tue, 6 Sep 2022 16:13:01 +0800 Subject: [PATCH] feat: init the project --- .gitignore | 5 + LICENSE | 201 ++++++++++ README.md | 107 ++++++ fluent-sql-core/pom.xml | 37 ++ .../group/flyfish/fluent/binding/Alias.java | 19 + .../flyfish/fluent/binding/JSONInject.java | 14 + .../group/flyfish/fluent/chain/Order.java | 26 ++ .../group/flyfish/fluent/chain/OrderImpl.java | 37 ++ .../java/group/flyfish/fluent/chain/SQL.java | 64 ++++ .../flyfish/fluent/chain/SQLFactory.java | 19 + .../group/flyfish/fluent/chain/SQLImpl.java | 255 ++++++++++++ .../flyfish/fluent/chain/SQLOperations.java | 30 ++ .../flyfish/fluent/chain/SQLSegment.java | 29 ++ .../chain/common/AfterJoinSqlChain.java | 26 ++ .../fluent/chain/common/ExecutableSql.java | 16 + .../fluent/chain/common/HandleSqlChain.java | 20 + .../fluent/chain/common/JoinOperations.java | 84 ++++ .../fluent/chain/common/PreSqlChain.java | 26 ++ .../chain/select/AfterOrderSqlChain.java | 32 ++ .../chain/select/AfterWhereSqlChain.java | 19 + .../fluent/chain/select/SelectComposite.java | 73 ++++ .../fluent/chain/update/AfterSetSqlChain.java | 22 ++ .../fluent/mapping/SQLMappedRowMapper.java | 362 ++++++++++++++++++ .../flyfish/fluent/query/ConcatCandidate.java | 27 ++ .../group/flyfish/fluent/query/Condition.java | 47 +++ .../fluent/query/ConditionCandidate.java | 72 ++++ .../flyfish/fluent/query/JoinCandidate.java | 26 ++ .../flyfish/fluent/query/Parameterized.java | 32 ++ .../group/flyfish/fluent/query/Query.java | 72 ++++ .../flyfish/fluent/query/SimpleCondition.java | 115 ++++++ .../flyfish/fluent/query/SimpleQuery.java | 142 +++++++ .../group/flyfish/fluent/update/Update.java | 52 +++ .../flyfish/fluent/update/UpdateImpl.java | 110 ++++++ .../flyfish/fluent/utils/cache/LRUCache.java | 36 ++ .../fluent/utils/context/AliasComposite.java | 182 +++++++++ .../fluent/utils/data/ObjectMappers.java | 32 ++ .../fluent/utils/data/ParameterUtils.java | 31 ++ .../fluent/utils/sql/ConcatSegment.java | 63 +++ .../fluent/utils/sql/EntityNameUtils.java | 157 ++++++++ .../flyfish/fluent/utils/sql/SFunction.java | 82 ++++ .../fluent/utils/sql/SerializedLambda.java | 156 ++++++++ .../fluent/utils/sql/SqlNameUtils.java | 103 +++++ .../fluent/utils/text/TemplateCompiler.java | 98 +++++ fluent-sql-jdbctemplate/pom.xml | 19 + pom.xml | 181 +++++++++ 45 files changed, 3358 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 fluent-sql-core/pom.xml create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/binding/Alias.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/binding/JSONInject.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/Order.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/OrderImpl.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQL.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLFactory.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLImpl.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLOperations.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLSegment.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/AfterJoinSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/ExecutableSql.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/HandleSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/JoinOperations.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/PreSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterOrderSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterWhereSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/SelectComposite.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/chain/update/AfterSetSqlChain.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/mapping/SQLMappedRowMapper.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConcatCandidate.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/Condition.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConditionCandidate.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/JoinCandidate.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/Parameterized.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/Query.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleCondition.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleQuery.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/update/Update.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/update/UpdateImpl.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/cache/LRUCache.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/context/AliasComposite.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ObjectMappers.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ParameterUtils.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/ConcatSegment.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/EntityNameUtils.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SFunction.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SerializedLambda.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SqlNameUtils.java create mode 100644 fluent-sql-core/src/main/java/group/flyfish/fluent/utils/text/TemplateCompiler.java create mode 100644 fluent-sql-jdbctemplate/pom.xml create mode 100644 pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60245d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +*.iml +.DS_Store + +target diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f98d5fd --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# fluent-sql + +基于Fluent Api实现的SQL构建器,秒杀mybatis plus的存在,易用性的API让你爽到飞起。 + +## 特性介绍 + +1. 实现了SQL底层的语法解析,创新使用片段的构建模式,忽略嵌套层级任意嵌套 +2. 高质量的代码,没有一点点冗余的设计,为您的代码保驾护航 +3. 天生自带防SQL注入和条件空策略解析,后续会增加更加精细的配置 +4. 支持任意多表的关联查询和数据绑定 +5. 支持返回实体映射,基于注解式开发,更解耦,更面向对象 +6. 智能别名策略,写查询再也不用担心多张表的别名问题,代码简介易懂,用java跟sql体验直接拉满 +7. 高精度api控制,sql构建每个步骤严格把关,保证输入一个api立即能写出来接下来的步骤还不出错 + +## 快速使用 + +本小组件主要解决的是sql的书写问题,旨在用更加优雅的方式实现sql,并且不用再担心数据库方言(SQL Dialect) +变化导致的频繁变更SQL问题。 + +如果要实现下面一段SQL + +```sql +SELECT t1.`id` AS `id`, + t1.`name` AS `name`, + t1.`identifier` AS `identifier`, + `max_tenant` AS `maxTenant`, + t2.`related_id` AS `relatedId` +FROM saas_tenant `t1` + INNER JOIN saas_quota `t2` ON t2.`related_id` = t1.`id` + AND t1.`identifier` = ? +WHERE t1.`id` = ? + AND t1.`name` LIKE CONCAT(%,?,%) + AND t2.`related_id` IN (?, ?) + AND t2.`related_type` = ? +ORDER BY t1.`create_time` DESC, + t1.`id` ASC +``` + +您只需要写以下java代码: + +```java +public class TestSql { + + /** + * 执行sql测试 + * @return 最终组装的实体 + */ + public TenantContext executeSql() { + // 查询开始 + return select( + // 某张表的几个字段 + composite(SaasTenant::getId, SaasTenant::getName, SaasTenant::getIdentifier), + // 某张表的全量字段 + all(SaasProperties.class), + // 其他表的字段 + composite(SaasQuota::getId, SaasQuota::getRelatedType), + // 指定别名的字段 + composite(SaasQuota::getRelatedId, "relatedOtherId")) + .from(SaasTenant.class) + .join(SaasQuota.class).on(where(SaasQuota::getRelatedId).eq(SaasTenant::getId) + .and(SaasTenant::getIdentifier).eq(5)) + .join(SaasProperties.class) + .then() + .matching(where(SaasTenant::getId).eq("1") + .and(SaasTenant::getName).like("王大锤") + .and(SaasQuota::getRelatedId).in(Arrays.asList("5", "10")) + .and(SaasQuota::getRelatedType).eq(SaasQuota.RelatedType.TENANT) + ) + .order(by(SaasTenant::getCreateTime).desc(), by(SaasTenant::getId).asc()) + .one(TenantContext.class); + } +} +``` + +## 常规步骤 + +建议使用静态导入,美观代码,如下: + +1. SQL.select => select +2. SelectComposite.composite => composite +3. SelectComposite.all => all +4. Order.by => by +5. Query.where => where + +为了方便演示,我们下面的代码都基于静态导入函数: + +1. 绑定您执行sql的数据源。如下: + +```java +class SQLConfig { + + void doConfig() { + // 创建或者获取您的数据源 + DataSource = createDataSource(...) + // 基于spring jdbc template实例化 + SQL.bind(new JdbcTemplate(dataSource)); + } +} + +``` + +2. 写一个结果对象Vo用来接取执行结果,如快速开始中的`TenantContext` +3. 用java写sql,然后执行`.one()`或者`.list()`返回一条或者多条。 +4. 查询单表所有字段,请使用`select().from(TableClass.class)` +5. 查询多表中某个单表的所有字段,请使用`select(all(TableClass.class)).from(TableClass.class).join(Other.class).then()` +6. +多张表中查询某些字段,并使用别名,请参考`select(composite(TableA::getName, "nameA"), composite(TableB::getName, "nameB")).from(TableA.class).join(TableB.class)...` diff --git a/fluent-sql-core/pom.xml b/fluent-sql-core/pom.xml new file mode 100644 index 0000000..5e2d442 --- /dev/null +++ b/fluent-sql-core/pom.xml @@ -0,0 +1,37 @@ + + + + fluent-sql + group.flyfish.framework + 1.0-SNAPSHOT + + 4.0.0 + + fluent-sql-core + + + 8 + 8 + + + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework + spring-jdbc + + + javax.persistence + persistence-api + + + org.slf4j + slf4j-api + + + diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/Alias.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/Alias.java new file mode 100644 index 0000000..23c1c18 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/Alias.java @@ -0,0 +1,19 @@ +package group.flyfish.fluent.binding; + +import java.lang.annotation.*; + +/** + * 结果映射指定别名 + * + * @author wangyu + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Alias { + + /** + * @return 别名 + */ + String value(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/JSONInject.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/JSONInject.java new file mode 100644 index 0000000..d642c3d --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/binding/JSONInject.java @@ -0,0 +1,14 @@ +package group.flyfish.fluent.binding; + +import java.lang.annotation.*; + +/** + * json注入 + * + * @author wangyu + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface JSONInject { +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/Order.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/Order.java new file mode 100644 index 0000000..a7e1aac --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/Order.java @@ -0,0 +1,26 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.utils.sql.SFunction; + +/** + * 排序链式声明 + * + * @author wangyu + */ +public interface Order extends SQLSegment { + + /** + * 以字段排序 + * + * @param field 字段 + * @param 泛型 + * @return 链式调用 + */ + static Order by(SFunction field) { + return new OrderImpl(field); + } + + Order asc(); + + Order desc(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/OrderImpl.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/OrderImpl.java new file mode 100644 index 0000000..3da7f32 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/OrderImpl.java @@ -0,0 +1,37 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.utils.sql.SFunction; +import lombok.RequiredArgsConstructor; + +/** + * 排序支持 + * + * @author wangyu + */ +@RequiredArgsConstructor +final class OrderImpl implements Order, SQLSegment { + + private final SFunction field; + + private String order = "asc"; + + @Override + public OrderImpl asc() { + order = "asc"; + return this; + } + + @Override + public OrderImpl desc() { + order = "desc"; + return this; + } + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return String.join(" ", field.getName(), order); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQL.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQL.java new file mode 100644 index 0000000..f9afea0 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQL.java @@ -0,0 +1,64 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.chain.common.PreSqlChain; +import group.flyfish.fluent.chain.select.SelectComposite; +import group.flyfish.fluent.update.Update; +import group.flyfish.fluent.utils.sql.SFunction; +import org.springframework.jdbc.core.JdbcOperations; + +import static group.flyfish.fluent.utils.sql.SqlNameUtils.cast; + +/** + * 链式查询入口 + * + * @author wangyu + */ +public interface SQL { + + /** + * 查询起手式 + * + * @return 自身 + */ + @SafeVarargs + static PreSqlChain select(SFunction... fields) { + return SQLFactory.produce().select(fields); + } + + /** + * 查询起手式 + * + * @return 自身 + */ + static PreSqlChain select(SelectComposite... composites) { + return SQLFactory.produce().select(SelectComposite.combine(composites)); + } + + /** + * 查询起手式,查询全部字段 + * + * @return 结果 + */ + static PreSqlChain select() { + return SQLFactory.produce().select(cast(new SFunction[]{})); + } + + /** + * 更新表起手 + * + * @param clazz 表 + * @return 更新链式 + */ + static Update update(Class clazz) { + return SQLFactory.produce().update(clazz); + } + + /** + * 绑定数据源上下文,基于jdbc template + * + * @param operations jdbc操作 + */ + static void bind(JdbcOperations operations) { + SQLImpl.bind(operations); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLFactory.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLFactory.java new file mode 100644 index 0000000..4a7c2b3 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLFactory.java @@ -0,0 +1,19 @@ +package group.flyfish.fluent.chain; + +/** + * sql工厂 + * + * @author wangyu + * 普通静态工厂,用于生产实现实例 + */ +public interface SQLFactory { + + /** + * 生产实例 + * + * @return sql操作 + */ + static SQLOperations produce() { + return new SQLImpl(); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLImpl.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLImpl.java new file mode 100644 index 0000000..bb3ad7e --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLImpl.java @@ -0,0 +1,255 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.chain.common.AfterJoinSqlChain; +import group.flyfish.fluent.chain.common.HandleSqlChain; +import group.flyfish.fluent.chain.common.PreSqlChain; +import group.flyfish.fluent.chain.select.AfterOrderSqlChain; +import group.flyfish.fluent.chain.select.AfterWhereSqlChain; +import group.flyfish.fluent.chain.update.AfterSetSqlChain; +import group.flyfish.fluent.mapping.SQLMappedRowMapper; +import group.flyfish.fluent.query.JoinCandidate; +import group.flyfish.fluent.query.Parameterized; +import group.flyfish.fluent.query.Query; +import group.flyfish.fluent.update.Update; +import group.flyfish.fluent.update.UpdateImpl; +import group.flyfish.fluent.utils.context.AliasComposite; +import group.flyfish.fluent.utils.data.ParameterUtils; +import group.flyfish.fluent.utils.sql.ConcatSegment; +import group.flyfish.fluent.utils.sql.EntityNameUtils; +import group.flyfish.fluent.utils.sql.SFunction; +import group.flyfish.fluent.utils.sql.SqlNameUtils; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcOperations; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 查询工具类 + * + * @author wangyu + */ +final class SQLImpl extends ConcatSegment implements SQLOperations, PreSqlChain, HandleSqlChain, AfterJoinSqlChain, AfterSetSqlChain { + + // 共享的操作 + private static JdbcOperations SHARED_OPERATIONS; + // 参数map,有序 + private final List parameters = new ArrayList<>(); + + // 调试标识 + private final boolean debug = false; + + /** + * 基于jdbc template + * + * @param operations jdbc操作 + */ + public static void bind(JdbcOperations operations) { + SHARED_OPERATIONS = operations; + } + + /** + * 查询起手 + * + * @param fields 字段列表,不传代表所有字段 + * @return 预查询链 + */ + @SafeVarargs + @Override + public final PreSqlChain select(SFunction... fields) { + String linker = !segments.isEmpty() ? "," : "SELECT"; + if (fields != null && fields.length != 0) { + return this.concat(linker) + .concat(() -> Arrays.stream(fields).map(SFunction::getSelect).collect(Collectors.joining(","))); + } + return this.concat(linker).concat("*"); + } + + /** + * 更新起手 + * + * @param clazz 具体表 + * @return 链式调用 + */ + @Override + public Update update(Class clazz) { + return new UpdateImpl(update -> { + if (withoutParameter(update)) return this; + return this.concat("UPDATE") + .concat(() -> EntityNameUtils.getTableName(clazz)) + .concat("SET") + .concat(update); + }); + } + + /** + * 从表里查 + * + * @param type 类型 + * @return 链式调用 + */ + public HandleSqlChain from(Class type) { + return from(type, null); + } + + /** + * 从指定表查询,附加别名 + * 该接口适用于同一张表多次from的情况,可以从自表进行多次查询 + * 大部分情况下,您都不需要指定别名 + * + * @param type 类型 + * @param alias 别名 + * @return 处理环节 + */ + @Override + public HandleSqlChain from(Class type, String alias) { + String mapped = AliasComposite.add(type, alias); + return concat("FROM") + .concat(() -> EntityNameUtils.getTableName(type)) + .concat(() -> SqlNameUtils.wrap(mapped)); + } + + /** + * 全量的join连接支持 + * + * @param type 连接类型 + * @param clazz 目标表 + * @param alias 别名 + * @return join后的操作 + */ + @Override + public AfterJoinSqlChain join(JoinCandidate type, Class clazz, String alias) { + String mapped = AliasComposite.add(clazz, alias); + return concat(type) + .concat(() -> EntityNameUtils.getTableName(clazz)) + .concat(() -> SqlNameUtils.wrap(mapped)); + } + + /** + * 连接条件 + * + * @param query 查询 + * @return 处理链 + */ + @Override + public HandleSqlChain on(Query query) { + if (withoutParameter(query)) return this; + return concat("ON").concat(query); + } + + /** + * 不带连接条件 + * + * @return 处理链 + */ + @Override + public HandleSqlChain then() { + return this; + } + + /** + * 拼接查询条件 + * + * @param query 条件 + * @return 结果 + */ + public AfterWhereSqlChain matching(Query query) { + if (withoutParameter(query)) return this; + return concat("WHERE").concat(query); + } + + /** + * 拼接排序条件 + * + * @param orders 排序 + * @return 结果 + */ + @Override + public AfterOrderSqlChain order(Order... orders) { + if (null != orders && orders.length != 0) { + return concat("ORDER BY") + .concat(() -> Arrays.stream(orders).map(SQLSegment::get).collect(Collectors.joining(","))); + } + return this; + } + + + /** + * 执行并获取结果 + * + * @param clazz 结果类 + * @param 泛型 + */ + public T one(Class clazz) { + try { + return SHARED_OPERATIONS.queryForObject(sql().concat(" limit 1"), + new SQLMappedRowMapper<>(clazz), parsedParameters()); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + /** + * 执行并获取多条结果 + * + * @param clazz 结果类 + * @return 结果列表 + */ + @Override + public List list(Class clazz) { + return SHARED_OPERATIONS.query(sql(), new SQLMappedRowMapper<>(clazz), parsedParameters()); + } + + /** + * 执行并获取更新条数 + * + * @return 更新条数 + */ + @Override + public int execute() { + return SHARED_OPERATIONS.update(sql(), parsedParameters()); + } + + /** + * 构建sql + * + * @return 构建结果 + */ + private String sql() { + Assert.notNull(SHARED_OPERATIONS, "未指定执行数据源!"); + String sql = segments.stream().map(SQLSegment::get).collect(Collectors.joining(" ")); + if (debug) { + System.out.println("prepared sql: " + sql); + System.out.println("prepared args:" + parameters.stream().map(ParameterUtils::convert).map(String::valueOf) + .collect(Collectors.joining(","))); + } + AliasComposite.flush(); + return sql; + } + + /** + * 解析后的参数 + * + * @return 结果 + */ + private Object[] parsedParameters() { + return parameters.stream().map(ParameterUtils::convert).toArray(); + } + + /** + * 没有参数值 + * + * @param params 参数 + * @return 为true,代表没有参数,不添加该片段 + */ + private boolean withoutParameter(Parameterized params) { + if (params.isEmpty() || null == params.getParameters()) { + return true; + } + parameters.addAll(params.getParameters()); + return false; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLOperations.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLOperations.java new file mode 100644 index 0000000..1acda4b --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLOperations.java @@ -0,0 +1,30 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.chain.common.PreSqlChain; +import group.flyfish.fluent.update.Update; +import group.flyfish.fluent.utils.sql.SFunction; + +/** + * SQL操作 + */ +public interface SQLOperations { + + /** + * 查询起手 + * + * @param fields 字段列表,不传代表所有字段 + * @param 实体泛型 + * @return 预查询链 + */ + @SuppressWarnings("unchecked") + PreSqlChain select(SFunction... fields); + + /** + * 更新起手 + * + * @param clazz 具体表 + * @param 泛型 + * @return 链式调用 + */ + Update update(Class clazz); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLSegment.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLSegment.java new file mode 100644 index 0000000..cc7812a --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/SQLSegment.java @@ -0,0 +1,29 @@ +package group.flyfish.fluent.chain; + +import group.flyfish.fluent.utils.sql.SqlNameUtils; + +/** + * sql片段 + * + * @author wangyu + */ +@FunctionalInterface +public interface SQLSegment { + + /** + * @return 得到sql片段 + */ + String get(); + + /** + * 类型强转,请慎用,除非你知道真实类型 + * + * @param value 任意类型值 + * @param 入参泛型 + * @param 出参泛型 + * @return 转换类型值 + */ + default R cast(T value) { + return SqlNameUtils.cast(value); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/AfterJoinSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/AfterJoinSqlChain.java new file mode 100644 index 0000000..6f382a4 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/AfterJoinSqlChain.java @@ -0,0 +1,26 @@ +package group.flyfish.fluent.chain.common; + +import group.flyfish.fluent.query.Query; + +/** + * join连接后可执行的操作 + * + * @author wangyu + */ +public interface AfterJoinSqlChain { + + /** + * 连接条件 + * + * @param query 查询 + * @return 处理链 + */ + HandleSqlChain on(Query query); + + /** + * 不带连接条件 + * + * @return 处理链 + */ + HandleSqlChain then(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/ExecutableSql.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/ExecutableSql.java new file mode 100644 index 0000000..fbedf9c --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/ExecutableSql.java @@ -0,0 +1,16 @@ +package group.flyfish.fluent.chain.common; + +/** + * 可执行的sql + * + * @author wangyu + */ +public interface ExecutableSql { + + /** + * 执行并获取更新条数 + * + * @return 更新条数 + */ + int execute(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/HandleSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/HandleSqlChain.java new file mode 100644 index 0000000..63a4368 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/HandleSqlChain.java @@ -0,0 +1,20 @@ +package group.flyfish.fluent.chain.common; + +import group.flyfish.fluent.chain.select.AfterWhereSqlChain; +import group.flyfish.fluent.query.Query; + +/** + * 处理阶段的sql链 + * + * @author wangyu + */ +public interface HandleSqlChain extends JoinOperations, AfterWhereSqlChain { + + /** + * 拼接查询条件 + * + * @param query 条件 + * @return 结果 + */ + AfterWhereSqlChain matching(Query query); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/JoinOperations.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/JoinOperations.java new file mode 100644 index 0000000..fff936b --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/JoinOperations.java @@ -0,0 +1,84 @@ +package group.flyfish.fluent.chain.common; + +import group.flyfish.fluent.query.JoinCandidate; + +/** + * 表连接操作 + * + * @author wangyu + */ +public interface JoinOperations { + + /** + * 全量的join连接支持 + * + * @param type 连接类型 + * @param clazz 目标表 + * @param alias 别名 + * @return join后的操作 + */ + AfterJoinSqlChain join(JoinCandidate type, Class clazz, String alias); + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @return join后的操作 + */ + default AfterJoinSqlChain join(Class clazz) { + return join(JoinCandidate.INNER_JOIN, clazz, null); + } + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @param alias 别名 + * @return join后的操作 + */ + default AfterJoinSqlChain join(Class clazz, String alias) { + return join(JoinCandidate.INNER_JOIN, clazz, alias); + } + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @return join后的操作 + */ + default AfterJoinSqlChain leftJoin(Class clazz) { + return join(JoinCandidate.LEFT_JOIN, clazz, null); + } + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @param alias 别名 + * @return join后的操作 + */ + default AfterJoinSqlChain leftJoin(Class clazz, String alias) { + return join(JoinCandidate.LEFT_JOIN, clazz, alias); + } + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @return join后的操作 + */ + default AfterJoinSqlChain rightJoin(Class clazz) { + return join(JoinCandidate.RIGHT_JOIN, clazz, null); + } + + /** + * 使用内连接连接其他表 + * + * @param clazz 其他表实体 + * @param alias 别名 + * @return join后的操作 + */ + default AfterJoinSqlChain rightJoin(Class clazz, String alias) { + return join(JoinCandidate.RIGHT_JOIN, clazz, alias); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/PreSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/PreSqlChain.java new file mode 100644 index 0000000..bd24998 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/common/PreSqlChain.java @@ -0,0 +1,26 @@ +package group.flyfish.fluent.chain.common; + +/** + * 刚设置了查询字段后的链 + */ +public interface PreSqlChain { + + /** + * 从指定表查询 + * + * @param type 表实体 + * @return 处理环节 + */ + HandleSqlChain from(Class type); + + /** + * 从指定表查询,附加别名 + * 该接口适用于同一张表多次from的情况,可以从自表进行多次查询 + * 大部分情况下,您都不需要指定别名 + * + * @param type 类型 + * @param alias 别名 + * @return 处理环节 + */ + HandleSqlChain from(Class type, String alias); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterOrderSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterOrderSqlChain.java new file mode 100644 index 0000000..f229ad6 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterOrderSqlChain.java @@ -0,0 +1,32 @@ +package group.flyfish.fluent.chain.select; + +import group.flyfish.fluent.chain.common.ExecutableSql; + +import java.util.List; + +/** + * order做完后支持的操作 + * + * @author wangyu + */ +public interface AfterOrderSqlChain extends ExecutableSql { + + /** + * 执行并获取结果 + * + * @param clazz 结果类 + * @param 泛型 + * @return 单一结果值 + */ + T one(Class clazz); + + /** + * 执行并获取多条结果 + * + * @param clazz 结果类 + * @param 结果泛型 + * @return 结果列表 + */ + List list(Class clazz); + +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterWhereSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterWhereSqlChain.java new file mode 100644 index 0000000..a558304 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/AfterWhereSqlChain.java @@ -0,0 +1,19 @@ +package group.flyfish.fluent.chain.select; + +import group.flyfish.fluent.chain.Order; + +/** + * where条件后支持的操作 + * + * @author wangyu + */ +public interface AfterWhereSqlChain extends AfterOrderSqlChain { + + /** + * 拼接排序条件 + * + * @param orders 排序 + * @return 结果 + */ + AfterOrderSqlChain order(Order... orders); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/SelectComposite.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/SelectComposite.java new file mode 100644 index 0000000..535153e --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/select/SelectComposite.java @@ -0,0 +1,73 @@ +package group.flyfish.fluent.chain.select; + +import group.flyfish.fluent.utils.context.AliasComposite; +import group.flyfish.fluent.utils.sql.EntityNameUtils; +import group.flyfish.fluent.utils.sql.SFunction; +import group.flyfish.fluent.utils.sql.SqlNameUtils; + +import java.util.Arrays; +import java.util.stream.Stream; + +/** + * 选择语句泛型包装 + * + * @author wangyu + */ +@FunctionalInterface +public interface SelectComposite { + + /** + * 基于泛型的包装,以实体类为T,可以组合任意字段集合 + * + * @param getter 字段属性getter + * @param 实体泛型 + * @return 包装集合 + */ + @SafeVarargs + static SelectComposite composite(SFunction... getter) { + return () -> Arrays.stream(getter); + } + + /** + * 基于单字段composite,指定别名 + * + * @param getter 字段属性getter + * @param alias 别名 + * @param 实体泛型 + * @return 包装集合 + */ + static SelectComposite composite(SFunction getter, String alias) { + AliasComposite.add(getter, alias); + return () -> Stream.of(getter); + } + + /** + * 组合多个组合条件,并进行解包,忽略泛型 + * + * @param composites 多个包 + * @param 泛型,任意返回 + * @return 扁平化的字段列表 + */ + static T combine(SelectComposite... composites) { + return SqlNameUtils.cast(Arrays.stream(composites).flatMap(SelectComposite::stream).toArray(SFunction[]::new)); + } + + /** + * 查询表下面的所有字段 + * + * @param clazz 实体类 + * @param 实体类泛型 + * @return 字段集合 + */ + static SelectComposite all(Class clazz) { + return () -> EntityNameUtils.getFields(clazz).entrySet().stream() + .map(entry -> new SFunction.StaticRef<>(clazz, entry.getKey(), entry.getValue())); + } + + /** + * 返回stream + * + * @return 结果流对象 + */ + Stream> stream(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/update/AfterSetSqlChain.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/update/AfterSetSqlChain.java new file mode 100644 index 0000000..d9509ac --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/chain/update/AfterSetSqlChain.java @@ -0,0 +1,22 @@ +package group.flyfish.fluent.chain.update; + +import group.flyfish.fluent.chain.common.ExecutableSql; +import group.flyfish.fluent.chain.common.PreSqlChain; +import group.flyfish.fluent.chain.select.AfterWhereSqlChain; +import group.flyfish.fluent.query.Query; + +/** + * set之后能干的事儿 + * + * @author wangyu + */ +public interface AfterSetSqlChain extends PreSqlChain, ExecutableSql { + + /** + * 查询条件 + * + * @param query 查询 + * @return 链式调用 + */ + AfterWhereSqlChain matching(Query query); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/mapping/SQLMappedRowMapper.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/mapping/SQLMappedRowMapper.java new file mode 100644 index 0000000..8312abb --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/mapping/SQLMappedRowMapper.java @@ -0,0 +1,362 @@ +package group.flyfish.fluent.mapping; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import group.flyfish.fluent.binding.Alias; +import group.flyfish.fluent.binding.JSONInject; +import group.flyfish.fluent.utils.data.ObjectMappers; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.*; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.dao.DataRetrievalFailureException; +import org.springframework.dao.InvalidDataAccessApiUsageException; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import java.beans.PropertyDescriptor; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * 基于SQL映射的行映射器 + * + * @param 响应实体泛型 + * @author wangyu + */ +@Slf4j +public class SQLMappedRowMapper implements RowMapper { + + private final ObjectMapper objectMapper = ObjectMappers.shared(); + + /** + * The class we are mapping to. + */ + @Nullable + private Class mappedClass; + /** + * ConversionService for binding JDBC values to bean properties. + */ + @Nullable + private ConversionService conversionService = DefaultConversionService.getSharedInstance(); + /** + * Map of the fields we provide mapping for. + */ + @Nullable + private Map mappedFields; + /** + * Map of the fields which need json convert + */ + private Map> jsonFields; + + /** + * Create a new {@code BeanPropertyRowMapper}, accepting unpopulated + * properties in the target bean. + * + * @param mappedClass the class that each row should be mapped to + */ + public SQLMappedRowMapper(Class mappedClass) { + initialize(mappedClass); + } + + /** + * Static factory method to create a new {@code BeanPropertyRowMapper}. + * + * @param mappedClass the class that each row should be mapped to + * @see #newInstance(Class, ConversionService) + */ + public static SQLMappedRowMapper newInstance(Class mappedClass) { + return new SQLMappedRowMapper<>(mappedClass); + } + + /** + * Static factory method to create a new {@code BeanPropertyRowMapper}. + * + * @param mappedClass the class that each row should be mapped to + * @param conversionService the {@link ConversionService} for binding + * JDBC values to bean properties, or {@code null} for none + * @see #newInstance(Class) + * @see #setConversionService + * @since 5.2.3 + */ + public static SQLMappedRowMapper newInstance( + Class mappedClass, @Nullable ConversionService conversionService) { + + SQLMappedRowMapper rowMapper = newInstance(mappedClass); + rowMapper.setConversionService(conversionService); + return rowMapper; + } + + /** + * Get the class that we are mapping to. + */ + @Nullable + public final Class getMappedClass() { + return this.mappedClass; + } + + /** + * Set the class that each row should be mapped to. + */ + public void setMappedClass(Class mappedClass) { + if (this.mappedClass == null) { + initialize(mappedClass); + } else { + if (this.mappedClass != mappedClass) { + throw new InvalidDataAccessApiUsageException("The mapped class can not be reassigned to map to " + + mappedClass + " since it is already providing mapping for " + this.mappedClass); + } + } + } + + /** + * Return a {@link ConversionService} for binding JDBC values to bean properties, + * or {@code null} if none. + * + * @since 4.3 + */ + @Nullable + public ConversionService getConversionService() { + return this.conversionService; + } + + /** + * Set a {@link ConversionService} for binding JDBC values to bean properties, + * or {@code null} for none. + *

Default is a {@link DefaultConversionService}, as of Spring 4.3. This + * provides support for {@code java.time} conversion and other special types. + * + * @see #initBeanWrapper(BeanWrapper) + * @since 4.3 + */ + public void setConversionService(@Nullable ConversionService conversionService) { + this.conversionService = conversionService; + } + + /** + * Initialize the mapping meta-data for the given class. + * + * @param mappedClass the mapped class + */ + protected void initialize(Class mappedClass) { + this.mappedClass = mappedClass; + this.mappedFields = new HashMap<>(); + this.jsonFields = new HashMap<>(); + + Map fieldAnnotations = new HashMap<>(); + ReflectionUtils.doWithFields(mappedClass, field -> fieldAnnotations.put(field.getName(), MergedAnnotations.from(field))); + + for (PropertyDescriptor pd : BeanUtils.getPropertyDescriptors(mappedClass)) { + if (pd.getWriteMethod() != null) { + MergedAnnotations annotations = fieldAnnotations.get(pd.getName()); + String lowerCaseName; + if (annotations.isPresent(Alias.class)) { + String rawName = annotations.get(Alias.class).synthesize().value(); + lowerCaseName = lowerCaseName(rawName.replace("_", "")); + } else { + lowerCaseName = lowerCaseName(pd.getName()); + } + this.mappedFields.put(lowerCaseName, pd); + String underscoreName = underscoreName(pd.getName()); + if (!lowerCaseName.equals(underscoreName)) { + this.mappedFields.put(underscoreName, pd); + } + // 添加json字段 + if (annotations.isPresent(JSONInject.class)) { + this.jsonFields.put(pd.getName(), pd.getPropertyType()); + } + } + } + } + + /** + * Remove the specified property from the mapped fields. + * + * @param propertyName the property name (as used by property descriptors) + * @since 5.3.9 + */ + protected void suppressProperty(String propertyName) { + if (this.mappedFields != null) { + this.mappedFields.remove(lowerCaseName(propertyName)); + this.mappedFields.remove(underscoreName(propertyName)); + } + } + + /** + * Convert the given name to lower case. + * By default, conversions will happen within the US locale. + * + * @param name the original name + * @return the converted name + * @since 4.2 + */ + protected String lowerCaseName(String name) { + return name.toLowerCase(Locale.US); + } + + /** + * Convert a name in camelCase to an underscored name in lower case. + * Any upper case letters are converted to lower case with a preceding underscore. + * + * @param name the original name + * @return the converted name + * @see #lowerCaseName + * @since 4.2 + */ + protected String underscoreName(String name) { + if (!StringUtils.hasLength(name)) { + return ""; + } + + StringBuilder result = new StringBuilder(); + result.append(Character.toLowerCase(name.charAt(0))); + for (int i = 1; i < name.length(); i++) { + char c = name.charAt(i); + if (Character.isUpperCase(c)) { + result.append('_').append(Character.toLowerCase(c)); + } else { + result.append(c); + } + } + return result.toString(); + } + + /** + * Extract the values for all columns in the current row. + *

Utilizes public setters and result set meta-data. + * + * @see java.sql.ResultSetMetaData + */ + @Override + public T mapRow(ResultSet rs, int rowNumber) throws SQLException { + BeanWrapperImpl bw = new BeanWrapperImpl(); + initBeanWrapper(bw); + + T mappedObject = constructMappedInstance(rs, bw); + bw.setBeanInstance(mappedObject); + + ResultSetMetaData rsmd = rs.getMetaData(); + int columnCount = rsmd.getColumnCount(); + + for (int index = 1; index <= columnCount; index++) { + String column = JdbcUtils.lookupColumnName(rsmd, index); + String field = lowerCaseName(StringUtils.delete(column, " ")); + PropertyDescriptor pd = (this.mappedFields != null ? this.mappedFields.get(field) : null); + if (pd != null) { + try { + Object value = getColumnValue(rs, index, pd); + if (rowNumber == 0 && log.isDebugEnabled()) { + log.debug("Mapping column '" + column + "' to property '" + pd.getName() + + "' of type '" + ClassUtils.getQualifiedName(pd.getPropertyType()) + "'"); + } + if (jsonFields.containsKey(pd.getName())) { + value = convert(value, jsonFields.get(pd.getName())); + } + bw.setPropertyValue(pd.getName(), value); + } catch (NotWritablePropertyException ex) { + throw new DataRetrievalFailureException( + "Unable to map column '" + column + "' to property '" + pd.getName() + "'", ex); + } + } + } + + return mappedObject; + } + + /** + * Construct an instance of the mapped class for the current row. + * + * @param rs the ResultSet to map (pre-initialized for the current row) + * @param tc a TypeConverter with this RowMapper's conversion service + * @return a corresponding instance of the mapped class + * @throws SQLException if an SQLException is encountered + * @since 5.3 + */ + protected T constructMappedInstance(ResultSet rs, TypeConverter tc) throws SQLException { + Assert.state(this.mappedClass != null, "Mapped class was not specified"); + return BeanUtils.instantiateClass(this.mappedClass); + } + + /** + * Initialize the given BeanWrapper to be used for row mapping. + * To be called for each row. + *

The default implementation applies the configured {@link ConversionService}, + * if any. Can be overridden in subclasses. + * + * @param bw the BeanWrapper to initialize + * @see #getConversionService() + * @see BeanWrapper#setConversionService + */ + protected void initBeanWrapper(BeanWrapper bw) { + ConversionService cs = getConversionService(); + if (cs != null) { + bw.setConversionService(cs); + } + } + + /** + * Retrieve a JDBC object value for the specified column. + *

The default implementation delegates to + * {@link #getColumnValue(ResultSet, int, Class)}. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @param pd the bean property that each result object is expected to match + * @return the Object value + * @throws SQLException in case of extraction failure + * @see #getColumnValue(ResultSet, int, Class) + */ + @Nullable + protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException { + return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType()); + } + + /** + * Retrieve a JDBC object value for the specified column. + *

The default implementation calls + * {@link JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class)}. + * Subclasses may override this to check specific value types upfront, + * or to post-process values return from {@code getResultSetValue}. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @param paramType the target parameter type + * @return the Object value + * @throws SQLException in case of extraction failure + * @see org.springframework.jdbc.support.JdbcUtils#getResultSetValue(java.sql.ResultSet, int, Class) + * @since 5.3 + */ + @Nullable + protected Object getColumnValue(ResultSet rs, int index, Class paramType) throws SQLException { + return JdbcUtils.getResultSetValue(rs, index, paramType); + } + + /** + * 转换json对象 + * + * @param type 目标类型 + * @param value 值 + * @return 结果 + */ + private Object convert(Object value, Class type) { + if (value instanceof String) { + try { + return objectMapper.readValue((String) value, type); + } catch (JsonProcessingException e) { + log.error("转换json为对象时出错!{}", e.getMessage()); + return null; + } + } + return value; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConcatCandidate.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConcatCandidate.java new file mode 100644 index 0000000..bf95f7b --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConcatCandidate.java @@ -0,0 +1,27 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.chain.SQLSegment; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 连接用的候选 + * + * @author wangyu + */ +@AllArgsConstructor +@Getter +public enum ConcatCandidate implements SQLSegment { + + AND("且"), OR("或"); + + private final String name; + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return name(); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Condition.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Condition.java new file mode 100644 index 0000000..a6f4088 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Condition.java @@ -0,0 +1,47 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.utils.sql.SFunction; + +import java.util.Collection; + +/** + * 条件sql链 + * + * @author wangyu + */ +public interface Condition extends Parameterized, SQLSegment { + + /** + * 等于条件 + * + * @param value 值 + * @return 查询链 + */ + Query eq(Object value); + + /** + * 等于另外一个字段 + * + * @param ref 引用 + * @param 泛型 + * @return 查询链 + */ + Query eq(SFunction ref); + + /** + * 模糊查询 + * + * @param pattern 关键字 + * @return 查询链 + */ + Query like(String pattern); + + /** + * 在集合内 + * + * @param collection 任意内容集合 + * @return 查询链 + */ + Query in(Collection collection); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConditionCandidate.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConditionCandidate.java new file mode 100644 index 0000000..3aa429f --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/ConditionCandidate.java @@ -0,0 +1,72 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.utils.text.TemplateCompiler; +import lombok.Getter; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 查询条件候选模板 + * + * @author wangyu + */ +@Getter +enum ConditionCandidate { + + EQ("字段等于值", "{column} = ?"), + NE("字段不等于值", "{column} != ?"), + GT("字段大于值", "{column} > ?"), + GTE("字段大于等于值", "{column} >= ?"), + LT("字段小于值", "{column} < ?"), + LTE("字段小于等于值", "{column} <= ?"), + LIKE("字段模糊匹配值", "{column} LIKE CONCAT('%', ?, '%')"), + LIKE_LEFT("字段匹配左半部分值", "{column} LIKE CONCAT(?, '%')"), + LIKE_RIGHT("字段匹配右半部分值", "{column} LIKE CONCAT('%', ?)"), + IN("字段在值列表内", "{column} IN ({?})", ConditionCandidate::multiple), + NIN("字段不在值列表内", "{column} NOT IN ({?))", ConditionCandidate::multiple), + NOT_NULL("字段不为空", "{column} IS NOT NULL"), + IS_NULL("字段为空", "{column} IS NULL"), + BETWEEN("字段介于列表下标0和1的值之间", "{column} BETWEEN ? and ?"), + DATE_GTE("日期字段大于值", "{column} > ?"), + DATE_LTE("日期字段小于值", "{column} < ?"); + + private final String name; + + private final TemplateCompiler.DynamicValue template; + + private final Function mapper; + + ConditionCandidate(String name, String template) { + this(name, template, null); + } + + ConditionCandidate(String name, String template, Function mapper) { + this.name = name; + this.template = TemplateCompiler.compile(template); + this.mapper = mapper; + } + + private static String multiple(Object value) { + return value instanceof Collection ? + ((Collection) value).stream().map(v -> "?").collect(Collectors.joining(", ")) : "?"; + } + + /** + * 编译并取得值 + * + * @param field 字段 + * @param value 值 + * @return 结果 + */ + public String compile(String field, Object value) { + Map params = new HashMap<>(); + params.put("column", field); + params.put("value", value); + params.put("?", null != mapper ? mapper.apply(value) : "?"); + return template.apply(params); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/JoinCandidate.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/JoinCandidate.java new file mode 100644 index 0000000..8b0de22 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/JoinCandidate.java @@ -0,0 +1,26 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.chain.SQLSegment; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum JoinCandidate implements SQLSegment { + + INNER_JOIN("内连接", "INNER JOIN"), + LEFT_JOIN("左连接", "LEFT JOIN"), + RIGHT_JOIN("右连接", "RIGHT JOIN"); + + private final String name; + + private final String content; + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return content; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Parameterized.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Parameterized.java new file mode 100644 index 0000000..5f98213 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Parameterized.java @@ -0,0 +1,32 @@ +package group.flyfish.fluent.query; + +import org.springframework.lang.Nullable; + +import java.util.Collection; + +/** + * 表示带参数的类 + * + * @author wangyu + */ +public interface Parameterized { + + /** + * 获取当前对象包含的参数 + * 返回空集合,代表不需要参数 + * 返回null,代表参数为空,不处理该条件或查询 + * + * @return 结果 + */ + @Nullable + Collection getParameters(); + + /** + * 是否为空,参数为null则为空 + * + * @return 结果 + */ + default boolean isEmpty() { + return null == getParameters(); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Query.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Query.java new file mode 100644 index 0000000..118e301 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/Query.java @@ -0,0 +1,72 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.utils.sql.SFunction; + +/** + * 查询构建入口 + * + * @author wangyu + */ +public interface Query extends Parameterized, SQLSegment { + + /** + * 从where开始 + * + * @param getter 字段lambda + * @param 实体泛型 + * @return 构建条件 + */ + static Condition where(SFunction getter) { + return new SimpleCondition(getter, new SimpleQuery()); + } + + /** + * 以且连接下一个条件 + * + * @param getter 字段lambda + * @return 构建操作 + */ + Condition and(SFunction getter); + + /** + * 以且直接连接其他条件 + * + * @param condition 其他条件 + * @return 查询操作 + */ + Query and(Condition condition); + + /** + * 以且嵌套其他查询条件 + * + * @param query 查询条件 + * @return 结果 + */ + Query and(Query query); + + + /** + * 以或连接下一个条件 + * + * @param getter 字段lambda + * @return 构建操作 + */ + Condition or(SFunction getter); + + /** + * 以或直接连接其他条件 + * + * @param condition 其他条件 + * @return 查询操作 + */ + Query or(Condition condition); + + /** + * 以或嵌套其他查询条件 + * + * @param query 查询条件 + * @return 结果 + */ + Query or(Query query); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleCondition.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleCondition.java new file mode 100644 index 0000000..4bb83e0 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleCondition.java @@ -0,0 +1,115 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.utils.sql.EntityNameUtils; +import group.flyfish.fluent.utils.sql.SFunction; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Function; + +/** + * 查询条件 + * + * @author wangyu + */ +class SimpleCondition implements Condition { + + private final SFunction target; + + private final Function callback; + + private Object value; + + private ConditionCandidate candidate; + + public SimpleCondition(SFunction target, SimpleQuery query) { + this(target, query::and); + } + + public SimpleCondition(SFunction target, Function callback) { + this.target = target; + this.callback = callback; + } + + @Override + @Nullable + public Collection getParameters() { + if (ObjectUtils.isEmpty(value)) { + return null; + } + if (value instanceof Collection) { + return cast(value); + } + if (value instanceof SFunction) { + return Collections.emptyList(); + } + return Collections.singletonList(value); + } + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + String compiled = candidate.compile(target.getName(), value); + // 值属于引用时,替换参数为引用 + if (value instanceof SFunction) { + return compiled.replace("?", EntityNameUtils.toName(cast(value))); + } + return compiled; + } + + /** + * 等于条件 + * + * @param value 值 + * @return 查询链 + */ + @Override + public Query eq(Object value) { + this.value = value; + this.candidate = ConditionCandidate.EQ; + return callback.apply(this); + } + + /** + * 等于另外一个字段 + * + * @param ref 引用 + * @return 查询链 + */ + @Override + public Query eq(SFunction ref) { + this.value = ref; + this.candidate = ConditionCandidate.EQ; + return callback.apply(this); + } + + /** + * 模糊查询 + * + * @param pattern 关键字 + * @return 查询链 + */ + @Override + public Query like(String pattern) { + this.value = pattern; + this.candidate = ConditionCandidate.LIKE; + return callback.apply(this); + } + + /** + * 在集合内 + * + * @param collection 任意内容集合 + * @return 查询链 + */ + @Override + public Query in(Collection collection) { + this.value = collection; + this.candidate = ConditionCandidate.IN; + return callback.apply(this); + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleQuery.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleQuery.java new file mode 100644 index 0000000..e9377bc --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/query/SimpleQuery.java @@ -0,0 +1,142 @@ +package group.flyfish.fluent.query; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.utils.sql.ConcatSegment; +import group.flyfish.fluent.utils.sql.SFunction; +import org.springframework.lang.Nullable; +import org.springframework.util.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.stream.Collectors; + +import static group.flyfish.fluent.query.ConcatCandidate.AND; +import static group.flyfish.fluent.query.ConcatCandidate.OR; + +/** + * 查询构建器api + * + * @author wangyu + */ +class SimpleQuery extends ConcatSegment implements Query { + + // 参数源 + private final Collection parameters = new ArrayList<>(); + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return segments.stream().map(SQLSegment::get).collect(Collectors.joining(" ")); + } + + /** + * 获取参数源 + * + * @return 结果 + */ + @Override + @Nullable + public Collection getParameters() { + if (segments.isEmpty()) { + return null; + } + return parameters; + } + + /** + * 以与连接下一个条件 + * + * @param getter 字段lambda + * @return 构建操作 + */ + @Override + public Condition and(SFunction getter) { + return new SimpleCondition(getter, this::and); + } + + /** + * 直接连接其他条件 + * + * @param condition 其他条件 + * @return 查询操作 + */ + @Override + public Query and(Condition condition) { + if (condition.isEmpty()) { + return this; + } + addParameters(condition); + return concat(AND).concat(condition); + } + + /** + * 嵌套其他查询条件 + * + * @param query 查询条件 + * @return 结果 + */ + @Override + public Query and(Query query) { + if (query.isEmpty()) { + return this; + } + addParameters(query); + return concat(AND).concat(query); + } + + /** + * 以或连接下一个条件 + * + * @param getter 字段lambda + * @return 构建操作 + */ + @Override + public Condition or(SFunction getter) { + return new SimpleCondition(getter, this::or); + } + + /** + * 以或直接连接其他条件 + * + * @param condition 其他条件 + * @return 查询操作 + */ + @Override + public Query or(Condition condition) { + if (condition.isEmpty()) { + return this; + } + addParameters(condition); + return concat(OR).concat(condition); + } + + /** + * 以或嵌套其他查询条件 + * + * @param query 查询条件 + * @return 结果 + */ + @Override + public Query or(Query query) { + if (query.isEmpty()) { + return this; + } + addParameters(query); + return concat(OR).concat(query); + } + + /** + * 添加参数 + * + * @param parameterized 带参数的对象 + */ + private void addParameters(Parameterized parameterized) { + Collection params = parameterized.getParameters(); + if (!CollectionUtils.isEmpty(params)) { + // 优先拼接参数 + parameters.addAll(params); + } + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/update/Update.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/update/Update.java new file mode 100644 index 0000000..77601ad --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/update/Update.java @@ -0,0 +1,52 @@ +package group.flyfish.fluent.update; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.chain.update.AfterSetSqlChain; +import group.flyfish.fluent.query.Parameterized; +import group.flyfish.fluent.utils.sql.SFunction; + +/** + * 更新内容类 + * + * @author wangyu + */ +public interface Update extends SQLSegment, Parameterized { + + /** + * 基于对象设置所有同名值 + * + * @param value 值 + * @return 链式 + */ + default Update setAll(Object value) { + throw new UnsupportedOperationException("当前未实现该操作!"); + } + + /** + * 设置值,来自具体值 + * + * @param target 目标字段 + * @param value 具体值 + * @param 泛型 + * @return 链式 + */ + Update set(SFunction target, Object value); + + /** + * 设置值,来自其他表字段 + * + * @param target 目标字段 + * @param source 源端字段 + * @param 目标泛型 + * @param 源端泛型 + * @return 链式 + */ + Update set(SFunction target, SFunction source); + + /** + * 接下来要做的事 + * + * @return 结果 + */ + AfterSetSqlChain then(); +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/update/UpdateImpl.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/update/UpdateImpl.java new file mode 100644 index 0000000..512e1bc --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/update/UpdateImpl.java @@ -0,0 +1,110 @@ +package group.flyfish.fluent.update; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.chain.update.AfterSetSqlChain; +import group.flyfish.fluent.utils.sql.ConcatSegment; +import group.flyfish.fluent.utils.sql.SFunction; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.Nullable; +import org.springframework.util.ObjectUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 更新实现 + * + * @author wangyu + */ +@RequiredArgsConstructor +public class UpdateImpl implements Update { + + private final Function chain; + + // 参数源 + private final Collection parameters = new ArrayList<>(); + + // 片段 + private final List segments = new ArrayList<>(); + + /** + * 设置值,来自具体值 + * + * @param target 目标字段 + * @param value 具体值 + * @return 链式 + */ + @Override + public Update set(SFunction target, Object value) { + if (ObjectUtils.isEmpty(value)) { + return this; + } + this.parameters.add(value); + segments.add(new UpdatePart().concat(target::getName).concat("=").concat("?")); + return this; + } + + /** + * 设置值,来自其他表字段 + * + * @param target 目标字段 + * @param source 源端字段 + * @return 链式 + */ + @Override + public Update set(SFunction target, SFunction source) { + segments.add(new UpdatePart().concat(target::getName).concat("=").concat(source::getName)); + return this; + } + + /** + * 接下来要做的事 + * + * @return 结果 + */ + @Override + public AfterSetSqlChain then() { + return chain.apply(this); + } + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return segments.stream().map(SQLSegment::get).collect(Collectors.joining(", ")); + } + + /** + * 获取当前对象包含的参数 + * 返回空集合,代表不需要参数 + * 返回null,代表参数为空,不处理该条件或查询 + * + * @return 结果 + */ + @Override + @Nullable + public Collection getParameters() { + if (segments.isEmpty()) { + return null; + } + return parameters; + } + + /** + * 内部的部分 + */ + private static class UpdatePart extends ConcatSegment implements SQLSegment { + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return this.segments.stream().map(SQLSegment::get).collect(Collectors.joining(" ")); + } + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/cache/LRUCache.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/cache/LRUCache.java new file mode 100644 index 0000000..a05f073 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/cache/LRUCache.java @@ -0,0 +1,36 @@ +package group.flyfish.fluent.utils.cache; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * LRU (least recently used)最近最久未使用缓存
+ * 根据使用时间来判定对象是否被持续缓存
+ * 当对象被访问时放入缓存,当缓存满了,最久未被使用的对象将被移除。
+ * 此缓存基于LinkedHashMap,因此当被缓存的对象每被访问一次,这个对象的key就到链表头部。
+ * 这个算法简单并且非常快,他比FIFO有一个显著优势是经常使用的对象不太可能被移除缓存。
+ * 缺点是当缓存满时,不能被很快的访问。 + * + * @param 键类型 + * @param 值类型 + * @author wangyu + */ +public class LRUCache extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private final int maxSize; + + public LRUCache(int maxSize) { + this(maxSize, 16, 0.75f, false); + } + + public LRUCache(int maxSize, int initialCapacity, float loadFactor, boolean accessOrder) { + super(initialCapacity, loadFactor, accessOrder); + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > this.maxSize; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/context/AliasComposite.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/context/AliasComposite.java new file mode 100644 index 0000000..2dadd0c --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/context/AliasComposite.java @@ -0,0 +1,182 @@ +package group.flyfish.fluent.utils.context; + +import group.flyfish.fluent.utils.sql.SFunction; +import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 别名管理器 + * 因sql构建器运行时线程确定,故该类使用线程局部变量 + * + * @author wangyu + */ +public final class AliasComposite { + + private static final String PREFIX = "t"; + + // 表、列别名存储,本质上是一个简单map。线程局部缓存,阅后即焚 + private static final ThreadLocal ALIAS = new ThreadLocal<>(); + + /** + * 添加别名 + * + * @param key 别名key + * @param alias 别名 + */ + public static String add(Class key, String alias) { + return sharedCache().add(key, alias); + } + + /** + * 添加别名 + * + * @param key 别名key + * @param alias 别名 + */ + public static String add(SFunction key, String alias) { + return sharedCache().add(key, alias); + } + + + /** + * 判断表是否有别名 + * + * @param key 实体类 + * @return 是否存在 + */ + public static boolean has(Class key) { + return sharedCache().has(key); + } + + /** + * 判断表是否有别名 + * + * @param key 实体类 + * @return 是否存在 + */ + public static boolean has(SFunction key) { + return sharedCache().has(key); + } + + /** + * 获取别名 + * + * @param key 键 + * @return 结果 + */ + public static String get(Class key) { + return sharedCache().get(key); + } + + /** + * 获取别名 + * + * @param key 键 + * @return 结果 + */ + public static String get(SFunction key) { + return sharedCache().get(key); + } + + /** + * 清空别名缓存 + */ + public static void flush() { + ALIAS.remove(); + } + + public static AliasCache sharedCache() { + AliasCache cache = ALIAS.get(); + if (null == cache) { + cache = new AliasCache(); + ALIAS.set(cache); + } + return cache; + } + + public static class AliasCache { + + // 表别名缓存map + private final Map, String> instance = new ConcurrentHashMap<>(); + + // 表别名内置计数 + private final AtomicInteger counter = new AtomicInteger(0); + + // 列别名缓存map + private final Map, String> columns = new ConcurrentHashMap<>(); + + /** + * 添加缓存,基本规则: + * 1. 新缓存指定手动别名,优先以手动别名为准 + * 2. 未指定手动别名,且存在缓存的,不更新别名 + * + * @param key 缓存key + * @param alias 缓存别名,可为空 + * @return 返回缓存的别名 + */ + public String add(Class key, @Nullable String alias) { + if (StringUtils.hasText(alias)) { + instance.put(key, alias); + return alias; + } else { + return get(key); + } + } + + public boolean has(Class key) { + return instance.containsKey(key); + } + + public String get(Class key) { + return instance.computeIfAbsent(key, this::generate); + } + + /** + * 生成key + * + * @param key 类型key + * @return 生成的key + */ + public String generate(Class key) { + return PREFIX + counter.incrementAndGet(); + } + + /** + * 判断是否含有某个列引用的别名 + * + * @param column 列引用 + * @param 泛型 + * @return 是否存在 + */ + public boolean has(SFunction column) { + return columns.containsKey(column); + } + + /** + * 列缓存 + * + * @param column 列引用 + * @param alias 别名 + * @param 实体泛型 + */ + public String add(SFunction column, String alias) { + columns.put(column, alias); + return alias; + } + + /** + * 获取列引用所绑定的别名 + * + * @param column 列引用 + * @param 泛型 + * @return 别名,可能为空 + */ + public String get(SFunction column) { + return columns.get(column); + } + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ObjectMappers.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ObjectMappers.java new file mode 100644 index 0000000..ac32041 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ObjectMappers.java @@ -0,0 +1,32 @@ +package group.flyfish.fluent.utils.data; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import java.text.SimpleDateFormat; + +/** + * 项目唯一的jackson工具 + * + * @author wangyu + */ +public final class ObjectMappers { + + private static final ObjectMapper objectMapper; + + static { + objectMapper = new ObjectMapper(); + objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); + } + + private ObjectMappers() { + + } + + public static ObjectMapper shared() { + return objectMapper; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ParameterUtils.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ParameterUtils.java new file mode 100644 index 0000000..25174cc --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/data/ParameterUtils.java @@ -0,0 +1,31 @@ +package group.flyfish.fluent.utils.data; + +import com.fasterxml.jackson.core.JsonProcessingException; +import org.springframework.beans.BeanUtils; + +import java.security.InvalidParameterException; + +/** + * 参数工具类 + * + * @author wangyu + */ +public final class ParameterUtils { + + public static Object convert(Object value) { + if (null == value) { + return null; + } + if (value instanceof Enum) { + return String.valueOf(value); + } + if (BeanUtils.isSimpleProperty(value.getClass())) { + return value; + } + try { + return ObjectMappers.shared().writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new InvalidParameterException("不是一个json数据,或者是未识别的类!"); + } + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/ConcatSegment.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/ConcatSegment.java new file mode 100644 index 0000000..bc63e0f --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/ConcatSegment.java @@ -0,0 +1,63 @@ +package group.flyfish.fluent.utils.sql; + +import group.flyfish.fluent.chain.SQLSegment; +import group.flyfish.fluent.query.ConcatCandidate; +import lombok.RequiredArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 可连接的片段 + * + * @author wangyu + */ +public abstract class ConcatSegment> { + + // 处理链集合 + protected final List segments = new ArrayList<>(); + + /** + * 拼接sql片段 + * + * @param segment 片段 + * @return 链式调用 + */ + public T concat(SQLSegment segment) { + if (segments.isEmpty() && segment instanceof ConcatCandidate) { + return self(); + } + this.segments.add(segment); + return self(); + } + + /** + * 拼接静态sql片段 + * + * @param content 内容 + * @return 结果 + */ + public T concat(String content) { + this.segments.add(new StaticSegment(content)); + return self(); + } + + @SuppressWarnings("unchecked") + private T self() { + return (T) this; + } + + @RequiredArgsConstructor + private static class StaticSegment implements SQLSegment { + + private final String content; + + /** + * @return 得到sql片段 + */ + @Override + public String get() { + return content; + } + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/EntityNameUtils.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/EntityNameUtils.java new file mode 100644 index 0000000..372d238 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/EntityNameUtils.java @@ -0,0 +1,157 @@ +package group.flyfish.fluent.utils.sql; + +import group.flyfish.fluent.utils.cache.LRUCache; +import group.flyfish.fluent.utils.context.AliasComposite; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +import javax.persistence.Column; +import javax.persistence.Table; +import java.lang.ref.WeakReference; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BinaryOperator; + +import static group.flyfish.fluent.utils.sql.SqlNameUtils.wrap; + +/** + * 属性名字处理器 + * + * @author wangyu + */ +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class EntityNameUtils { + + // SerializedLambda 反序列化缓存 + private static final Map> FUNC_CACHE = new ConcurrentHashMap<>(); + + // 列别名缓存 + private static final Map, Map> COLUMN_CACHE = new LRUCache<>(5); + + // 表名缓存 + private static final Map, String> TABLE_CACHE = new LRUCache<>(3); + + public static String toName(SFunction func) { + return resolve(func, (column, property) -> wrap(column)); + } + + public static String toSelect(SFunction func) { + return resolve(func, + (column, property) -> String.join(" ", wrap(column), "as", wrap(property))); + } + + public static Map getFields(Class clazz) { + tryCache(clazz); + return COLUMN_CACHE.get(clazz); + } + + /** + * 从一个实体类中取得表名 + * + * @param entityClass 实体类 + * @return 结果 + */ + public static String getTableName(Class entityClass) { + return TABLE_CACHE.computeIfAbsent(entityClass, k -> { + Table table = entityClass.getAnnotation(Table.class); + if (null != table && StringUtils.hasText(table.name())) { + return table.name(); + } + return wrap(SqlNameUtils.camelToUnderline(entityClass.getSimpleName())); + }); + } + + /** + * 解析列并使用处理函数处理 + * + * @param func 方法引用 + * @param handler 处理器 + * @param 实体泛型 + * @return 处理结果 + */ + private static String resolve(SFunction func, BinaryOperator handler) { + SerializedLambda lambda = resolve(func); + String property = SqlNameUtils.methodToProperty(lambda.getImplMethodName()); + Class beanClass = resolveEntityClass(lambda); + String column = COLUMN_CACHE.get(beanClass).getOrDefault(property, SqlNameUtils.camelToUnderline(property)); + // 取得别名缓存 + AliasComposite.AliasCache cache = AliasComposite.sharedCache(); + // 确定最终名称 + String finalName = cache.has(func) ? cache.get(func) : property; + if (cache.has(beanClass)) { + return cache.get(beanClass) + "." + handler.apply(column, finalName); + } + return handler.apply(column, finalName); + } + + + /** + * 解析方法引用为序列化lambda实例 + * 该方式会使用缓存 + * + * @param func 方法引用 + * @param 泛型 + * @return 解析结果 + */ + private static SerializedLambda resolve(SFunction func) { + Class clazz = func.getClass(); + String name = clazz.getName(); + return Optional.ofNullable(FUNC_CACHE.get(name)) + .map(WeakReference::get) + .orElseGet(() -> { + SerializedLambda lambda = SerializedLambda.resolve(func); + FUNC_CACHE.put(name, new WeakReference<>(lambda)); + return lambda; + }); + } + + /** + * 解析获得实体类 + * + * @param lambda 序列化的lambda + * @return 最终获取的类 + */ + private static Class resolveEntityClass(SerializedLambda lambda) { + return tryCache(lambda.getInstantiatedType()); + } + + /** + * 构建字段缓存 + * + * @param type 类型 + * @return 构建后的缓存 + */ + private static Map buildFieldsCache(Class type) { + Map fields = new HashMap<>(); + ReflectionUtils.doWithFields(type, field -> fields.put(field.getName(), resolveFinalName(field))); + return fields; + } + + /** + * 解析字段注解或直接取用下划线逻辑 + * + * @return 解析结果 + */ + private static String resolveFinalName(Field field) { + Column column = field.getAnnotation(Column.class); + if (null != column && StringUtils.hasText(column.name())) { + return column.name(); + } + return SqlNameUtils.camelToUnderline(field.getName()); + } + + /** + * 尝试缓存 + * + * @param entityClass bean的类型 + */ + private static Class tryCache(Class entityClass) { + COLUMN_CACHE.computeIfAbsent(entityClass, EntityNameUtils::buildFieldsCache); + return entityClass; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SFunction.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SFunction.java new file mode 100644 index 0000000..9923b4a --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SFunction.java @@ -0,0 +1,82 @@ +package group.flyfish.fluent.utils.sql; + +import group.flyfish.fluent.utils.context.AliasComposite; +import lombok.RequiredArgsConstructor; + +import java.io.Serializable; +import java.util.function.Function; +import java.util.function.Supplier; + +import static group.flyfish.fluent.utils.sql.SqlNameUtils.wrap; + +/** + * 支持序列化的function + * + * @param 泛型入参 + * @param 泛型返回值 + */ +@FunctionalInterface +public interface SFunction extends Function, Serializable { + + /** + * 快速获取名称 + * + * @return 序列化后的名称 + */ + default String getName() { + return EntityNameUtils.toName(this); + } + + /** + * 快速获取选择语句,附带别名 + * + * @return 结果 + */ + default String getSelect() { + return EntityNameUtils.toSelect(this); + } + + @SuppressWarnings("unchecked") + default SFunction cast() { + return (SFunction) this; + } + + /** + * 覆盖写法 + * + * @param 泛型 + * @param 泛型 + */ + @RequiredArgsConstructor + class StaticRef implements SFunction { + + private final Class type; + + private final String name; + + private final String column; + + @Override + public String getName() { + return handle(() -> wrap(name)); + } + + @Override + public String getSelect() { + return handle(() -> String.join(" ", wrap(column), "as", wrap(name))); + } + + private String handle(Supplier handler) { + if (AliasComposite.has(type)) { + return AliasComposite.get(type) + "." + handler.get(); + } + return handler.get(); + } + + @Override + public R apply(T t) { + return null; + } + } + +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SerializedLambda.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SerializedLambda.java new file mode 100644 index 0000000..f361f01 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SerializedLambda.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2011-2021, baomidou (jobob@qq.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package group.flyfish.fluent.utils.sql; + +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.SerializationUtils; + +import java.io.*; + +/** + * 这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的, + * 字段信息完全一样 + *

负责将一个支持序列的 Function 序列化为 SerializedLambda

+ * + * @author mbp + */ +@SuppressWarnings("unused") +class SerializedLambda implements Serializable { + + private static final long serialVersionUID = 8025925345765570181L; + + private Class capturingClass; + private String functionalInterfaceClass; + private String functionalInterfaceMethodName; + private String functionalInterfaceMethodSignature; + private String implClass; + private String implMethodName; + private String implMethodSignature; + private int implMethodKind; + private String instantiatedMethodType; + private Object[] capturedArgs; + + /** + * 通过反序列化转换 lambda 表达式,该方法只能序列化 lambda 表达式,不能序列化接口实现或者正常非 lambda 写法的对象 + * + * @param lambda lambda对象 + * @return 返回解析后的 SerializedLambda + */ + public static SerializedLambda resolve(SFunction lambda) { + Assert.isTrue(lambda.getClass().isSynthetic(), "该方法仅能传入 lambda 表达式产生的合成类"); + byte[] stream = SerializationUtils.serialize(lambda); + Assert.notNull(stream, "序列化类失败!"); + try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(stream)) { + @Override + protected Class resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException { + Class clazz; + try { + clazz = forName(objectStreamClass.getName()); + } catch (Exception ex) { + clazz = super.resolveClass(objectStreamClass); + } + return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz; + } + }) { + return (SerializedLambda) objIn.readObject(); + } catch (ClassNotFoundException | IOException e) { + throw new NullPointerException("不应该发生这件事"); + } + } + + /** + * 以类名实例化类,会隐藏报错,需要在确定有该类的情况下调用 + * + * @param name 类型 + * @return 实例 + */ + private static Class forName(String name) { + try { + return ClassUtils.forName(name, null); + } catch (ClassNotFoundException e) { + return null; + } + } + + /** + * 获取接口 class + * + * @return 返回 class 名称 + */ + public String getFunctionalInterfaceClassName() { + return normalizedName(functionalInterfaceClass); + } + + /** + * 获取实现的 class + * + * @return 实现类 + */ + public Class getImplClass() { + return forName(getImplClassName()); + } + + /** + * 获取 class 的名称 + * + * @return 类名 + */ + public String getImplClassName() { + return normalizedName(implClass); + } + + /** + * 获取实现者的方法名称 + * + * @return 方法名称 + */ + public String getImplMethodName() { + return implMethodName; + } + + /** + * 正常化类名称,将类名称中的 / 替换为 . + * + * @param name 名称 + * @return 正常的类名 + */ + private String normalizedName(String name) { + return name.replace('/', '.'); + } + + /** + * @return 获取实例化方法的类型 + */ + public Class getInstantiatedType() { + String instantiatedTypeName = normalizedName(instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(';'))); + return forName(instantiatedTypeName); + } + + /** + * @return 字符串形式 + */ + @Override + public String toString() { + String interfaceName = getFunctionalInterfaceClassName(); + String implName = getImplClassName(); + return String.format("%s -> %s::%s", + interfaceName.substring(interfaceName.lastIndexOf('.') + 1), + implName.substring(implName.lastIndexOf('.') + 1), + implMethodName); + } + +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SqlNameUtils.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SqlNameUtils.java new file mode 100644 index 0000000..4bbffd7 --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/sql/SqlNameUtils.java @@ -0,0 +1,103 @@ +package group.flyfish.fluent.utils.sql; + +import java.security.InvalidParameterException; +import java.util.Locale; + +public class SqlNameUtils { + + /** + * 方法转为属性 + * + * @param name 方法名称 + * @return 最终属性 + */ + public static String methodToProperty(String name) { + if (name.startsWith("is")) { + name = name.substring(2); + } else { + if (!name.startsWith("get") && !name.startsWith("set")) { + throw new InvalidParameterException("不是正确的getter或setter"); + } + name = name.substring(3); + } + if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) { + name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1); + } + return name; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。 + * 如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。
+ * 例如:hello_world->helloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String camelName(String name) { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) { + // 没必要转换 + return ""; + } else if (!name.contains("_")) { + // 不含下划线,仅将首字母小写 + //update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能 + //update-begin--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能 + return name.substring(0, 1).toLowerCase() + name.substring(1).toLowerCase(); + //update-end--Author:zhoujf Date:20180503 for:TASK #2500 【代码生成器】代码生成器开发一通用模板生成功能 + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) { + continue; + } + // 处理真正的驼峰片段 + if (result.length() == 0) { + // 第一个驼峰片段,全部字母都小写 + result.append(camel.toLowerCase()); + } else { + // 其他的驼峰片段,首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + } + return result.toString(); + } + + /** + * 将驼峰命名转化成下划线 + * + * @param para + * @return + */ + public static String camelToUnderline(String para) { + if (para.length() < 3) { + return para.toLowerCase(); + } + StringBuilder sb = new StringBuilder(para); + int temp = 0;//定位 + //从第三个字符开始 避免命名不规范 + for (int i = 2; i < para.length(); i++) { + if (Character.isUpperCase(para.charAt(i))) { + sb.insert(i + temp, "_"); + temp += 1; + } + } + return sb.toString().toLowerCase(); + } + + public static String wrap(String identifier) { + if (identifier.startsWith("`")) { + return identifier; + } + return String.join("", "`", identifier, "`"); + } + + @SuppressWarnings("unchecked") + public static R cast(T t) { + return (R) t; + } +} diff --git a/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/text/TemplateCompiler.java b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/text/TemplateCompiler.java new file mode 100644 index 0000000..5d4d8ad --- /dev/null +++ b/fluent-sql-core/src/main/java/group/flyfish/fluent/utils/text/TemplateCompiler.java @@ -0,0 +1,98 @@ +package group.flyfish.fluent.utils.text; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * 模板编译器 + * 基于匹配模式,将字符串替换为表达式,通过sb返回结果 + */ +public class TemplateCompiler { + + // 暂存静态文本 + private final StringBuilder sb = new StringBuilder(); + // 负责存储结果 + private final List parts = new ArrayList<>(); + // 栈。负责存储标记 + private int start = -1; + + private TemplateCompiler(String code) { + parse(code); + } + + /** + * 静态编译,编译为Function + * + * @param template 模板 + * @return 编译结果 + */ + public static DynamicValue compile(String template) { + return new TemplateCompiler(template).getCompiled(); + } + + /** + * 动态编译,直接获得结果 + * + * @return 结果 + */ + public static String explain(String template, Map args) { + return new TemplateCompiler(template).getCompiled().apply(args); + } + + private void parse(String code) { + int length = code.length(); + for (int i = 0; i < length; i++) { + char c = code.charAt(i); + // 在栈作用中 + if (start != -1) { + if (c == '}') { + parts.add(resolveExpression(code.substring(start, i))); + start = -1; + } + } else { + // 匹配到开始标记 + if (c == '{') { + start = i + 1; + // 文本缓存非空,拼接之前的文本 + if (sb.length() != 0) { + parts.add(sb.toString()); + sb.delete(0, sb.length()); + } + } else { + sb.append(c); + } + } + } + // 拼接完成,释放sb资源。 + if (sb.length() != 0) { + parts.add(sb.toString()); + sb.delete(0, sb.length()); + } + } + + /** + * 获取编译后的内容 + * + * @return 结果 + */ + public DynamicValue getCompiled() { + return context -> parts.stream().map(obj -> { + if (obj instanceof DynamicValue) { + return ((DynamicValue) obj).apply(context); + } + return String.valueOf(obj); + }).collect(Collectors.joining()); + } + + private DynamicValue resolveExpression(String key) { + return context -> String.valueOf(context.get(key)); + } + + @FunctionalInterface + public interface DynamicValue extends Function, String> { + + } +} diff --git a/fluent-sql-jdbctemplate/pom.xml b/fluent-sql-jdbctemplate/pom.xml new file mode 100644 index 0000000..2e0ee1c --- /dev/null +++ b/fluent-sql-jdbctemplate/pom.xml @@ -0,0 +1,19 @@ + + + + fluent-sql + group.flyfish.framework + 1.0-SNAPSHOT + + 4.0.0 + + fluent-sql-jdbctemplate + + + 8 + 8 + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..199020f --- /dev/null +++ b/pom.xml @@ -0,0 +1,181 @@ + + + 4.0.0 + + group.flyfish.framework + fluent-sql + pom + 1.0-SNAPSHOT + + fluent-sql-core + fluent-sql-jdbctemplate + + + + 8 + 8 + 1.18.24 + 2.13.3 + 5.3.22 + + + + + + wangyu + wybaby168@gmail.com + http://flyfish.group + +8 + + + + + + central + https://maven.aliyun.com/nexus/content/groups/public + + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.springframework + spring-jdbc + ${spring.version} + + + org.slf4j + slf4j-api + 1.7.36 + + + javax.persistence + persistence-api + 1.0.2 + + + + + + + release + + + + org.apache.maven.plugins + maven-javadoc-plugin + + package + none + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${maven.compiler.source} + ${maven.compiler.target} + UTF-8 + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.1 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + false + true + true + + + + + + +