first commit

This commit is contained in:
开源海哥 2023-03-01 13:10:17 +08:00
commit 83e9a89c6d
130 changed files with 14324 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
target
logs
.idea
*.iml
*.log
.logs
*.iws
*.report
*.classpath
.DS_Store
.ossutil_checkpoint
*.patch
~$*
node_modules
yarn.lock
dist
package-lock.json
remarks.md

201
LICENSE Normal file
View File

@ -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.

2
changes.txt Normal file
View File

@ -0,0 +1,2 @@
mybatis-flex v1.0.0-beta.1:
init mybatis-flex

0
docs/readme.md Normal file
View File

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>com.mybatis-flex</groupId>
<version>1.0.0-beta.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-flex-annotation</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<excludes>
<exclude>
*.properties
</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<proc>none</proc>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Column {
/**
* 字段名称
*/
String value() default "";
/**
* update 的时候自动赋值这个值会直接被拼接到 sql 而不通过参数设置
*/
String update() default "";
/**
* insert 的时候默认值这个值会直接被拼接到 sql 而不通过参数设置
*/
String insert() default "";
/**
* 是否忽略该字段可能只是业务字段而非数据库对应字段
*/
boolean ignore() default false;
/**
* 是否是大字段大字默认不会对齐进行查询除非指定查询
*/
boolean isLarge() default false;
/**
* 是否是逻辑删除字段一张表中只能存在 1 一个逻辑删除字段
*/
boolean isLogicDelete() default false;
/**
* 是否为乐观锁字段如果是的话更新的时候会去检测当前版本号更新成功的话会设置当前版本号 +1
* 只能用于数值的字段
*/
boolean version() default false;
}

View File

@ -0,0 +1,52 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.annotation;
import com.mybatisflex.core.enums.KeyType;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Id {
/**
* ID 生成策略默认为 auto
*
* @return 生成策略
*/
KeyType keyType() default KeyType.Auto;
/**
* keyType 类型是 sequence value 则代表的是
* sequence 序列的 sql 内容
* 例如select SEQ_USER_ID.nextval as id from dual
*
* keyType Generatorvalue 则代表的是使用的那个 keyGenerator 的名称
*
*/
String value() default "";
/**
* sequence 序列执行顺序
* 是在 entity 数据插入之前执行还是之后执行之后执行的一般是数据主动生成的 id
*
* @return 执行之前还是之后
*/
boolean before() default true;
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.annotation;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Table {
/**
* 显式指定表名称
*/
String value();
/**
* 数据库的 schema
*/
String schema() default "";
/**
* 是否使用 mybatis 缓存
*/
boolean useCached() default false;
/**
* 默认为 驼峰属性 转换为 下划线字段
*/
boolean camelToUnderline() default true;
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.enums;
/**
* ID 生成策略
*/
public enum KeyType {
/**
* 自增的方式
*/
Auto,
/**
* 通过执行数据库 sql 生成
* 例如select SEQ_USER_ID.nextval as id from dual
*/
Sequence,
/**
* 通过 IKeyGenerator 生成器生成
*/
Generator,
/**
* 其他方式比如说在代码层用户手动设置
*/
None,
}

View File

@ -0,0 +1,108 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.processer;
import java.io.*;
import java.net.URL;
import java.util.Properties;
class MyBatisFlexProps {
protected Properties properties = null;
private static final String DEFAULT_ENCODING = "UTF-8";
public MyBatisFlexProps(String fileName) {
this(fileName, DEFAULT_ENCODING);
}
public MyBatisFlexProps(String fileName, String encoding) {
properties = new Properties();
InputStream inputStream = null;
try {
inputStream = getResourceAsStreamByCurrentThread(fileName);
if (inputStream == null) {
inputStream = getResourceAsStreamByClassloader(fileName);
}
// 当系统未编译的时候开发环境下的 resources 目录下的 properties 文件不会自动被 copy target/classes 目录下
// 此时需要主动去探测 resources 目录的文件
if (inputStream == null) {
URL resourceURL = MyBatisFlexProps.class.getResource("/");
if (resourceURL != null) {
String classPath = resourceURL.toURI().getPath();
if (removeSlashEnd(classPath).endsWith("classes")) {
//from classes path
File propFile = new File(classPath, fileName);
if (propFile.exists() && propFile.isFile()) {
inputStream = new FileInputStream(propFile);
}
//from resources path
else {
File resourcesDir = new File(classPath, "../../src/main/resources");
propFile = new File(resourcesDir, fileName);
if (propFile.exists() && propFile.isFile()) {
inputStream = new FileInputStream(propFile);
}
}
}
}
}
if (inputStream != null) {
properties.load(new InputStreamReader(inputStream, encoding));
} else if (!fileName.contains("-")) {
}
} catch (Exception e) {
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
private InputStream getResourceAsStreamByCurrentThread(String fileName) {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
return ret != null ? ret.getResourceAsStream(fileName) : null;
}
private InputStream getResourceAsStreamByClassloader(String fileName) {
ClassLoader ret = MyBatisFlexProps.class.getClassLoader();
return ret != null ? ret.getResourceAsStream(fileName) : null;
}
private static String removeSlashEnd(String path) {
if (path != null && (path.endsWith("/") || path.endsWith("\\"))) {
return path.substring(0, path.length() - 1);
} else {
return path;
}
}
public Properties getProperties() {
return properties;
}
}

View File

@ -0,0 +1,360 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.processer;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Table;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import java.io.*;
import java.util.*;
import java.util.function.Consumer;
public class QueryEntityProcesser extends AbstractProcessor {
private static final String classTableTemplate = "package @package;\n" +
"\n" +
"import com.mybatisflex.core.querywrapper.QueryColumn;\n" +
"import com.mybatisflex.core.table.TableDef;\n" +
"\n" +
"// Auto generate by mybatis-flex, do not modify it.\n" +
"public class Tables {\n" +
"@classesInfo" +
"}\n";
private static final String tableDefTemplate = "\n\n public static final @entityClassTableDef @tableField = new @entityClassTableDef(\"@tableName\");\n";
private static final String classTemplate = "\n" +
" public static class @entityClassTableDef extends TableDef {\n" +
"\n" +
"@queryColumns" +
"@allColumns" +
"\n" +
" public @entityClassTableDef(String tableName) {\n" +
" super(tableName);\n" +
" }\n" +
" }\n";
private static final String columnsTemplate = " public QueryColumn @property = new QueryColumn(this, \"@columnName\");\n";
private static final String allColumnsTemplate = "\n public QueryColumn[] ALL_COLUMNS = new QueryColumn[]{@allColumns};\n\n";
protected Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
this.filer = processingEnvironment.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!roundEnv.processingOver()) {
MyBatisFlexProps props = new MyBatisFlexProps("mybatis-flex.properties");
String enable = props.getProperties().getProperty("processer.enable", "");
if ("false".equalsIgnoreCase(enable)) {
return true;
}
String genPath = props.getProperties().getProperty("processer.genPath", "");
final String genPackage = props.getProperties().getProperty("processer.package");
String className = props.getProperties().getProperty("processer.className", "Tables");
StringBuilder guessPackage = new StringBuilder();
StringBuilder tablesContent = new StringBuilder();
roundEnv.getElementsAnnotatedWith(Table.class).forEach((Consumer<Element>) entityClassElement -> {
Table table = entityClassElement.getAnnotation(Table.class);
//init genPackage
if ((genPackage == null || genPackage.trim().length() == 0)
&& guessPackage.length() == 0) {
String entityClassName = entityClassElement.toString();
if (!entityClassName.contains(".")) {
guessPackage.append("table");// = "table";
} else {
guessPackage.append(entityClassName.substring(0, entityClassName.lastIndexOf(".")) + ".table");
}
}
String tableName = table != null && table.value().trim().length() != 0
? table.value()
: firstCharToLowerCase(entityClassElement.getSimpleName().toString());
Map<String, String> propertyAndColumns = new LinkedHashMap<>();
TypeElement classElement = (TypeElement) entityClassElement;
for (Element fieldElement : classElement.getEnclosedElements()) {
//all fields
if (ElementKind.FIELD == fieldElement.getKind()) {
Column column = fieldElement.getAnnotation(Column.class);
if (column != null && column.ignore()) {
continue;
}
String columnName = column != null && column.value().trim().length() > 0 ? column.value() : camelToUnderline(fieldElement.toString());
propertyAndColumns.put(fieldElement.toString(), columnName);
}
}
String entityClassName = entityClassElement.getSimpleName().toString();
tablesContent.append(buildClass(entityClassName, tableName, propertyAndColumns));
});
if (tablesContent.length() > 0) {
String realGenPackage = genPackage == null || genPackage.trim().length() == 0 ? guessPackage.toString() : genPackage;
genClass(genPath, realGenPackage, className, tablesContent.toString());
}
}
return false;
}
private String buildClass(String entityClass, String tableName, Map<String, String> propertyAndColumns) {
// tableDefTemplate = " public static final @entityClassTableDef @tableField = new @entityClassTableDef(\"@tableName\");\n";
String tableDef = tableDefTemplate.replace("@entityClass", entityClass)
.replace("@tableField", entityClass.toUpperCase())
.replace("@tableName", tableName);
//columnsTemplate = " public QueryColumn @property = new QueryColumn(this, \"@columnName\");\n";
StringBuilder queryColumns = new StringBuilder();
propertyAndColumns.forEach((property, column) ->
queryColumns.append(columnsTemplate
.replace("@property", camelToUnderline(property).toUpperCase())
.replace("@columnName", column)
));
// public QueryColumn[] ALL_COLUMNS = new QueryColumn[]{@allColumns};
StringJoiner allColumns = new StringJoiner(", ");
propertyAndColumns.forEach((property, column) -> allColumns.add(camelToUnderline(property).toUpperCase()));
String allColumnsString = allColumnsTemplate.replace("@allColumns", allColumns.toString());
// classTemplate = "\n" +
// " public static class @entityClassTableDef extends TableDef {\n" +
// "\n" +
// "@queryColumns" +
// "@allColumns" +
// "\n" +
// " public @entityClassTableDef(String tableName) {\n" +
// " super(tableName);\n" +
// " }\n" +
// " }\n";
String tableClass = classTemplate.replace("@entityClass", entityClass)
.replace("@queryColumns", queryColumns)
.replace("@allColumns", allColumnsString);
return tableDef + tableClass;
}
private void genClass(String genBasePath, String genPackageName, String className, String classContent) {
String genContent = classTableTemplate.replace("@package", genPackageName)
.replace("@classesInfo", classContent);
Writer writer = null;
try {
JavaFileObject sourceFile = filer.createSourceFile(genPackageName + "." + className);
if (genBasePath == null || genBasePath.trim().length() == 0) {
writer = sourceFile.openWriter();
writer.write(genContent);
writer.flush();
printMessage(">>>>> mybatis-flex success generate tables class: \n" + sourceFile.toUri());
return;
}
String defaultGenPath = sourceFile.toUri().getPath();
//真实的生成代码的目录
String realPath;
//用户配置的路径为绝对路径
if (isAbsolutePath(genBasePath)) {
realPath = genBasePath;
}
//配置的是相对路径那么则以项目根目录为相对路径
else {
String projectRootPath = getProjectRootPath(defaultGenPath);
realPath = new File(projectRootPath, genBasePath).getAbsolutePath();
}
//通过在 test/java 目录下执行编译生成的
boolean fromTestSource = isFromTestSource(defaultGenPath);
if (fromTestSource) {
realPath = new File(realPath, "src/test/java").getAbsolutePath();
} else {
realPath = new File(realPath, "src/main/java").getAbsolutePath();
}
File genJavaFile = new File(realPath, (genPackageName + "." + className).replace(".", "/") + ".java");
if (!genJavaFile.getParentFile().exists() && !genJavaFile.getParentFile().mkdirs()) {
System.out.println(">>>>>ERROR: can not mkdirs by mybatis-flex processer for: " + genJavaFile.getParentFile());
return;
}
writer = new PrintWriter(new FileOutputStream(genJavaFile));
writer.write(classContent);
writer.flush();
printMessage(">>>>> mybatis-flex success generate tables class: \n" + genJavaFile.toURI());
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
}
}
}
}
private void printMessage(String message) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, message);
System.out.println(message);
}
public static String firstCharToLowerCase(String str) {
char firstChar = str.charAt(0);
if (firstChar >= 'A' && firstChar <= 'Z') {
char[] arr = str.toCharArray();
arr[0] += ('a' - 'A');
return new String(arr);
}
return str;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add(Table.class.getCanonicalName());
return supportedAnnotationTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
private boolean isFromTestSource(String path) {
return path.contains("test-sources") || path.contains("test-annotations");
}
public static boolean isAbsolutePath(String path) {
return path != null && (path.startsWith("/") || path.indexOf(":") > 0);
}
public static String camelToUnderline(String string) {
if (string == null || string.trim().length() == 0) {
return "";
}
int len = string.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = string.charAt(i);
if (Character.isUpperCase(c) && i > 0) {
sb.append('_');
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 获取项目的根目录也就是根节点 pom.xml 所在的目录
*
* @return
*/
private String getProjectRootPath(String genFilePath) {
File file = new File(genFilePath);
int count = 20;
return getProjectRootPath(file, count);
}
private String getProjectRootPath(File file, int count) {
if (count <= 0) {
return null;
}
if (file.isFile()) {
return getProjectRootPath(file.getParentFile(), --count);
} else {
if (new File(file, "pom.xml").exists() && !new File(file.getParentFile(), "pom.xml").exists()) {
return file.getAbsolutePath();
} else {
return getProjectRootPath(file.getParentFile(), --count);
}
}
}
/**
* 当前 Maven 模块所在所在的目录
*
* @return
*/
private String getModuleRootPath(String genFilePath) {
File file = new File(genFilePath);
int count = 20;
return getModuleRootPath(file, count);
}
private String getModuleRootPath(File file, int count) {
if (count <= 0) {
return null;
}
if (file.isFile()) {
return getModuleRootPath(file.getParentFile(), --count);
} else {
if (new File(file, "pom.xml").exists()) {
return file.getAbsolutePath();
} else {
return getModuleRootPath(file.getParentFile(), --count);
}
}
}
}

View File

@ -0,0 +1 @@
com.mybatisflex.processer.QueryEntityProcesser

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>com.mybatis-flex</groupId>
<version>1.0.0-beta.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-flex-codegen</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>

53
mybatis-flex-core/pom.xml Normal file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>com.mybatis-flex</groupId>
<version>1.0.0-beta.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-flex-core</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-annotation</artifactId>
<version>1.0.0-beta.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,284 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.provider.EntitySqlProvider;
import com.mybatisflex.core.querywrapper.QueryColumn;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.querywrapper.CPI;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface BaseMapper<T> {
/**
* 插入 entity 数据
*
* @param entity
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#insert(Map, ProviderContext)
*/
@InsertProvider(type = EntitySqlProvider.class, method = "insert")
int insert(@Param(FlexConsts.ENTITY) T entity);
/**
* 批量插入 entity 数据只会根据第一条数据来构建插入的字段内容
*
* @param entities 插入的数据列表
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#insertBatchWithFirstEntityColumns(Map, ProviderContext)
*/
@InsertProvider(type = EntitySqlProvider.class, method = "insertBatchWithFirstEntityColumns")
int insertBatchWithFirstEntityColumns(@Param(FlexConsts.ENTITIES) List<T> entities);
/**
* 根据 id 删除数据
* 如果是多个主键的情况下需要传入数组 new Object[]{100,101}
*
* @param id 主键数据
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#deleteById(Map, ProviderContext)
*/
@DeleteProvider(type = EntitySqlProvider.class, method = "deleteById")
int deleteById(@Param(FlexConsts.PRIMARY_VALUE) Serializable id);
/**
* 根据多个 id 批量删除数据
*
* @param ids ids 列表
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#deleteBatchByIds(Map, ProviderContext)
*/
@DeleteProvider(type = EntitySqlProvider.class, method = "deleteBatchByIds")
int deleteBatchByIds(@Param(FlexConsts.PRIMARY_VALUE) Collection<? extends Serializable> ids);
/**
* 根据 map 构建的条件来删除数据
*
* @param whereConditions
* @return 返回影响的行数
*/
default int deleteByMap(Map<String, Object> whereConditions) {
return deleteByQuery(QueryWrapper.create().where(whereConditions));
}
/**
* 根据 query 构建的条件来数据吗
*
* @param queryWrapper query 条件
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#deleteByQuery(Map, ProviderContext)
*/
@DeleteProvider(type = EntitySqlProvider.class, method = "deleteByQuery")
int deleteByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 根据主键来更新数据若数据为 null也会更新到数据库
*
* @param entity 数据内容必须包含有主键
* @return 返回影响的行数
*/
default int update(T entity) {
return update(entity, true);
}
/**
* 根据主键来更新数据到数据库
*
* @param entity 数据内容
* @param ignoreNulls 是否忽略空内容字段
* @return 返回影响的行数
* @see com.mybatisflex.core.provider.EntitySqlProvider#update(Map, ProviderContext)
*/
@UpdateProvider(type = EntitySqlProvider.class, method = "update")
int update(@Param(FlexConsts.ENTITY) T entity, @Param(FlexConsts.IGNORE_NULLS) boolean ignoreNulls);
/**
* 根据 map 构建的条件来更新数据
*
* @param entity 数据
* @param map where 条件内容
* @return 返回影响的行数
*/
default int updateByMap(T entity, Map<String, Object> map) {
return updateByQuery(entity, QueryWrapper.create().where(map));
}
/**
* 根据 query 构建的条件来更新数据
*
* @param entity 数据内容
* @param queryWrapper query 条件
* @return 返回影响的行数
*/
default int updateByQuery(@Param(FlexConsts.ENTITY) T entity, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper) {
return updateByQuery(entity, true, queryWrapper);
}
/**
* 根据 query 构建的条件来更新数据
*
* @param entity 数据内容
* @param ignoreNulls 是否忽略空值
* @param queryWrapper query 条件
* @return
* @see com.mybatisflex.core.provider.EntitySqlProvider#updateByQuery(Map, ProviderContext)
*/
@UpdateProvider(type = EntitySqlProvider.class, method = "updateByQuery")
int updateByQuery(@Param(FlexConsts.ENTITY) T entity, @Param("ignoreNulls") boolean ignoreNulls, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 根据主键来选择数据
*
* @param id 多个主键
* @return entity
* @see com.mybatisflex.core.provider.EntitySqlProvider#selectOneById(Map, ProviderContext)
*/
@SelectProvider(type = EntitySqlProvider.class, method = "selectOneById")
T selectOneById(@Param(FlexConsts.PRIMARY_VALUE) Serializable id);
/**
* 根据 map 构建的条件来查询数据
*
* @param whereConditions where 条件
* @return entity 数据
*/
default T selectOneByMap(Map<String, Object> whereConditions) {
return selectOneByQuery(QueryWrapper.create().where(whereConditions));
}
/**
* 根据 queryWrapper 构建的条件来查询 1 条数据
*
* @param queryWrapper query 条件
* @return entity 数据
*/
default T selectOneByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper) {
List<T> entities = selectListByQuery(queryWrapper.limit(1));
if (entities == null || entities.isEmpty()) {
return null;
} else {
return entities.get(0);
}
}
/**
* 根据多个主键来查询多条数据
*
* @param ids 主键列表
* @return 数据列表
* @see com.mybatisflex.core.provider.EntitySqlProvider#selectListByIds(Map, ProviderContext)
*/
@SelectProvider(type = EntitySqlProvider.class, method = "selectListByIds")
List<T> selectListByIds(@Param(FlexConsts.PRIMARY_VALUE) Collection<? extends Serializable> ids);
/**
* 根据 map 来构建查询条件查询多条数据
*
* @param whereConditions 条件列表
* @return 数据列表
*/
default List<T> selectListByMap(Map<String, Object> whereConditions) {
return selectListByQuery(QueryWrapper.create().where(whereConditions));
}
/**
* 根据 query 来构建条件查询数据列表
*
* @param queryWrapper 查询条件
* @return 数据列表
* @see com.mybatisflex.core.provider.EntitySqlProvider#selectListByQuery(Map, ProviderContext)
*/
@SelectProvider(type = EntitySqlProvider.class, method = "selectListByQuery")
List<T> selectListByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 根据 queryWrapper 来查询数据量
*
* @param queryWrapper
* @return 数据量
* @see EntitySqlProvider#selectCountByQuery(Map, ProviderContext)
*/
@SelectProvider(type = EntitySqlProvider.class, method = "selectCountByQuery")
long selectCountByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 分页查询
*
* @param pageNumber 当前页码
* @param pageSize 每页的数据量
* @param queryWrapper 查询条件
* @return 返回 Page 数据
*/
default Page<T> paginate(int pageNumber, int pageSize, QueryWrapper queryWrapper) {
Page<T> page = new Page<>(pageNumber, pageSize);
return paginate(page, queryWrapper);
}
/**
* 分页查询
*
* @param page page其包含了页码每页的数据量可能包含数据总量
* @param queryWrapper 查询条件
* @return page 数据
*/
default Page<T> paginate(@Param("page") Page<T> page, @Param("query") QueryWrapper queryWrapper) {
List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
// 只有 totalRow 小于 0 的时候才会去查询总量
// 这样方便用户做总数缓存而非每次都要去查询总量
// 一般的分页场景中只有第一页的时候有必要去查询总量第二页以后是不需要的
if (page.getTotalRow() < 0) {
//清除group by 去查询数据
CPI.setGroupByColumns(queryWrapper, null);
long count = selectCountByQuery(queryWrapper);
page.setTotalRow(count);
}
if (page.getTotalRow() == 0 || page.getPageNumber() > page.getTotalPage()) {
return page;
}
//恢复数量查询清除的 groupBy
CPI.setGroupByColumns(queryWrapper, groupByColumns);
int offset = page.getPageSize() * (page.getPageNumber() - 1);
queryWrapper.limit(offset, page.getPageSize());
List<T> rows = selectListByQuery(queryWrapper);
page.setList(rows);
return page;
}
}

View File

@ -0,0 +1,39 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core;
/**
* Mybatis-Flex 可能用到的静态常量
*/
public class FlexConsts {
public static final String VERSION = "1.0.0-beta.1";
public static final String DEFAULT_PRIMARY_FIELD = "id";
public static final String SQL = "$$sql";
public static final String SQL_ARGS = "$$sql_args";
public static final String TABLE_NAME = "$$tableName";
public static final String PRIMARY_KEY = "$$primaryKey";
public static final String PRIMARY_VALUE = "$$primaryValue";
public static final String QUERY = "$$query";
public static final String ROW = "$$row";
public static final String ROWS = "$$rows";
public static final String ENTITY = "$$entity";
public static final String ENTITIES = "$$entities";
public static final String IGNORE_NULLS = "$$ignoreNulls";
}

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core;
import com.mybatisflex.core.dialect.DbType;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.concurrent.ConcurrentHashMap;
/**
* 全局配置文件
*/
public class FlexGlobalConfig {
/**
* 默认使用 Mysql 数据库类型
*/
private DbType dbType = DbType.MYSQL;
/**
* 创建好的 sqlSessionFactory
*/
private SqlSessionFactory sqlSessionFactory;
public DbType getDbType() {
return dbType;
}
public void setDbType(DbType dbType) {
this.dbType = dbType;
}
public SqlSessionFactory getSqlSessionFactory() {
return sqlSessionFactory;
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
/////static factory methods/////
private static ConcurrentHashMap<String, FlexGlobalConfig> globalConfigs = new ConcurrentHashMap();
private static FlexGlobalConfig defaultConfig;
public static FlexGlobalConfig getDefaultConfig() {
return defaultConfig;
}
public static void setDefaultConfig(FlexGlobalConfig config) {
defaultConfig = config;
}
public static FlexGlobalConfig getConfig(String environmentId) {
return globalConfigs.get(environmentId);
}
public static void setConfig(String id, FlexGlobalConfig config) {
if (getDefaultConfig() == null) {
setDefaultConfig(config);
}
globalConfigs.put(id, config);
}
}

View File

@ -0,0 +1,160 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
/**
* MybatisFlex 的启动类
*
* <code>
* MybatisFlexBootstrap.getInstance()
* .setDatasource(...)
* .addMapper(...)
* .start();
* <p>
* <p>
* MybatisFlexBootstrap.getInstance()
* .execute(...)
* </code>
*/
public class MybatisFlexBootstrap {
private final AtomicBoolean started = new AtomicBoolean(false);
private String environmentId = "mybatis-flex";
private TransactionFactory transactionFactory;
private DataSource dataSource;
private Configuration configuration;
private List<Class<?>> mappers;
private DbType dbType;
private SqlSessionFactory sqlSessionFactory;
private static volatile MybatisFlexBootstrap instance;
public static MybatisFlexBootstrap getInstance() {
if (instance == null) {
synchronized (MybatisFlexBootstrap.class) {
if (instance == null) {
instance = new MybatisFlexBootstrap();
}
}
}
return instance;
}
public MybatisFlexBootstrap setDatasource(DataSource datasource) {
this.dataSource = datasource;
return this;
}
public MybatisFlexBootstrap setConfiguration(Configuration configuration) {
this.configuration = configuration;
return this;
}
public MybatisFlexBootstrap setTransactionFactory(TransactionFactory transactionFactory) {
this.transactionFactory = transactionFactory;
return this;
}
public MybatisFlexBootstrap setEnvironmentId(String environmentId) {
this.environmentId = environmentId;
return this;
}
public <T> MybatisFlexBootstrap addMapper(Class<T> type) {
if (this.mappers == null) {
mappers = new ArrayList<>();
}
mappers.add(type);
return this;
}
public MybatisFlexBootstrap start() {
if (started.compareAndSet(false, true)) {
if (dataSource == null) {
throw new IllegalStateException("dataSource can not be null.");
}
//init configuration
if (configuration == null) {
if (transactionFactory == null) {
transactionFactory = new JdbcTransactionFactory();
}
Environment environment = new Environment(environmentId, transactionFactory, dataSource);
configuration = new FlexConfiguration(environment);
}
//init mappers
if (mappers != null) {
mappers.forEach(configuration::addMapper);
}
//init sqlSessionFactory
this.sqlSessionFactory = new FlexSqlSessionFactoryBuilder().build(configuration);
//init dbType
this.dbType = FlexGlobalConfig.getConfig(environmentId).getDbType();
LogFactory.getLog(MybatisFlexBootstrap.class).debug("Mybatis-Flex has started.");
}
return this;
}
public <R, T> R execute(Class<T> mapperClass, Function<T, R> function) {
try (SqlSession sqlSession = openSession()) {
DialectFactory.setHintDbType(dbType);
T mapper = sqlSession.getMapper(mapperClass);
return function.apply(mapper);
} finally {
DialectFactory.clearHintDbType();
}
}
private SqlSession openSession() {
return sqlSessionFactory.openSession(configuration.getDefaultExecutorType());
}
}

View File

@ -0,0 +1,576 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
import com.mybatisflex.core.querywrapper.*;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 通用的方言设计其他方言可以继承于当前 CommonsDialectImpl
* 创建或获取方言请参考 {@link com.mybatisflex.core.dialect.DialectFactory}
*/
public class CommonsDialectImpl implements IDialect {
protected KeywordWrap keywordWrap = KeywordWrap.NONE;
private LimitOffsetProcesser limitOffsetProcesser = LimitOffsetProcesser.MYSQL;
public CommonsDialectImpl() {
}
public CommonsDialectImpl(LimitOffsetProcesser limitOffsetProcesser) {
this.limitOffsetProcesser = limitOffsetProcesser;
}
public CommonsDialectImpl(KeywordWrap keywordWrap, LimitOffsetProcesser limitOffsetProcesser) {
this.keywordWrap = keywordWrap;
this.limitOffsetProcesser = limitOffsetProcesser;
}
@Override
public String wrap(String keyword) {
return keywordWrap.wrap(keyword);
}
@Override
public String forInsertRow(String tableName, Row row) {
StringBuilder fields = new StringBuilder();
StringBuilder questions = new StringBuilder();
Set<String> attrs = row.obtainModifyAttrs();
int index = 0;
for (String attr : attrs) {
fields.append(wrap(attr));
questions.append("?");
if (index != attrs.size() - 1) {
fields.append(", ");
questions.append(", ");
}
index++;
}
String sql = "INSERT INTO " + wrap(tableName) +
"(" + fields + ") VALUES " +
"(" + questions + ")";
return sql;
}
@Override
public String forInsertBatchWithFirstRowColumns(String tableName, List<Row> rows) {
StringBuilder fields = new StringBuilder();
StringBuilder questions = new StringBuilder();
Row firstRow = rows.get(0);
Set<String> attrs = firstRow.obtainModifyAttrs();
int index = 0;
for (String attr : attrs) {
fields.append(wrap(attr));
if (index != attrs.size() - 1) {
fields.append(", ");
}
index++;
}
for (int i = 0; i < rows.size(); i++) {
questions.append(buildQuestion(attrs.size(), true));
if (i != rows.size() - 1) {
questions.append(",");
}
}
String sql = "INSERT INTO " + wrap(tableName) +
"(" + fields + ") VALUES " + questions;
return sql;
}
@Override
public String forDeleteById(String tableName, String[] primaryKeys) {
StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ");
sql.append(wrap(tableName));
sql.append(" WHERE ");
for (int i = 0; i < primaryKeys.length; i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append(wrap(primaryKeys[i])).append(" = ?");
}
return sql.toString();
}
@Override
public String forDeleteBatchByIds(String tableName, String[] primaryKeys, Object[] ids) {
StringBuilder sql = new StringBuilder();
sql.append("DELETE FROM ");
sql.append(wrap(tableName));
sql.append(" WHERE ");
//多主键的场景
if (primaryKeys.length > 1) {
for (int i = 0; i < ids.length / primaryKeys.length; i++) {
if (i > 0) {
sql.append(" OR ");
}
sql.append("(");
for (int j = 0; j < primaryKeys.length; j++) {
if (j > 0) {
sql.append(" AND ");
}
sql.append(wrap(primaryKeys[j])).append(" = ?");
}
sql.append(")");
}
}
// 单主键
else {
for (int i = 0; i < ids.length; i++) {
if (i > 0) {
sql.append(" OR ");
}
sql.append(wrap(primaryKeys[0])).append(" = ?");
}
}
return sql.toString();
}
@Override
public String forDeleteByQuery(QueryWrapper queryWrapper) {
return buildDeleteSql(queryWrapper);
}
@Override
public String forUpdateById(String tableName, Row row) {
StringBuilder sql = new StringBuilder();
Set<String> modifyAttrs = row.obtainModifyAttrs();
String[] primaryKeys = row.obtainsPrimaryKeyStrings();
sql.append("UPDATE ").append(wrap(tableName)).append(" SET ");
int index = 0;
for (Map.Entry<String, Object> e : row.entrySet()) {
String colName = e.getKey();
if (modifyAttrs.contains(colName) && !ArrayUtil.contains(primaryKeys, colName)) {
if (index > 0) {
sql.append(", ");
}
sql.append(wrap(colName)).append(" = ? ");
index++;
}
}
sql.append(" WHERE ");
for (int i = 0; i < primaryKeys.length; i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append(wrap(primaryKeys[i])).append(" = ?");
}
return sql.toString();
}
@Override
public String forUpdateByQuery(String tableName, Row row, QueryWrapper queryWrapper) {
StringBuilder sql = new StringBuilder();
Set<String> modifyAttrs = row.obtainModifyAttrs();
sql.append("UPDATE ").append(wrap(tableName)).append(" SET ");
int index = 0;
for (String modifyAttr : modifyAttrs) {
if (index > 0) {
sql.append(", ");
}
sql.append(wrap(modifyAttr)).append(" = ? ");
index++;
}
String whereConditionSql = buildWhereConditionSql(queryWrapper);
if (StringUtil.isNotBlank(whereConditionSql)) {
sql.append(" WHERE ").append(whereConditionSql);
}
return sql.toString();
}
@Override
public String forUpdateBatchById(String tableName, List<Row> rows) {
if (rows.size() == 1) {
return forUpdateById(tableName, rows.get(0));
}
StringBuilder sql = new StringBuilder();
for (Row row : rows) {
sql.append(forUpdateById(tableName, row)).append("; ");
}
return sql.toString();
}
@Override
public String forSelectOneById(String tableName, String[] primaryKeys, Object[] primaryValues) {
StringBuilder sql = new StringBuilder("SELECT * FROM ");
sql.append(wrap(tableName)).append(" WHERE ");
for (int i = 0; i < primaryKeys.length; i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append('`').append(primaryKeys[i]).append("` = ?");
}
return sql.toString();
}
@Override
public String forSelectListByQuery(QueryWrapper queryWrapper) {
return buildSelectSql(queryWrapper);
}
@Override
public String forSelectCountByQuery(QueryWrapper queryWrapper) {
return buildSelectCountSql(queryWrapper);
}
////////////build query sql///////
@Override
public String buildSelectSql(QueryWrapper queryWrapper) {
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
StringBuilder sqlBuilder = new StringBuilder("SELECT ");
if (selectColumns == null || selectColumns.isEmpty()) {
sqlBuilder.append("*");
} else {
int index = 0;
for (QueryColumn selectColumn : selectColumns) {
String selectColumnSql = CPI.toSelectSql(selectColumn, allTables, this);
sqlBuilder.append(selectColumnSql);
if (index != selectColumns.size() - 1) {
sqlBuilder.append(", ");
}
index++;
}
}
sqlBuilder.append(" FROM ").append(StringUtil.join(",", queryTables, queryTable -> queryTable.toSql(this)));
buildJoinSql(sqlBuilder, queryWrapper, allTables);
buildWhereSql(sqlBuilder, queryWrapper, allTables);
buildGroupBySql(sqlBuilder, queryWrapper, allTables);
buildHavingSql(sqlBuilder, queryWrapper, allTables);
buildOrderBySql(sqlBuilder, queryWrapper, allTables);
Integer limitRows = CPI.getLimitRows(queryWrapper);
Integer limitOffset = CPI.getLimitOffset(queryWrapper);
if (limitRows != null || limitOffset != null) {
sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
}
return sqlBuilder.toString();
}
@Override
public String buildSelectCountSql(QueryWrapper queryWrapper) {
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
//ignore selectColumns
StringBuilder sqlBuilder = new StringBuilder("SELECT COUNT(*) FROM ");
sqlBuilder.append(StringUtil.join(",", queryTables, queryTable -> queryTable.toSql(this)));
buildJoinSql(sqlBuilder, queryWrapper, allTables);
buildWhereSql(sqlBuilder, queryWrapper, allTables);
buildGroupBySql(sqlBuilder, queryWrapper, allTables);
buildHavingSql(sqlBuilder, queryWrapper, allTables);
// ignore orderBy and limit
// buildOrderBySql(sqlBuilder, queryWrapper);
// buildLimitSql(sqlBuilder, queryWrapper);
return sqlBuilder.toString();
}
@Override
public String buildDeleteSql(QueryWrapper queryWrapper) {
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
//ignore selectColumns
StringBuilder sqlBuilder = new StringBuilder("DELETE FROM ");
sqlBuilder.append(StringUtil.join(",", queryTables, queryTable -> queryTable.toSql(this)));
buildJoinSql(sqlBuilder, queryWrapper, allTables);
buildWhereSql(sqlBuilder, queryWrapper, allTables);
buildGroupBySql(sqlBuilder, queryWrapper, allTables);
buildHavingSql(sqlBuilder, queryWrapper, allTables);
//ignore orderBy and limit
//buildOrderBySql(sqlBuilder, queryWrapper);
//buildLimitSql(sqlBuilder, queryWrapper);
return sqlBuilder.toString();
}
@Override
public String buildWhereConditionSql(QueryWrapper queryWrapper) {
QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(queryWrapper);
return whereQueryCondition != null ? whereQueryCondition.toSql(CPI.getQueryTables(queryWrapper), this) : "";
}
@Override
public String forInsertEntity(TableInfo tableInfo, Object entity) {
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ").append(wrap(tableInfo.getTableName()));
String[] insertColumns = tableInfo.obtainInsertColumns();
sql.append("(").append(StringUtil.join(",", insertColumns)).append(")");
sql.append(" VALUES ");
sql.append(buildQuestion(insertColumns.length, true));
return sql.toString();
}
@Override
public String forInsertBatchWithFirstEntityColumns(TableInfo tableInfo, List<Object> entities) {
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO ").append(wrap(tableInfo.getTableName()));
String[] insertColumns = tableInfo.obtainInsertColumns();
sql.append("(").append(StringUtil.join(",", insertColumns)).append(")");
sql.append(" VALUES ");
for (int i = 0; i < entities.size(); i++) {
sql.append(buildQuestion(insertColumns.length, true));
if (i != entities.size() - 1) {
sql.append(", ");
}
}
return sql.toString();
}
@Override
public String forDeleteEntityById(TableInfo tableInfo) {
return forDeleteById(tableInfo.getTableName(), tableInfo.getPrimaryKeys());
}
@Override
public String forDeleteEntityBatchById(TableInfo tableInfo, Object[] primaryValues) {
return forDeleteBatchByIds(tableInfo.getTableName(), tableInfo.getPrimaryKeys(), primaryValues);
}
@Override
public String forUpdateEntity(TableInfo tableInfo, Object entity, boolean ignoreNulls) {
StringBuilder sql = new StringBuilder();
Set<String> modifyAttrs = tableInfo.obtainUpdateColumns(entity, ignoreNulls, false);
String[] primaryKeys = tableInfo.getPrimaryKeys();
sql.append("UPDATE ").append(wrap(tableInfo.getTableName())).append(" SET ");
int index = 0;
for (String modifyAttr : modifyAttrs) {
if (index > 0) {
sql.append(", ");
}
sql.append(wrap(modifyAttr)).append(" = ? ");
index++;
}
sql.append(" WHERE ");
for (int i = 0; i < primaryKeys.length; i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append(wrap(primaryKeys[i])).append(" = ?");
}
return sql.toString();
}
@Override
public String forUpdateEntityByQuery(TableInfo tableInfo, Object entity, boolean ignoreNulls, QueryWrapper queryWrapper) {
StringBuilder sql = new StringBuilder();
Set<String> modifyAttrs = tableInfo.obtainUpdateColumns(entity, ignoreNulls, true);
sql.append("UPDATE ").append(wrap(tableInfo.getTableName())).append(" SET ");
int index = 0;
for (String modifyAttr : modifyAttrs) {
if (index > 0) {
sql.append(", ");
}
sql.append(wrap(modifyAttr)).append(" = ? ");
index++;
}
sql.append(" WHERE ");
sql.append(buildWhereConditionSql(queryWrapper));
return sql.toString();
}
@Override
public String forSelectOneEntityById(TableInfo tableInfo) {
StringBuilder sql = new StringBuilder("SELECT * FROM ");
sql.append(wrap(tableInfo.getTableName()));
sql.append(" WHERE ");
String[] pKeys = tableInfo.getPrimaryKeys();
for (int i = 0; i < pKeys.length; i++) {
if (i > 0) {
sql.append(" AND ");
}
sql.append(wrap(pKeys[i])).append(" = ?");
}
return sql.toString();
}
@Override
public String forSelectEntityListByIds(TableInfo tableInfo, Object[] primaryValues) {
StringBuilder sql = new StringBuilder("SELECT * FROM ");
sql.append(wrap(tableInfo.getTableName()));
sql.append(" WHERE ");
String[] primaryKeys = tableInfo.getPrimaryKeys();
//多主键的场景
if (primaryKeys.length > 1) {
for (int i = 0; i < primaryValues.length / primaryKeys.length; i++) {
if (i > 0) {
sql.append(" OR ");
}
sql.append("(");
for (int j = 0; j < primaryKeys.length; j++) {
if (j > 0) {
sql.append(" AND ");
}
sql.append(wrap(primaryKeys[j])).append(" = ?");
}
sql.append(")");
}
}
// 单主键
else {
for (int i = 0; i < primaryValues.length; i++) {
if (i > 0) {
sql.append(" OR ");
}
sql.append(wrap(primaryKeys[0])).append(" = ?");
}
}
return sql.toString();
}
protected void buildJoinSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
List<Join> joins = CPI.getJoins(queryWrapper);
if (joins != null && !joins.isEmpty()) {
for (Join join : joins) {
if (!join.checkEffective()) {
continue;
}
sqlBuilder.append(join.toSql(queryTables, this));
}
}
}
protected void buildWhereSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
QueryCondition whereQueryCondition = CPI.getWhereQueryCondition(queryWrapper);
if (whereQueryCondition != null) {
String whereSql = whereQueryCondition.toSql(queryTables, this);
if (StringUtil.isNotBlank(whereSql)) {
sqlBuilder.append(" WHERE ").append(whereSql);
}
}
}
protected void buildGroupBySql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
if (groupByColumns != null && !groupByColumns.isEmpty()) {
sqlBuilder.append(" GROUP BY ");
int index = 0;
for (QueryColumn groupByColumn : groupByColumns) {
String groupBy = CPI.toConditionSql(groupByColumn, queryTables, this);
sqlBuilder.append(groupBy);
if (index != groupByColumns.size() - 1) {
sqlBuilder.append(", ");
}
index++;
}
}
}
protected void buildHavingSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
QueryCondition havingQueryCondition = CPI.getHavingQueryCondition(queryWrapper);
if (havingQueryCondition != null) {
String havingSql = havingQueryCondition.toSql(queryTables, this);
if (StringUtil.isNotBlank(havingSql)) {
sqlBuilder.append(" HAVING ").append(havingSql);
}
}
}
protected void buildOrderBySql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, List<QueryTable> queryTables) {
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
if (orderBys != null && !orderBys.isEmpty()) {
sqlBuilder.append(" ORDER BY ");
int index = 0;
for (QueryOrderBy orderBy : orderBys) {
sqlBuilder.append(orderBy.toSql(queryTables, this));
if (index != orderBys.size() - 1) {
sqlBuilder.append(", ");
}
index++;
}
}
}
/**
* 构建 limit offset 的参数
*/
protected StringBuilder buildLimitOffsetSql(StringBuilder sqlBuilder, QueryWrapper queryWrapper, Integer limitRows, Integer limitOffset) {
return limitOffsetProcesser.process(sqlBuilder, queryWrapper, limitRows, limitOffset);
}
protected String buildQuestion(int count, boolean withBrackets) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) {
sb.append("?");
if (i != count - 1) {
sb.append(", ");
}
}
return withBrackets ? "(" + sb + ")" : sb.toString();
}
}

View File

@ -0,0 +1,196 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
public enum DbType {
/**
* MYSQL
*/
MYSQL("mysql", "MySql数据库"),
/**
* MARIADB
*/
MARIADB("mariadb", "MariaDB数据库"),
/**
* ORACLE
*/
ORACLE("oracle", "Oracle11g及以下数据库"),
/**
* oracle12c
*/
ORACLE_12C("oracle12c", "Oracle12c及以上数据库"),
/**
* DB2
*/
DB2("db2", "DB2数据库"),
/**
* H2
*/
H2("h2", "H2数据库"),
/**
* HSQL
*/
HSQL("hsql", "HSQL数据库"),
/**
* SQLITE
*/
SQLITE("sqlite", "SQLite数据库"),
/**
* POSTGRE
*/
POSTGRE_SQL("postgresql", "Postgre数据库"),
/**
* SQLSERVER2005
*/
SQL_SERVER2005("sqlserver2005", "SQLServer2005数据库"),
/**
* SQLSERVER
*/
SQL_SERVER("sqlserver", "SQLServer数据库"),
/**
* DM
*/
DM("dm", "达梦数据库"),
/**
* xugu
*/
XU_GU("xugu", "虚谷数据库"),
/**
* Kingbase
*/
KINGBASE_ES("kingbasees", "人大金仓数据库"),
/**
* Phoenix
*/
PHOENIX("phoenix", "Phoenix HBase数据库"),
/**
* Gauss
*/
GAUSS("gauss", "Gauss 数据库"),
/**
* ClickHouse
*/
CLICK_HOUSE("clickhouse", "clickhouse 数据库"),
/**
* GBase
*/
GBASE("gbase", "南大通用(华库)数据库"),
/**
* GBase-8s
*/
GBASE_8S("gbase-8s", "南大通用数据库 GBase 8s"),
/**
* Oscar
*/
OSCAR("oscar", "神通数据库"),
/**
* Sybase
*/
SYBASE("sybase", "Sybase ASE 数据库"),
/**
* OceanBase
*/
OCEAN_BASE("oceanbase", "OceanBase 数据库"),
/**
* Firebird
*/
FIREBIRD("Firebird", "Firebird 数据库"),
/**
* derby
*/
DERBY("derby", "derby 数据库"),
/**
* HighGo
*/
HIGH_GO("highgo", "瀚高数据库"),
/**
* CUBRID
*/
CUBRID("cubrid", "CUBRID数据库"),
/**
* GOLDILOCKS
*/
GOLDILOCKS("goldilocks", "GOLDILOCKS数据库"),
/**
* CSIIDB
*/
CSIIDB("csiidb", "CSIIDB数据库"),
/**
* CSIIDB
*/
SAP_HANA("hana", "SAP_HANA数据库"),
/**
* Impala
*/
IMPALA("impala", "impala数据库"),
/**
* Vertica
*/
VERTICA("vertica", "vertica数据库"),
/**
* 东方国信 xcloud
*/
XCloud("xcloud", "行云数据库"),
/**
* redshift
*/
REDSHIFT("redshift", "亚马逊redshift数据库"),
/**
* openGauss
*/
OPENGAUSS("openGauss", "华为 opengauss 数据库"),
/**
* TDengine
*/
TDENGINE("TDengine", "TDengine数据库"),
/**
* Informix
*/
INFORMIX("informix", "Informix数据库"),
/**
* uxdb
*/
UXDB("uxdb", "优炫数据库"),
/**
* greenplum
*/
GREENPLUM("greenplum", "greenplum数据库"),
/**
* UNKNOWN DB
*/
OTHER("other", "其他数据库");
/**
* 数据库名称
*/
private final String name;
/**
* 描述
*/
private final String remarks;
DbType(String name, String remarks) {
this.name = name;
this.remarks = remarks;
}
}

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
import java.util.regex.Pattern;
/**
* DbType 解析 工具类
*/
public class DbTypeUtil {
/**
* 参考 druid MyBatis-plus JdbcUtils
* {@link com.alibaba.druid.util.JdbcUtils#getDbType(String, String)}
* {@link com.baomidou.mybatisplus.extension.toolkit.JdbcUtils#getDbType(String)}
*
* @param jdbcUrl jdbcURL
* @return 返回数据库类型
*/
public static DbType parseDbType(String jdbcUrl) {
jdbcUrl = jdbcUrl.toLowerCase();
if (jdbcUrl.contains(":mysql:") || jdbcUrl.contains(":cobar:")) {
return DbType.MYSQL;
} else if (jdbcUrl.contains(":mariadb:")) {
return DbType.MARIADB;
} else if (jdbcUrl.contains(":oracle:")) {
return DbType.ORACLE;
} else if (jdbcUrl.contains(":sqlserver:") || jdbcUrl.contains(":microsoft:")) {
return DbType.SQL_SERVER2005;
} else if (jdbcUrl.contains(":sqlserver2012:")) {
return DbType.SQL_SERVER;
} else if (jdbcUrl.contains(":postgresql:")) {
return DbType.POSTGRE_SQL;
} else if (jdbcUrl.contains(":hsqldb:")) {
return DbType.HSQL;
} else if (jdbcUrl.contains(":db2:")) {
return DbType.DB2;
} else if (jdbcUrl.contains(":sqlite:")) {
return DbType.SQLITE;
} else if (jdbcUrl.contains(":h2:")) {
return DbType.H2;
} else if (isMatchedRegex(":dm\\d*:", jdbcUrl)) {
return DbType.DM;
} else if (jdbcUrl.contains(":xugu:")) {
return DbType.XU_GU;
} else if (isMatchedRegex(":kingbase\\d*:", jdbcUrl)) {
return DbType.KINGBASE_ES;
} else if (jdbcUrl.contains(":phoenix:")) {
return DbType.PHOENIX;
} else if (jdbcUrl.contains(":zenith:")) {
return DbType.GAUSS;
} else if (jdbcUrl.contains(":gbase:")) {
return DbType.GBASE;
} else if (jdbcUrl.contains(":gbasedbt-sqli:") || jdbcUrl.contains(":informix-sqli:")) {
return DbType.GBASE_8S;
} else if (jdbcUrl.contains(":ch:") || jdbcUrl.contains(":clickhouse:")) {
return DbType.CLICK_HOUSE;
} else if (jdbcUrl.contains(":oscar:")) {
return DbType.OSCAR;
} else if (jdbcUrl.contains(":sybase:")) {
return DbType.SYBASE;
} else if (jdbcUrl.contains(":oceanbase:")) {
return DbType.OCEAN_BASE;
} else if (jdbcUrl.contains(":highgo:")) {
return DbType.HIGH_GO;
} else if (jdbcUrl.contains(":cubrid:")) {
return DbType.CUBRID;
} else if (jdbcUrl.contains(":goldilocks:")) {
return DbType.GOLDILOCKS;
} else if (jdbcUrl.contains(":csiidb:")) {
return DbType.CSIIDB;
} else if (jdbcUrl.contains(":sap:")) {
return DbType.SAP_HANA;
} else if (jdbcUrl.contains(":impala:")) {
return DbType.IMPALA;
} else if (jdbcUrl.contains(":vertica:")) {
return DbType.VERTICA;
} else if (jdbcUrl.contains(":xcloud:")) {
return DbType.XCloud;
} else if (jdbcUrl.contains(":firebirdsql:")) {
return DbType.FIREBIRD;
} else if (jdbcUrl.contains(":redshift:")) {
return DbType.REDSHIFT;
} else if (jdbcUrl.contains(":opengauss:")) {
return DbType.OPENGAUSS;
} else if (jdbcUrl.contains(":taos:") || jdbcUrl.contains(":taos-rs:")) {
return DbType.TDENGINE;
} else if (jdbcUrl.contains(":informix")) {
return DbType.INFORMIX;
} else if (jdbcUrl.contains(":uxdb:")) {
return DbType.UXDB;
} else if (jdbcUrl.contains(":greenplum:")) {
return DbType.GREENPLUM;
} else {
return DbType.OTHER;
}
}
/**
* 正则匹配验证成功返回 true验证失败返回 false
*/
public static boolean isMatchedRegex(String regex, String jdbcUrl) {
if (null == jdbcUrl) {
return false;
}
return Pattern.compile(regex).matcher(jdbcUrl).find();
}
}

View File

@ -0,0 +1,138 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.util.ObjectUtil;
import org.apache.ibatis.util.MapUtil;
import java.util.EnumMap;
import java.util.Map;
/**
* 方言工厂类用于创建方言
*/
public class DialectFactory {
/**
* 数据库类型和方言的映射关系可以通过其读取指定的方言亦可能通过其扩展其他方言
* 比如 mybatis-flex 实现的方言中有 bug 或者 有自己的独立实现可以添加自己的方言实现到
* map 用于覆盖系统的方言实现
*/
private static Map<DbType, IDialect> dialectMap = new EnumMap<>(DbType.class);
/**
* 通过设置当前线程的数据库类型以达到在代码执行时随时切换方言的功能
*/
private static ThreadLocal<DbType> dbTypeThreadLocal = new ThreadLocal<>();
/**
* 获取方言
*
* @return IDialect
*/
public static IDialect getDialect() {
DbType dbType = ObjectUtil.requireNonNullElse(dbTypeThreadLocal.get(), FlexGlobalConfig.getDefaultConfig().getDbType());
return MapUtil.computeIfAbsent(dialectMap, dbType, DialectFactory::createDialectByDbType);
}
/**
* 设置当前线程的 dbType
*
* @param dbType
*/
public static void setHintDbType(DbType dbType) {
dbTypeThreadLocal.set(dbType);
}
/**
* 获取当前线程的 dbType
*
* @return dbType
*/
public static DbType getHintDbType() {
return dbTypeThreadLocal.get();
}
/**
* 清除当前线程的 dbType
*/
public static void clearHintDbType() {
dbTypeThreadLocal.remove();
}
/**
* 可以为某个 dbType 注册新增或覆盖自己的方言
*
* @param dbType 数据库类型
* @param dialect 方言的实现
*/
public static void registerDialect(DbType dbType, IDialect dialect) {
dialectMap.put(dbType, dialect);
}
private static IDialect createDialectByDbType(DbType dbType) {
switch (dbType) {
case MYSQL:
case H2:
case MARIADB:
case GBASE:
case OSCAR:
case XU_GU:
case CLICK_HOUSE:
case OCEAN_BASE:
case CUBRID:
case GOLDILOCKS:
case CSIIDB:
return new CommonsDialectImpl(KeywordWrap.BACKQUOTE, LimitOffsetProcesser.MYSQL);
case ORACLE:
case DM:
case GAUSS:
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcesser.ORACLE);
case POSTGRE_SQL:
case SQLITE:
case HSQL:
case KINGBASE_ES:
case PHOENIX:
case SAP_HANA:
case IMPALA:
case HIGH_GO:
case VERTICA:
case REDSHIFT:
case OPENGAUSS:
case TDENGINE:
case UXDB:
return new CommonsDialectImpl(KeywordWrap.BACKQUOTE, LimitOffsetProcesser.POSTGRESQL);
case ORACLE_12C:
case FIREBIRD:
case SQL_SERVER:
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcesser.DERBY);
case SQL_SERVER2005:
return new CommonsDialectImpl(KeywordWrap.SQUARE_BRACKETS, LimitOffsetProcesser.DB2);
case INFORMIX:
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcesser.INFORMIX);
case DB2:
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcesser.DB2);
default:
return new CommonsDialectImpl();
}
}
}

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.table.TableInfo;
import java.util.List;
public interface IDialect {
String wrap(String keyword);
String forInsertRow(String tableName, Row row);
String forInsertBatchWithFirstRowColumns(String tableName, List<Row> rows);
String forDeleteById(String tableName, String[] primaryKeys);
String forDeleteBatchByIds(String tableName, String[] primaryKeys, Object[] ids);
String forDeleteByQuery(QueryWrapper queryWrapper);
String forUpdateById(String tableName, Row row);
String forUpdateByQuery(String tableName, Row data, QueryWrapper queryWrapper);
String forUpdateBatchById(String tableName, List<Row> rows);
String forSelectOneById(String tableName, String[] primaryKeys, Object[] primaryValues);
String forSelectListByQuery(QueryWrapper queryWrapper);
String forSelectCountByQuery(QueryWrapper queryWrapper);
String buildSelectSql(QueryWrapper queryWrapper);
String buildSelectCountSql(QueryWrapper queryWrapper);
String buildDeleteSql(QueryWrapper queryWrapper);
String buildWhereConditionSql(QueryWrapper queryWrapper);
//////for entity /////
String forInsertEntity(TableInfo tableInfo, Object entity);
String forInsertBatchWithFirstEntityColumns(TableInfo tableInfo, List<Object> entities);
String forDeleteEntityById(TableInfo tableInfo);
String forDeleteEntityBatchById(TableInfo tableInfo, Object[] primaryValues);
String forUpdateEntity(TableInfo tableInfo, Object entity, boolean ignoreNulls);
String forUpdateEntityByQuery(TableInfo tableInfo, Object entity, boolean ignoreNulls, QueryWrapper queryWrapper);
String forSelectOneEntityById(TableInfo tableInfo);
String forSelectEntityListByIds(TableInfo tableInfo, Object[] primaryValues);
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.dialect;
import com.mybatisflex.core.util.StringUtil;
/**
* 用于对数据库的关键字包装
*/
public class KeywordWrap {
/**
* 无反义处理, 适用于 db2, informix, clickhouse
*/
public final static KeywordWrap NONE = new KeywordWrap("", "") {
@Override
public String wrap(String keyword) {
return keyword;
}
};
/**
* 反引号反义处理, 适用于 mysql, h2
*/
public final static KeywordWrap BACKQUOTE = new KeywordWrap("`", "`");
/**
* 双引号反义处理, 适用于 postgresql, sqlite, derby, oracle
*/
public final static KeywordWrap DOUBLE_QUOTATION = new KeywordWrap("\"", "\"");
/**
* 方括号反义处理, 适用于 sqlserver
*/
public final static KeywordWrap SQUARE_BRACKETS = new KeywordWrap("[", "]");
/**
* 前缀
*/
private final String prefix;
/**
* 后缀
*/
private final String suffix;
public KeywordWrap(String prefix, String suffix) {
this.prefix = prefix;
this.suffix = suffix;
}
public String wrap(String keyword) {
return StringUtil.isBlank(keyword) ? "" : prefix + keyword + suffix;
}
}

View File

@ -0,0 +1,138 @@
package com.mybatisflex.core.dialect;
import com.mybatisflex.core.querywrapper.QueryWrapper;
/**
* limit offset 参数的处理器
*/
public interface LimitOffsetProcesser {
/**
* MySql 的处理器
* 适合 {@link DbType#MYSQL,DbType#MARIADB,DbType#H2,DbType#CLICK_HOUSE,DbType#XCloud}
*/
LimitOffsetProcesser MYSQL = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
sql.append(" LIMIT ").append(limitOffset).append(", ").append(limitRows);
} else if (limitRows != null) {
sql.append(" LIMIT ").append(limitRows);
}
return sql;
};
/**
* Postgresql 的处理器
* 适合 {@link DbType#POSTGRE_SQL,DbType#SQLITE,DbType#H2,DbType#HSQL,DbType#KINGBASE_ES,DbType#PHOENIX}
* 适合 {@link DbType#SAP_HANA,DbType#IMPALA,DbType#HIGH_GO,DbType#VERTICA,DbType#REDSHIFT}
* 适合 {@link DbType#OPENGAUSS,DbType#TDENGINE,DbType#UXDB}
*/
LimitOffsetProcesser POSTGRESQL = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
sql.append(" LIMIT ").append(limitOffset).append(" OFFSET ").append(limitRows);
} else if (limitRows != null) {
sql.append(" LIMIT ").append(limitRows);
}
return sql;
};
/**
* derby 的处理器
* 适合 {@link DbType#DERBY,DbType#ORACLE_12C,DbType#SQL_SERVER,DbType#POSTGRE_SQL}
*/
LimitOffsetProcesser DERBY = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
// OFFSET ** ROWS FETCH NEXT ** ROWS ONLY")
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
} else if (limitRows != null) {
// FETCH FIRST 20 ROWS ONLY
sql.append(" FETCH FIRST ").append(limitRows).append(" ROWS ONLY");
}
return sql;
};
/**
* db2 的处理器
* 适合 {@link DbType#DB2,DbType#SQL_SERVER2005}
*/
LimitOffsetProcesser DB2 = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
// OFFSET ** ROWS FETCH NEXT ** ROWS ONLY")
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
} else if (limitRows != null) {
// FETCH FIRST 20 ROWS ONLY
sql.append(" FETCH FIRST ").append(limitRows).append(" ROWS ONLY");
}
return sql;
};
/**
* Informix 的处理器
* 适合 {@link DbType#INFORMIX}
* 文档 {@link <a href="https://www.ibm.com/docs/en/informix-servers/14.10?topic=clause-restricting-return-values-skip-limit-first-options">https://www.ibm.com/docs/en/informix-servers/14.10?topic=clause-restricting-return-values-skip-limit-first-options</a>}
*/
LimitOffsetProcesser INFORMIX = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
// SELECT SKIP 2 FIRST 1 * FROM
sql.insert(6, " SKIP " + limitOffset + " FIRST " + limitRows);
} else if (limitRows != null) {
sql.insert(6, " FIRST " + limitRows);
}
return sql;
};
/**
* Firebird 的处理器
* 适合 {@link DbType#FIREBIRD}
*/
LimitOffsetProcesser FIREBIRD = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
// ROWS 2 TO 3
sql.append(" ROWS ").append(limitOffset).append(" TO ").append(limitOffset + limitRows);
} else if (limitRows != null) {
sql.insert(6, " FIRST " + limitRows);
}
return sql;
};
/**
* Oracle11g及以下数据库的处理器
* 适合 {@link DbType#ORACLE,DbType#DM,DbType#GAUSS}
*/
LimitOffsetProcesser ORACLE = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null) {
if (limitOffset == null) {
limitOffset = 0;
}
StringBuilder newSql = new StringBuilder("SELECT * FROM (SELECT TEMP_DATAS.*, ROWNUM RN FROM (");
newSql.append(sql);
newSql.append(") TEMP_DATAS WHERE ROWNUM <=").append(limitOffset + limitRows).append(") WHERE RN >").append(limitOffset);
return newSql;
}
return sql;
};
/**
* Sybase 处理器
* 适合 {@link DbType#SYBASE}
*/
LimitOffsetProcesser SYBASE = (sql, queryWrapper, limitRows, limitOffset) -> {
if (limitRows != null && limitOffset != null) {
//SELECT TOP 1 START AT 3 * FROM
sql.insert(6, " TOP " + limitRows + " START AT " + (limitRows + limitOffset));
} else if (limitRows != null) {
sql.insert(6, " TOP " + limitRows);
}
return sql;
};
/**
* 处理构建 limit offset
*
* @param sql 已经构建的 sql
* @param queryWrapper 参数内容
* @param limitRows 用户传入的 limit 参数 可能为 null
* @param limitOffset 用户传入的 offset 参数可能为 null
*/
StringBuilder process(StringBuilder sql, QueryWrapper queryWrapper, Integer limitRows, Integer limitOffset);
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.exception;
/**
* MybatisFlexException 异常封装类
*/
public final class FlexExceptions {
private FlexExceptions() {
}
/**
* 封装 MybatisFlexException 异常
*
* @param throwable 异常
* @return MybatisFlexException
*/
public static MybatisFlexException wrap(Throwable throwable) {
if (throwable instanceof MybatisFlexException){
return (MybatisFlexException) throwable;
}
return new MybatisFlexException(throwable);
}
/**
* 封装 MybatisFlexException 异常
*
* @param throwable 异常
* @param msg 消息
* @param params 消息参数
* @return MybatisFlexException
*/
public static MybatisFlexException wrap(Throwable throwable, String msg, Object... params) {
return new MybatisFlexException(String.format(msg, params), throwable);
}
/**
* 封装 MybatisFlexException 异常
*
* @param msg 消息
* @param params 消息参数
* @return MybatisFlexException
*/
public static MybatisFlexException wrap(String msg, Object... params) {
return new MybatisFlexException(String.format(msg, params));
}
/**
* 抛出一个新的 MybatisFlexException 异常
*
* @param condition 抛出条件
* @param msg 消息
* @param params 消息参数
*/
public static void throwIf(boolean condition, String msg, Object... params) {
if (condition) {
throw wrap(msg, params);
}
}
}

View File

@ -0,0 +1,35 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.exception;
public class MybatisFlexException extends RuntimeException {
private static final long serialVersionUID = 1L;
public MybatisFlexException(String message) {
super(message);
}
public MybatisFlexException(String message, Throwable cause) {
super(message, cause);
}
public MybatisFlexException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,48 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.javassist;
import java.io.Serializable;
import java.util.LinkedHashSet;
import java.util.Set;
public interface ModifyAttrsRecord extends Serializable {
/**
* 注意
* 对于 entity 来说这里存放的只是 属性的名称而非字段
* 对于 row 来说存放的则是 字段 名称
*/
Set<String> modifyAttrs = new LinkedHashSet<>();
default void addModifyAttr(String attr) {
modifyAttrs.add(attr);
}
default void removeModifyAttr(String attr) {
modifyAttrs.remove(attr);
}
default Set<String> obtainModifyAttrs() {
return new LinkedHashSet<>(modifyAttrs);
}
default void clearModifyFlag() {
modifyAttrs.clear();
}
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.javassist;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.javassist.util.proxy.MethodHandler;
import java.lang.reflect.Method;
public class ModifyAttrsRecordHandler implements MethodHandler {
@Override
public Object invoke(Object self, Method originalMethod, Method proxyMethod, Object[] args) throws Throwable {
if (originalMethod.getName().startsWith("set")){
String property = StringUtil.firstCharToLowerCase(originalMethod.getName().substring(3));
((ModifyAttrsRecord) self).addModifyAttr(property);
}
return proxyMethod.invoke(self, args);
}
}

View File

@ -0,0 +1,61 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.javassist;
import org.apache.ibatis.javassist.util.proxy.ProxyFactory;
import org.apache.ibatis.javassist.util.proxy.ProxyObject;
import org.apache.ibatis.logging.LogFactory;
import java.util.Arrays;
public class ModifyAttrsRecordProxyFactory {
private static ModifyAttrsRecordProxyFactory instance = new ModifyAttrsRecordProxyFactory();
public static ModifyAttrsRecordProxyFactory getInstance(){
return instance;
}
private ModifyAttrsRecordProxyFactory(){}
public <T> T get(Class<T> target) {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(target);
Class<?>[] interfaces = Arrays.copyOf(target.getInterfaces(), target.getInterfaces().length + 1);
interfaces[interfaces.length - 1] = ModifyAttrsRecord.class;
factory.setInterfaces(interfaces);
final Class<?> proxyClass = factory.createClass();
T proxyObject = null;
try {
proxyObject = (T) proxyClass.newInstance();
((ProxyObject) proxyObject).setHandler(new ModifyAttrsRecordHandler());
} catch (Throwable e) {
LogFactory.getLog(ModifyAttrsRecordProxyFactory.class).error(e.toString(),e);
}
return proxyObject;
}
}

View File

@ -0,0 +1,126 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.table.IdInfo;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.invoker.Invoker;
import org.apache.ibatis.session.Configuration;
import java.sql.Statement;
import java.util.Map;
/**
* 通过 java 编码的方式生成主键
* 当主键类型配置为 KeyType#Generator 使用此生成器生成
* {@link com.mybatisflex.core.enums.KeyType#Generator}
*/
public class CustomKeyGenerator implements KeyGenerator {
protected Configuration configuration;
protected IKeyGenerator keyGenerator;
protected TableInfo tableInfo;
protected IdInfo idInfo;
public CustomKeyGenerator(Configuration configuration, TableInfo tableInfo, IdInfo idInfo) {
this.configuration = configuration;
this.keyGenerator = KeyGeneratorFactory.getKeyGenerator(idInfo.getValue());
this.tableInfo = tableInfo;
this.idInfo = idInfo;
ensuresKeyGeneratorNotNull();
}
private void ensuresKeyGeneratorNotNull() {
if (keyGenerator == null) {
throw FlexExceptions.wrap("The name of \"%s\" key generator not exist.\n" +
"please check annotation @Id(value=\"%s\") at field: %s#%s"
, idInfo.getValue(), idInfo.getValue(), tableInfo.getEntityClass().getName(), idInfo.getProperty());
}
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
Object entity = ((Map) parameter).get(FlexConsts.ENTITY);
Object generateId = keyGenerator.generate(entity, idInfo.getColumn());
try {
Invoker setInvoker = tableInfo.getReflector().getSetInvoker(idInfo.getProperty());
Object id = convert(generateId, setInvoker.getType());
setInvoker.invoke(entity, new Object[]{id});
} catch (Exception e) {
throw FlexExceptions.wrap(e);
}
}
public Object convert(Object value, Class<?> targetClass) {
if (value == null || (value.getClass() == String.class && StringUtil.isBlank((String) value)
&& targetClass != String.class)) {
return null;
}
if (value.getClass().isAssignableFrom(targetClass)) {
return value;
}
if (targetClass == String.class) {
return value.toString();
} else if (targetClass == Long.class || targetClass == long.class) {
if (value instanceof Number) {
return ((Number) value).longValue();
}
return Long.parseLong(value.toString());
} else if (targetClass == java.math.BigInteger.class) {
return new java.math.BigInteger(value.toString());
} else if (targetClass == java.math.BigDecimal.class) {
return new java.math.BigDecimal(value.toString());
} else if (targetClass == Integer.class || targetClass == int.class) {
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.parseInt(value.toString());
} else if (targetClass == Double.class || targetClass == double.class) {
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return Double.parseDouble(value.toString());
} else if (targetClass == Float.class || targetClass == float.class) {
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.parseFloat(value.toString());
} else if (targetClass == Short.class || targetClass == short.class) {
if (value instanceof Number) {
return ((Number) value).shortValue();
}
return Short.parseShort(value.toString());
}
throw FlexExceptions.wrap("\"%s\" can not be parsed for primary key.", value);
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
//do nothing
}
}

View File

@ -0,0 +1,22 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
public interface IKeyGenerator {
Object generate(Object entity,String keyColumn);
}

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
public interface IMultiKeyGenerator {
/**
* 是否需要数据库生成主键
*
* @return true 需要生成主键
*/
boolean isNeedGeneratedKeys();
/**
* 数据库主键的列名
*
* @return 列名数组
*/
String[] getKeyColumnNames();
}

View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.key.impl.UUIDKeyGenerator;
import java.util.HashMap;
import java.util.Map;
public class KeyGeneratorFactory {
private static final Map<String, IKeyGenerator> KEY_GENERATOR_MAP = new HashMap<>();
static {
/** 内置了 uuid 的生成器因此主键配置的时候可以直接配置为 @Id(keyType = KeyType.Generator, value = "uuid")
* {@link com.mybatisflex.annotation.Id}
*/
register("uuid", new UUIDKeyGenerator());
}
/**
* 获取 主键生成器
*
* @param name
* @return
*/
public static IKeyGenerator getKeyGenerator(String name) {
return KEY_GENERATOR_MAP.get(name.trim());
}
/**
* 注册一个主键生成器
*
* @param key
* @param keyGenerator
*/
public static void register(String key, IKeyGenerator keyGenerator) {
KEY_GENERATOR_MAP.put(key.trim(), keyGenerator);
}
}

View File

@ -0,0 +1,57 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
/**
* 多实体主键生成器用于批量插入的场景
*/
public class MultiEntityKeyGenerator implements KeyGenerator {
private KeyGenerator keyGenerator;
public MultiEntityKeyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
List<Object> entities = (List<Object>) ((Map) parameter).get(FlexConsts.ENTITIES);
if (CollectionUtil.isNotEmpty(entities)) {
for (Object entity : entities) {
((Map) parameter).put(FlexConsts.ENTITY, entity);
keyGenerator.processBefore(executor, ms, stmt, parameter);
}
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
// 多条数据批量插入的场景下不支持后设置主键
// 比如 INSERT INTO `tb_account`(uuid,name,sex) VALUES (?, ?, ?), (?, ?, ?)
}
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.table.IdInfo;
import com.mybatisflex.core.table.TableInfo;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
* 多主键复合主键 id 生成器
*/
public class MultiPrimaryKeyGenerator implements KeyGenerator, IMultiKeyGenerator {
private List<KeyGenerator> keyGenerators;
//所有自增字段的名称
private String[] autoGenKeyColumnNames;
public MultiPrimaryKeyGenerator(MappedStatement mappedStatement, TableInfo tableInfo, List<IdInfo> primaryKeyList) {
this.keyGenerators = new ArrayList<>();
List<String> autoGenKeyColumnNameList = new ArrayList<>();
for (IdInfo idInfo : primaryKeyList) {
KeyGenerator idKeyGenerator = MybatisKeyGeneratorUtil.createIdKeyGenerator(tableInfo, mappedStatement, idInfo);
keyGenerators.add(idKeyGenerator);
if (idKeyGenerator == Jdbc3KeyGenerator.INSTANCE) {
autoGenKeyColumnNameList.add(idInfo.getColumn());
}
}
this.autoGenKeyColumnNames = autoGenKeyColumnNameList.toArray(new String[0]);
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
for (KeyGenerator keyGenerator : keyGenerators) {
keyGenerator.processBefore(executor, ms, stmt, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
for (KeyGenerator keyGenerator : keyGenerators) {
keyGenerator.processAfter(executor, ms, stmt, parameter);
}
}
/**
* 是否需要数据库 自动生成主键
*
* @return true: need generated keys
*/
@Override
public boolean isNeedGeneratedKeys() {
for (KeyGenerator keyGenerator : keyGenerators) {
if (keyGenerator == Jdbc3KeyGenerator.INSTANCE) {
return true;
}
}
return false;
}
/**
* 自动生成主键的 columns 字段
*
* @return keyColumnNames
*/
@Override
public String[] getKeyColumnNames() {
return autoGenKeyColumnNames;
}
}

View File

@ -0,0 +1,58 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
/**
* 用于批量插入的场景
*/
public class MultiRowKeyGenerator implements KeyGenerator {
private KeyGenerator keyGenerator;
public MultiRowKeyGenerator(KeyGenerator keyGenerator) {
this.keyGenerator = keyGenerator;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
List<Row> rows = (List<Row>) ((Map) parameter).get(FlexConsts.ROWS);
if (CollectionUtil.isNotEmpty(rows)) {
for (Row entity : rows) {
((Map) parameter).put(FlexConsts.ROW, entity);
keyGenerator.processBefore(executor, ms, stmt, parameter);
}
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
// 多条数据批量插入的场景下不支持后设置主键
// 比如 INSERT INTO `tb_account`(uuid,name,sex) VALUES (?, ?, ?), (?, ?, ?)
}
}

View File

@ -0,0 +1,118 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.enums.KeyType;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.table.IdInfo;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.session.Configuration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MybatisKeyGeneratorUtil {
public static KeyGenerator createTableKeyGenerator(TableInfo tableInfo, MappedStatement ms) {
List<IdInfo> primaryKeyList = tableInfo.getPrimaryKeyList();
//无主键
if (primaryKeyList == null || primaryKeyList.isEmpty()) {
return NoKeyGenerator.INSTANCE;
}
//多主键的
if (primaryKeyList.size() > 1) {
return new MultiPrimaryKeyGenerator(ms, tableInfo, primaryKeyList);
}
return createIdKeyGenerator(tableInfo, ms, primaryKeyList.get(0));
}
public static KeyGenerator createIdKeyGenerator(TableInfo tableInfo, MappedStatement ms, IdInfo idInfo) {
if (idInfo.getKeyType() == KeyType.None) {
return NoKeyGenerator.INSTANCE;
}
//自增主键
if (idInfo.getKeyType() == KeyType.Auto) {
return Jdbc3KeyGenerator.INSTANCE;
}
//通过 java 生成的主键
if (idInfo.getKeyType() == KeyType.Generator) {
return new CustomKeyGenerator(ms.getConfiguration(), tableInfo, idInfo);
}
//通过序列生成的注解
String sequence = idInfo.getValue();
if (StringUtil.isBlank(sequence)) {
throw FlexExceptions.wrap("please config @Id(value=\"...\") for field: %s in class: %s"
, idInfo.getProperty()
, tableInfo.getEntityClass().getName());
}
String selectId = ms.getId() + SelectKeyGenerator.SELECT_KEY_SUFFIX;
SqlSource sqlSource = ms.getLang().createSqlSource(ms.getConfiguration(), sequence.trim(), idInfo.getPropertyType());
MappedStatement.Builder msBuilder = new MappedStatement.Builder(ms.getConfiguration(), selectId, sqlSource, SqlCommandType.SELECT)
.resource(ms.getResource())
.fetchSize(null)
.timeout(null)
.statementType(StatementType.PREPARED)
.keyGenerator(NoKeyGenerator.INSTANCE)
.keyProperty(FlexConsts.ENTITY + "." + idInfo.getProperty())
.keyColumn(idInfo.getColumn())
.databaseId(ms.getDatabaseId())
.lang(ms.getLang())
.resultOrdered(false)
.resultSets(null)
.resultMaps(createIdResultMaps(ms.getConfiguration(), selectId + "-Inline", idInfo.getPropertyType(), new ArrayList<>()))
.resultSetType(null)
.flushCacheRequired(false)
.useCache(false)
.cache(ms.getCache());
MappedStatement keyMappedStatement = msBuilder.build();
ms.getConfiguration().addMappedStatement(keyMappedStatement);
//看到有的框架把 keyGenerator 添加到 mybatis 的当前配置里去其实是完全没必要的
//因为只有在 xml 解析的时候才可能存在多一个 MappedStatement 拥有同一个 keyGenerator 的情况
//当前每个方法都拥有一个自己的 keyGenerator 没必要添加
//this.addKeyGenerator(selectId, keyGenerator);
return new SelectKeyGenerator(keyMappedStatement, idInfo.isBefore());
}
private static List<ResultMap> createIdResultMaps(Configuration configuration,
String statementId, Class<?> resultType, List<ResultMapping> resultMappings) {
ResultMap resultMap = new ResultMap.Builder(configuration, statementId, resultType, resultMappings, null)
.build();
return Arrays.asList(resultMap);
}
}

View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import java.sql.Statement;
import java.util.Map;
/**
* 通过 java 编码的方式生成主键
* 当主键类型配置为 KeyType#Generator 使用此生成器生成
* {@link com.mybatisflex.core.enums.KeyType#Generator}
*/
public class RowCustomKeyGenerator implements KeyGenerator {
protected RowKey rowKey;
protected IKeyGenerator keyGenerator;
public RowCustomKeyGenerator(RowKey rowKey) {
this.rowKey = rowKey;
this.keyGenerator = KeyGeneratorFactory.getKeyGenerator(rowKey.getValue());
ensuresKeyGeneratorNotNull();
}
private void ensuresKeyGeneratorNotNull() {
if (keyGenerator == null) {
throw FlexExceptions.wrap("The name of \"%s\" key generator not exist.", rowKey.getValue());
}
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
Row row = (Row) ((Map) parameter).get(FlexConsts.ROW);
Object generateId = keyGenerator.generate(row, rowKey.getKeyColumn());
try {
row.put(rowKey.getKeyColumn(), generateId);
} catch (Exception e) {
throw FlexExceptions.wrap(e);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
//do nothing
}
}

View File

@ -0,0 +1,270 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.ArrayUtil;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.ibatis.util.MapUtil;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.*;
import java.util.Map.Entry;
/**
* 主要作用是为 Row 生成自增主键
*/
public class RowJdbc3KeyGenerator implements KeyGenerator {
private String keyProperty;
private static final String SECOND_GENERIC_PARAM_NAME = ParamNameResolver.GENERIC_NAME_PREFIX + "2";
private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. "
+ "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523.";
public RowJdbc3KeyGenerator(String keyProperty) {
this.keyProperty = keyProperty;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
// do nothing
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
processBatch(ms, stmt, parameter);
}
public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
final String[] keyProperties = new String[]{FlexConsts.ROW + "." + keyProperty};
try (ResultSet rs = stmt.getGeneratedKeys()) {
final ResultSetMetaData rsmd = rs.getMetaData();
final Configuration configuration = ms.getConfiguration();
if (rsmd.getColumnCount() < keyProperties.length) {
// Error?
} else {
assignKeys(configuration, rs, rsmd, keyProperties, parameter);
}
} catch (Exception e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
}
}
@SuppressWarnings("unchecked")
private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
Object parameter) throws SQLException {
if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
// Multi-param or single param with @Param
assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
} else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
&& ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
// Multi-param or single param with @Param in batch operation
assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
} else {
// Single param without @Param
assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
}
}
private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Object parameter) throws SQLException {
Collection<?> params = collectionize(parameter);
if (params.isEmpty()) {
return;
}
List<KeyAssigner> assignerList = new ArrayList<>();
for (int i = 0; i < keyProperties.length; i++) {
assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
}
Iterator<?> iterator = params.iterator();
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
}
Object param = iterator.next();
assignerList.forEach(x -> x.assign(rs, param));
}
}
private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException {
Iterator<ParamMap<?>> iterator = paramMapList.iterator();
List<KeyAssigner> assignerList = new ArrayList<>();
long counter = 0;
while (rs.next()) {
if (!iterator.hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
}
ParamMap<?> paramMap = iterator.next();
if (assignerList.isEmpty()) {
for (int i = 0; i < keyProperties.length; i++) {
assignerList
.add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false)
.getValue());
}
}
assignerList.forEach(x -> x.assign(rs, paramMap));
counter++;
}
}
private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
String[] keyProperties, Map<String, ?> paramMap) throws SQLException {
if (paramMap.isEmpty()) {
return;
}
Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>();
for (int i = 0; i < keyProperties.length; i++) {
Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i],
keyProperties, true);
Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = MapUtil.computeIfAbsent(assignerMap, entry.getKey(),
k -> MapUtil.entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>()));
iteratorPair.getValue().add(entry.getValue());
}
long counter = 0;
while (rs.next()) {
for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) {
if (!pair.getKey().hasNext()) {
throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter));
}
Object param = pair.getKey().next();
pair.getValue().forEach(x -> x.assign(rs, param));
}
counter++;
}
}
private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd,
int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) {
Set<String> keySet = paramMap.keySet();
// A caveat : if the only parameter has {@code @Param("param2")} on it,
// it must be referenced with param name e.g. 'param2.x'.
boolean singleParam = !keySet.contains(SECOND_GENERIC_PARAM_NAME);
int firstDot = keyProperty.indexOf('.');
if (firstDot == -1) {
if (singleParam) {
return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
}
throw new ExecutorException("Could not determine which parameter to assign generated keys to. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ keySet);
}
String paramName = keyProperty.substring(0, firstDot);
if (keySet.contains(paramName)) {
String argParamName = omitParamName ? null : paramName;
String argKeyProperty = keyProperty.substring(firstDot + 1);
return MapUtil.entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty));
} else if (singleParam) {
return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName);
} else {
throw new ExecutorException("Could not find parameter '" + paramName + "'. "
+ "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). "
+ "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are "
+ keySet);
}
}
private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd,
int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) {
// Assume 'keyProperty' to be a property of the single param.
String singleParamName = nameOfSingleParam(paramMap);
String argParamName = omitParamName ? null : singleParamName;
return MapUtil.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty));
}
private static String nameOfSingleParam(Map<String, ?> paramMap) {
// There is virtually one parameter, so any key works.
return paramMap.keySet().iterator().next();
}
private static Collection<?> collectionize(Object param) {
if (param instanceof Collection) {
return (Collection<?>) param;
} else if (param instanceof Object[]) {
return Arrays.asList((Object[]) param);
} else {
return Arrays.asList(param);
}
}
private class KeyAssigner {
private final Configuration configuration;
private final ResultSetMetaData rsmd;
private final TypeHandlerRegistry typeHandlerRegistry;
private final int columnPosition;
private final String paramName;
private final String propertyName;
private TypeHandler<?> typeHandler;
protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName,
String propertyName) {
super();
this.configuration = configuration;
this.rsmd = rsmd;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.columnPosition = columnPosition;
this.paramName = paramName;
this.propertyName = propertyName;
}
protected void assign(ResultSet rs, Object param) {
if (paramName != null) {
// If paramName is set, param is ParamMap
param = ((ParamMap<?>) param).get(paramName);
}
MetaObject metaParam = configuration.newMetaObject(param);
try {
if (typeHandler == null) {
if (metaParam.hasSetter(propertyName)) {
Class<?> propertyType = metaParam.getSetterType(propertyName);
typeHandler = typeHandlerRegistry.getTypeHandler(propertyType,
JdbcType.forCode(rsmd.getColumnType(columnPosition)));
} else {
throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '"
+ metaParam.getOriginalObject().getClass().getName() + "'.");
}
}
if (typeHandler == null) {
// Error?
} else {
Object value = typeHandler.getResult(rs, columnPosition);
metaParam.setValue(propertyName, value);
}
} catch (SQLException e) {
throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e,
e);
}
}
}
}

View File

@ -0,0 +1,154 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.enums.KeyType;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowKey;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.mapping.StatementType;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* row 的主键生成器
*/
public class RowKeyGenerator implements KeyGenerator, IMultiKeyGenerator {
private static KeyGenerator[] NO_KEY_GENERATORS = new KeyGenerator[0];
private MappedStatement ms;
private KeyGenerator[] keyGenerators;
private List<String> autoKeyGeneratorNames;
public RowKeyGenerator(MappedStatement methodMappedStatement) {
this.ms = methodMappedStatement;
}
@Override
public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
Row row = (Row) ((Map) parameter).get(FlexConsts.ROW);
keyGenerators = buildRowKeyGenerators(row.obtainsPrimaryKeys());
for (KeyGenerator keyGenerator : keyGenerators) {
keyGenerator.processBefore(executor, ms, stmt, parameter);
}
}
@Override
public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
for (KeyGenerator keyGenerator : keyGenerators) {
keyGenerator.processAfter(executor, ms, stmt, parameter);
}
}
private KeyGenerator[] buildRowKeyGenerators(RowKey[] rowKeys) {
if (ArrayUtil.isEmpty(rowKeys)) {
return NO_KEY_GENERATORS;
}
KeyGenerator[] keyGenerators = new KeyGenerator[rowKeys.length];
for (int i = 0; i < rowKeys.length; i++) {
KeyGenerator keyGenerator = createByRowKey(rowKeys[i]);
keyGenerators[i] = keyGenerator;
}
return keyGenerators;
}
private KeyGenerator createByRowKey(RowKey rowKey) {
if (rowKey == null || rowKey.getKeyType() == KeyType.None) {
return NoKeyGenerator.INSTANCE;
}
if (rowKey.getKeyType() == KeyType.Auto) {
if (autoKeyGeneratorNames == null) {
autoKeyGeneratorNames = new ArrayList<>();
}
autoKeyGeneratorNames.add(rowKey.getKeyColumn());
return new RowJdbc3KeyGenerator(rowKey.getKeyColumn());
}
if (rowKey.getKeyType() == KeyType.Generator) {
return new RowCustomKeyGenerator(rowKey);
}
//通过数据库的 sequence 生成主键
else {
String selectId = "row." + SelectKeyGenerator.SELECT_KEY_SUFFIX;
String sequence = rowKey.getValue();
SqlSource sqlSource = ms.getLang().createSqlSource(ms.getConfiguration(), sequence.trim(), Object.class);
MappedStatement.Builder msBuilder = new MappedStatement.Builder(ms.getConfiguration(), selectId, sqlSource, SqlCommandType.SELECT)
.resource(ms.getResource())
.fetchSize(null)
.timeout(null)
.statementType(StatementType.PREPARED)
.keyGenerator(NoKeyGenerator.INSTANCE)
.keyProperty(FlexConsts.ROW + "." + rowKey.getKeyColumn())
.keyColumn(FlexConsts.ROW + "." + rowKey.getKeyColumn())
.databaseId(ms.getDatabaseId())
.lang(ms.getLang())
.resultOrdered(false)
.resultSets(null)
.resultMaps(new ArrayList<>())
.resultSetType(null)
.flushCacheRequired(false)
.useCache(false)
.cache(ms.getCache());
MappedStatement keyMappedStatement = msBuilder.build();
ms.getConfiguration().addMappedStatement(keyMappedStatement);
//看到有的框架把 keyGenerator 添加到 mybatis 的当前配置里去其实是完全没必要的
//因为只有在 xml 解析的时候才可能存在多一个 MappedStatement 拥有同一个 keyGenerator 的情况
//当前每个方法都拥有一个自己的 keyGenerator 没必要添加
//this.addKeyGenerator(selectId, keyGenerator);
return new SelectKeyGenerator(keyMappedStatement, rowKey.isBefore());
}
}
/**
* 是否需要数据库生成主键
*
* @return true 需要生成
*/
@Override
public boolean isNeedGeneratedKeys() {
return CollectionUtil.isNotEmpty(autoKeyGeneratorNames);
}
/**
* 数据库主键定义的 key
*
* @return key 数组
*/
@Override
public String[] getKeyColumnNames() {
return autoKeyGeneratorNames.toArray(new String[0]);
}
}

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.key.impl;
import com.mybatisflex.core.key.IKeyGenerator;
import java.util.UUID;
public class UUIDKeyGenerator implements IKeyGenerator {
@Override
public Object generate(Object entity, String keyColumn) {
return UUID.randomUUID().toString().replace("-", "");
}
}

View File

@ -0,0 +1,246 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.key.MultiEntityKeyGenerator;
import com.mybatisflex.core.key.MultiRowKeyGenerator;
import com.mybatisflex.core.key.MybatisKeyGeneratorUtil;
import com.mybatisflex.core.row.RowMapper;
import com.mybatisflex.core.key.RowKeyGenerator;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfos;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Map;
public class FlexConfiguration extends Configuration {
public FlexConfiguration(Environment environment) {
super(environment);
setMapUnderscoreToCamelCase(true);
setLogImpl(StdOutImpl.class);
initDefaultMappers();
}
public FlexConfiguration() {
setLogImpl(StdOutImpl.class);
initDefaultMappers();
}
/**
* 设置 mybatis-flex 默认的 Mapper
* 当前只有 RowMapper {@link RowMapper}
*/
private void initDefaultMappers() {
addMapper(RowMapper.class);
}
/**
* 为原生 sql 设置参数
*/
@Override
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
String mappedStatementId = mappedStatement.getId();
// "!selectKey" 结尾的 mappedStatementId是用于主键生成的无需为其设置参数
if (!mappedStatementId.endsWith(SelectKeyGenerator.SELECT_KEY_SUFFIX)
&& parameterObject instanceof Map
&& ((Map<?, ?>) parameterObject).containsKey(FlexConsts.SQL_ARGS)) {
return new SqlArgsParameterHandler(mappedStatement, (Map) parameterObject, boundSql);
} else {
return super.newParameterHandler(mappedStatement, parameterObject, boundSql);
}
}
/**
* 替换为 FlexRoutingStatementHandler主要用来为实体类的多主键做支持
* FlexRoutingStatementHandler 原生的 RoutingStatementHandler 对比没有任何性能影响
*/
@Override
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new FlexRoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
@Override
public void addMappedStatement(MappedStatement ms) {
//替换 RowMapper.insertRow 的主键生成器
//替换 RowMapper.insertBatchWithFirstRowColumns 的主键生成器
if (ms.getId().startsWith("com.mybatisflex.core.row.RowMapper.insert")) {
ms = replaceRowKeyGenerator(ms);
}
//entity insert methods
else if (StringUtil.endsWithAny(ms.getId(), "insert", "insertBatchWithFirstEntityColumns")
&& ms.getKeyGenerator() == NoKeyGenerator.INSTANCE) {
ms = replaceEntityKeyGenerator(ms);
}
//entity select
else if (StringUtil.endsWithAny(ms.getId(), "selectOneById", "selectListByIds"
, "selectListByQuery", "selectCountByQuery")) {
ms = replaceResultHandler(ms);
}
super.addMappedStatement(ms);
}
/**
* 替换 entity 查询的 ResultHandler
*/
private MappedStatement replaceResultHandler(MappedStatement ms) {
TableInfo tableInfo = getTableInfo(ms);
if (tableInfo == null) {
return ms;
}
String resultMapId = tableInfo.getEntityClass().getName();
ResultMap resultMap;
if (hasResultMap(resultMapId)) {
resultMap = getResultMap(resultMapId);
} else {
resultMap = tableInfo.buildResultMap(this);
this.addResultMap(resultMap);
}
return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
.resource(ms.getResource())
.fetchSize(ms.getFetchSize())
.timeout(ms.getTimeout())
.statementType(ms.getStatementType())
.keyGenerator(NoKeyGenerator.INSTANCE)
.keyProperty(ms.getKeyProperties() == null ? null : String.join(",", ms.getKeyProperties()))
.keyColumn(ms.getKeyColumns() == null ? null : String.join(",", ms.getKeyColumns()))
.databaseId(databaseId)
.lang(ms.getLang())
.resultOrdered(ms.isResultOrdered())
.resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
.resultMaps(CollectionUtil.newArrayList(resultMap)) // 替换resultMap
.resultSetType(ms.getResultSetType())
.flushCacheRequired(ms.isFlushCacheRequired())
.useCache(ms.isUseCache())
.cache(ms.getCache())
.build();
}
/**
* 生成新的已替换主键生成器的 MappedStatement
*
* @param ms MappedStatement
* @return replaced MappedStatement
*/
private MappedStatement replaceRowKeyGenerator(MappedStatement ms) {
KeyGenerator keyGenerator = new RowKeyGenerator(ms);
if (ms.getId().endsWith("insertBatchWithFirstRowColumns")) {
keyGenerator = new MultiRowKeyGenerator(keyGenerator);
}
return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
.resource(ms.getResource())
.fetchSize(ms.getFetchSize())
.timeout(ms.getTimeout())
.statementType(ms.getStatementType())
.keyGenerator(keyGenerator) // 替换主键生成器
.keyProperty(ms.getKeyProperties() == null ? null : String.join(",", ms.getKeyProperties()))
.keyColumn(ms.getKeyColumns() == null ? null : String.join(",", ms.getKeyColumns()))
.databaseId(databaseId)
.lang(ms.getLang())
.resultOrdered(ms.isResultOrdered())
.resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
.resultMaps(ms.getResultMaps())
.resultSetType(ms.getResultSetType())
.flushCacheRequired(ms.isFlushCacheRequired())
.useCache(ms.isUseCache())
.cache(ms.getCache())
.build();
}
/**
* 生成新的已替换主键生成器的 MappedStatement
*
* @param ms MappedStatement
* @return replaced MappedStatement
*/
private MappedStatement replaceEntityKeyGenerator(MappedStatement ms) {
TableInfo tableInfo = getTableInfo(ms);
if (tableInfo == null) {
return ms;
}
KeyGenerator keyGenerator = MybatisKeyGeneratorUtil.createTableKeyGenerator(tableInfo, ms);
if (keyGenerator == NoKeyGenerator.INSTANCE) {
return ms;
}
//批量插入
if (ms.getId().endsWith("insertBatchWithFirstEntityColumns")) {
keyGenerator = new MultiEntityKeyGenerator(keyGenerator);
}
return new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), ms.getSqlSource(), ms.getSqlCommandType())
.resource(ms.getResource())
.fetchSize(ms.getFetchSize())
.timeout(ms.getTimeout())
.statementType(ms.getStatementType())
.keyGenerator(keyGenerator) // 替换主键生成器
.keyProperty(tableInfo.getMappedStatementKeyProperties())
.keyColumn(tableInfo.getMappedStatementKeyColumns())
.databaseId(databaseId)
.lang(ms.getLang())
.resultOrdered(ms.isResultOrdered())
.resultSets(ms.getResultSets() == null ? null : String.join(",", ms.getResultSets()))
.resultMaps(ms.getResultMaps())
.resultSetType(ms.getResultSetType())
.flushCacheRequired(ms.isFlushCacheRequired())
.useCache(ms.isUseCache())
.cache(ms.getCache())
.build();
}
private TableInfo getTableInfo(MappedStatement ms) {
String mapperClassName = ms.getId().substring(0, ms.getId().lastIndexOf("."));
try {
Class<?> mapperClass = Class.forName(mapperClassName);
return TableInfos.ofMapperClass(mapperClass);
} catch (ClassNotFoundException e) {
return null;
}
}
}

View File

@ -0,0 +1,88 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import com.mybatisflex.core.key.IMultiKeyGenerator;
import com.mybatisflex.core.util.ArrayUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.statement.PreparedStatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.*;
/**
* @author Michael Yang(fuhai999@gmail.com)
*/
public class FlexPreparedStatementHandler extends PreparedStatementHandler {
public FlexPreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// String sql = boundSql.getSql();
// if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
// String[] keyColumnNames = mappedStatement.getKeyColumns();
// if (keyColumnNames == null) {
// return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
// } else {
// return connection.prepareStatement(sql, keyColumnNames);
// }
// } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
// return connection.prepareStatement(sql);
// } else {
// return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
// }
String sql = boundSql.getSql();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
if (keyGenerator instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
}
// 多主键的场景
else if (keyGenerator instanceof IMultiKeyGenerator) {
if (((IMultiKeyGenerator) keyGenerator).isNeedGeneratedKeys()) {
String[] keyColumnNames = ((IMultiKeyGenerator) keyGenerator).getKeyColumnNames();
if (ArrayUtil.isNotEmpty(keyColumnNames)) {
return connection.prepareStatement(sql, keyColumnNames);
} else {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
}
}
}
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
}

View File

@ -0,0 +1,102 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.CallableStatementHandler;
import org.apache.ibatis.executor.statement.SimpleStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
/**
* 源于 {@link org.apache.ibatis.executor.statement.RoutingStatementHandler}
* 主要是替换 PreparedStatementHandler FlexPreparedStatementHandler
*/
public class FlexRoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public FlexRoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
// use FlexPreparedStatementHandler to replace PreparedStatementHandler
delegate = new FlexPreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}

View File

@ -0,0 +1,129 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DbTypeUtil;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class FlexSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
@Override
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
FlexXMLConfigBuilder parser = new FlexXMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
@Override
public SqlSessionFactory build(Configuration configuration) {
if (!FlexConfiguration.class.isAssignableFrom(configuration.getClass())) {
throw FlexExceptions.wrap("only support FlexMybatisConfiguration.");
}
SqlSessionFactory sessionFactory = super.build(configuration);
DbType dbType = getDbType(configuration);
//设置全局配置的 sessionFactory dbType
initGlobalConfig(configuration, sessionFactory, dbType);
return sessionFactory;
}
/**
* 设置全局配置
*
* @param config
* @param sessionFactory
*/
private void initGlobalConfig(Configuration config, SqlSessionFactory sessionFactory, DbType dbType) {
FlexGlobalConfig flexGlobalConfig = new FlexGlobalConfig();
flexGlobalConfig.setSqlSessionFactory(sessionFactory);
flexGlobalConfig.setDbType(dbType);
String environmentId = config.getEnvironment().getId();
FlexGlobalConfig.setConfig(environmentId, flexGlobalConfig);
}
/**
* 获取当前配置的 DbType
*/
private DbType getDbType(Configuration configuration) {
DataSource dataSource = configuration.getEnvironment().getDataSource();
String jdbcUrl = getJdbcUrl(dataSource);
if (StringUtil.isNotBlank(jdbcUrl)){
return DbTypeUtil.parseDbType(jdbcUrl);
}
if ("org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy"
.equals(dataSource.getClass().getName())){
return DbType.H2;
}
return null;
}
/**
* 通过数据源中获取 jdbc url 配置
* 符合 HikariCP, druid, c3p0, DBCP, beecp 数据源框架 以及 MyBatis UnpooledDataSource 的获取规则
* UnpooledDataSource 参考 @{@link UnpooledDataSource#getUrl()}
* TODO: 2023/2/18 可能极个别特殊的数据源无法获取 JDBC 配置的 URL
*
* @return jdbc url 配置
*/
private String getJdbcUrl(DataSource dataSource) {
String[] methodNames = new String[]{"getUrl", "getJdbcUrl"};
for (String methodName : methodNames) {
try {
Method method = dataSource.getClass().getMethod(methodName);
return (String) method.invoke(dataSource);
} catch (Exception e) {
//ignore
}
}
return null;
}
}

View File

@ -0,0 +1,409 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.*;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.JdbcType;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;
/**
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class FlexXMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public FlexXMLConfigBuilder(Reader reader) {
this(reader, null, null);
}
public FlexXMLConfigBuilder(Reader reader, String environment) {
this(reader, environment, null);
}
public FlexXMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
public FlexXMLConfigBuilder(InputStream inputStream) {
this(inputStream, null, null);
}
public FlexXMLConfigBuilder(InputStream inputStream, String environment) {
this(inputStream, environment, null);
}
public FlexXMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private FlexXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 目前暂时只能通过这里替换 configuration
// 新的版本已经支持自定义 configuration但需新版本 release
// 详情 https://github.com/mybatis/mybatis-3/commit/d7826d71a7005a8b4d4e0e7a020db0f6c7e253a4
super(new FlexConfiguration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
Properties props = context.getChildrenAsProperties();
// Check that all settings are known to the configuration class
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
String value = props.getProperty("vfsImpl");
if (value != null) {
String[] clazzes = value.split(",");
for (String clazz : clazzes) {
if (!clazz.isEmpty()) {
@SuppressWarnings("unchecked")
Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
configuration.setVfsImpl(vfsImpl);
}
}
}
}
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
private void objectWrapperFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setObjectWrapperFactory(factory);
}
}
private void reflectorFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
configuration.setReflectorFactory(factory);
}
}
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
configuration.setArgNameBasedConstructorAutoMapping(booleanValueOf(props.getProperty("argNameBasedConstructorAutoMapping"), false));
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
configuration.setNullableOnForEach(booleanValueOf(props.getProperty("nullableOnForEach"), false));
}
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
private void databaseIdProviderElement(XNode context) throws Exception {
DatabaseIdProvider databaseIdProvider = null;
if (context != null) {
String type = context.getStringAttribute("type");
// awful patch to keep backward compatibility
if ("VENDOR".equals(type)) {
type = "DB_VENDOR";
}
Properties properties = context.getChildrenAsProperties();
databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
databaseIdProvider.setProperties(properties);
}
Environment environment = configuration.getEnvironment();
if (environment != null && databaseIdProvider != null) {
String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
configuration.setDatabaseId(databaseId);
}
}
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
private void typeHandlerElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeHandlerPackage = child.getStringAttribute("name");
typeHandlerRegistry.register(typeHandlerPackage);
} else {
String javaTypeName = child.getStringAttribute("javaType");
String jdbcTypeName = child.getStringAttribute("jdbcType");
String handlerTypeName = child.getStringAttribute("handler");
Class<?> javaTypeClass = resolveClass(javaTypeName);
JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
Class<?> typeHandlerClass = resolveClass(handlerTypeName);
if (javaTypeClass != null) {
if (jdbcType == null) {
typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
} else {
typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
}
} else {
typeHandlerRegistry.register(typeHandlerClass);
}
}
}
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
private boolean isSpecifiedEnvironment(String id) {
if (environment == null) {
throw new BuilderException("No environment specified.");
}
if (id == null) {
throw new BuilderException("Environment requires an id attribute.");
}
return environment.equals(id);
}
}

View File

@ -0,0 +1,89 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.mybatis;
import com.mybatisflex.core.FlexConsts;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Date;
import java.util.Map;
public class SqlArgsParameterHandler extends DefaultParameterHandler {
private final Map parameterObject;
public SqlArgsParameterHandler(MappedStatement mappedStatement, Map parameterObject, BoundSql boundSql) {
super(mappedStatement, parameterObject, boundSql);
this.parameterObject = parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
try {
doSetParameters(ps);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private void doSetParameters(PreparedStatement ps) throws SQLException {
Object[] sqlArgs = (Object[]) ((Map<?, ?>) parameterObject).get(FlexConsts.SQL_ARGS);
if (sqlArgs != null && sqlArgs.length > 0) {
int index = 1;
for (Object value : sqlArgs) {
// OracleSqlServer TIMESTAMPDATE 类型的数据是支持 java.util.Date 给值的
if (value instanceof java.util.Date) {
setDateParameter(ps, (Date) value, index++);
} else if (value instanceof byte[]) {
ps.setBytes(index++, (byte[]) value);
} else {
/** MySqlOracle 等驱动中通过 PreparedStatement.setObject 驱动会自动根据 value 内容进行转换
* 源码可参考 {{@link com.mysql.jdbc.PreparedStatement#setObject(int, Object)}
**/
ps.setObject(index++, value);
}
}
} else {
super.setParameters(ps);
}
}
/**
* OracleSqlServer 需要主动设置下 date 类型
* MySql 通过 setObject 后会自动转换具体查看 MySql 驱动源码
*
* @param ps PreparedStatement
* @param value date value
* @param index set to index
* @throws SQLException
*/
private void setDateParameter(PreparedStatement ps, Date value, int index) throws SQLException {
if (value instanceof java.sql.Date) {
ps.setDate(index, (java.sql.Date) value);
} else if (value instanceof java.sql.Timestamp) {
ps.setTimestamp(index, (java.sql.Timestamp) value);
} else {
ps.setTimestamp(index, new java.sql.Timestamp(value.getTime()));
}
}
}

View File

@ -0,0 +1,125 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.paginate;
import java.io.Serializable;
import java.util.List;
public class Page<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final int INIT_VALUE = -1;
private List<T> list; // list result of this page
private int pageNumber = INIT_VALUE; // page number
private int pageSize = INIT_VALUE; // result amount of this page
private long totalPage = INIT_VALUE; // total page
private long totalRow = INIT_VALUE; // total row
public static Page of(int pageNumber, int pageSize) {
return new Page(pageNumber, pageSize);
}
public static Page of(int pageNumber, int pageSize, long totalRow) {
return new Page(pageNumber, pageSize, totalRow);
}
public Page() {
}
public Page(int pageNumber, int pageSize) {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
}
public Page(int pageNumber, int pageSize, long totalRow) {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalRow = totalRow;
this.totalPage = totalRow % pageSize == 0 ? (totalRow / pageSize) : (totalRow / pageSize + 1);
}
public Page(List<T> list, int pageNumber, int pageSize, long totalRow) {
this.list = list;
this.pageNumber = pageNumber;
this.pageSize = pageSize;
this.totalRow = totalRow;
this.totalPage = totalRow % pageSize == 0 ? (totalRow / pageSize) : (totalRow / pageSize + 1);
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public int getPageNumber() {
return pageNumber;
}
public void setPageNumber(int pageNumber) {
this.pageNumber = pageNumber;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public long getTotalPage() {
return totalPage;
}
public void setTotalPage(long totalPage) {
this.totalPage = totalPage;
}
public long getTotalRow() {
return totalRow;
}
public void setTotalRow(long totalRow) {
this.totalRow = totalRow;
this.totalPage = totalRow % pageSize == 0 ? (totalRow / pageSize) : (totalRow / pageSize + 1);
}
public boolean isFirstPage() {
return pageNumber == 1;
}
public boolean isLastPage() {
return pageNumber >= totalPage;
}
@Override
public String toString() {
return "Page{" +
"pageNumber=" + pageNumber +
", pageSize=" + pageSize +
", totalPage=" + totalPage +
", totalRow=" + totalRow +
", list=" + list +
'}';
}
}

View File

@ -0,0 +1,292 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.provider;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.querywrapper.CPI;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class EntitySqlProvider {
/**
* 不让实例化使用静态方法的模式效率更高非静态方法每次都会实例化当前类
* 参考源码: {{@link org.apache.ibatis.builder.annotation.ProviderSqlSource#getBoundSql(Object)}
*/
private EntitySqlProvider() {
}
/**
* insert sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#insert(Object)
*/
public static String insert(Map params, ProviderContext context) {
Object entity = ProviderUtil.getEntity(params);
if (entity == null) {
throw FlexExceptions.wrap("entity can not be null.");
}
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
Object[] values = tableInfo.obtainInsertValues(entity);
ProviderUtil.setSqlArgs(params, values);
return DialectFactory.getDialect().forInsertEntity(tableInfo, entity);
}
/**
* insertBatchWithFirstEntityColumns sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#insertBatchWithFirstEntityColumns(List)
*/
public static String insertBatchWithFirstEntityColumns(Map params, ProviderContext context) {
List<Object> entities = ProviderUtil.getEntities(params);
if (CollectionUtil.isEmpty(entities)) {
throw FlexExceptions.wrap("entities can not be null or empty.");
}
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
Object[] values = new Object[0];
for (Object entity : entities) {
values = ArrayUtil.concat(values, tableInfo.obtainInsertValues(entity));
}
ProviderUtil.setSqlArgs(params, values);
return DialectFactory.getDialect().forInsertBatchWithFirstEntityColumns(tableInfo, entities);
}
/**
* deleteById sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#deleteById(Serializable)
*/
public static String deleteById(Map params, ProviderContext context) {
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
if (ArrayUtil.isEmpty(primaryValues)) {
throw FlexExceptions.wrap("primaryValues can not be null or empty.");
}
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
ProviderUtil.setSqlArgs(params, primaryValues);
return DialectFactory.getDialect().forDeleteEntityById(tableInfo);
}
/**
* deleteBatchByIds sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#deleteBatchByIds(Collection)
*/
public static String deleteBatchByIds(Map params, ProviderContext context) {
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
if (ArrayUtil.isEmpty(primaryValues)) {
throw FlexExceptions.wrap("primaryValues can not be null or empty.");
}
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
ProviderUtil.setSqlArgs(params, primaryValues);
return DialectFactory.getDialect().forDeleteEntityBatchById(tableInfo, primaryValues);
}
/**
* deleteByQuery sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#deleteByQuery(QueryWrapper)
*/
public static String deleteByQuery(Map params, ProviderContext context) {
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
if (queryWrapper == null) {
throw FlexExceptions.wrap("queryWrapper can not be null or empty.");
}
ProviderUtil.setSqlArgs(params, CPI.getValueArray(queryWrapper));
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
queryWrapper.from(tableInfo.getTableName());
return DialectFactory.getDialect().forDeleteByQuery(queryWrapper);
}
/**
* update sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#update(Object, boolean)
*/
public static String update(Map params, ProviderContext context) {
Object entity = ProviderUtil.getEntity(params);
if (entity == null) {
throw FlexExceptions.wrap("entity can not be null");
}
boolean ignoreNulls = ProviderUtil.isIgnoreNulls(params);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
Object[] updateValues = tableInfo.obtainUpdateValues(entity, ignoreNulls, false);
Object[] primaryValues = tableInfo.obtainPrimaryValues(entity);
ProviderUtil.setSqlArgs(params, ArrayUtil.concat(updateValues, primaryValues));
return DialectFactory.getDialect().forUpdateEntity(tableInfo, entity, ignoreNulls);
}
/**
* updateByQuery sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#updateByQuery(Object, QueryWrapper)
*/
public static String updateByQuery(Map params, ProviderContext context) {
Object entity = ProviderUtil.getEntity(params);
if (entity == null) {
throw FlexExceptions.wrap("entity can not be null");
}
boolean ignoreNulls = ProviderUtil.isIgnoreNulls(params);
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
Object[] values = tableInfo.obtainUpdateValues(entity, ignoreNulls, true);
ProviderUtil.setSqlArgs(params, ArrayUtil.concat(values, CPI.getValueArray(queryWrapper)));
return DialectFactory.getDialect().forUpdateEntityByQuery(tableInfo, entity, ignoreNulls, queryWrapper);
}
/**
* selectOneById sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#selectOneById(Serializable)
*/
public static String selectOneById(Map params, ProviderContext context) {
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
if (ArrayUtil.isEmpty(primaryValues)) {
throw FlexExceptions.wrap("primaryValues can not be null or empty.");
}
ProviderUtil.setSqlArgs(params, primaryValues);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
return DialectFactory.getDialect().forSelectOneEntityById(tableInfo);
}
/**
* selectListByIds sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#selectListByIds(Collection)
*/
public static String selectListByIds(Map params, ProviderContext context) {
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
if (ArrayUtil.isEmpty(primaryValues)) {
throw FlexExceptions.wrap("primaryValues can not be null or empty.");
}
ProviderUtil.setSqlArgs(params, primaryValues);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
return DialectFactory.getDialect().forSelectEntityListByIds(tableInfo, primaryValues);
}
/**
* selectListByQuery sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#selectListByQuery(QueryWrapper)
*/
public static String selectListByQuery(Map params, ProviderContext context) {
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
if (queryWrapper == null) {
throw FlexExceptions.wrap("queryWrapper can not be null.");
}
Object[] values = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, values);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
queryWrapper.from(tableInfo.getTableName());
return DialectFactory.getDialect().forSelectListByQuery(queryWrapper);
}
/**
* selectCountByQuery sql 构建
*
* @param params
* @param context
* @return sql
* @see com.mybatisflex.core.BaseMapper#selectCountByQuery(QueryWrapper)
*/
public static String selectCountByQuery(Map params, ProviderContext context) {
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
if (queryWrapper == null) {
throw FlexExceptions.wrap("queryWrapper can not be null.");
}
Object[] values = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, values);
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
queryWrapper.from(tableInfo.getTableName());
return DialectFactory.getDialect().forSelectCountByQuery(queryWrapper);
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.provider;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfos;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.builder.annotation.ProviderContext;
import java.util.Collection;
import java.util.List;
import java.util.Map;
class ProviderUtil {
private static final Object[] NULL_ARGS = new Object[0];
public static String getSqlString(Map params) {
return (String) params.get(FlexConsts.SQL);
}
public static void setSqlArgs(Map params, Object[] args) {
params.put(FlexConsts.SQL_ARGS, args);
}
public static String getTableName(Map params) {
return params.get(FlexConsts.TABLE_NAME).toString().trim();
}
public static String[] getPrimaryKeys(Map params) {
String primaryKey = (String) params.get(FlexConsts.PRIMARY_KEY);
if (StringUtil.isBlank(primaryKey)) {
throw FlexExceptions.wrap("primaryKey can not be null or blank.");
}
String[] primaryKeys = primaryKey.split(",");
for (int i = 0; i < primaryKeys.length; i++) {
primaryKeys[i] = primaryKeys[i].trim();
}
return primaryKeys;
}
public static Object[] getPrimaryValues(Map params) {
Object primaryValue = params.get(FlexConsts.PRIMARY_VALUE);
if (primaryValue == null) {
return NULL_ARGS;
}
if (primaryValue.getClass().isArray()) {
return (Object[]) primaryValue;
} else if (primaryValue instanceof Collection) {
return ((Collection<?>) primaryValue).toArray();
} else {
return new Object[]{primaryValue};
}
}
public static QueryWrapper getQueryWrapper(Map params) {
return (QueryWrapper) params.get(FlexConsts.QUERY);
}
public static Row getRow(Map params) {
return (Row) params.get(FlexConsts.ROW);
}
public static List<Row> getRows(Map params) {
return (List<Row>) params.get(FlexConsts.ROWS);
}
public static TableInfo getTableInfo(ProviderContext context){
return TableInfos.ofMapperClass(context.getMapperType());
}
public static Object getEntity(Map params) {
return params.get(FlexConsts.ENTITY);
}
public static List<Object> getEntities(Map params) {
return (List<Object>) params.get(FlexConsts.ENTITIES);
}
public static boolean isIgnoreNulls(Map params){
return params.containsKey(FlexConsts.IGNORE_NULLS) && (boolean) params.get(FlexConsts.IGNORE_NULLS);
}
}

View File

@ -0,0 +1,272 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.provider;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.querywrapper.CPI;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.row.RowMapper;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.CollectionUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class RowSqlProvider {
public static final String METHOD_RAW_SQL = "providerRawSql";
/**
* 不让实例化使用静态方法的模式效率更高非静态方法每次都会实例化当前类
* 参考源码: {{@link org.apache.ibatis.builder.annotation.ProviderSqlSource#getBoundSql(Object)}
*/
private RowSqlProvider() {
}
/**
* 执行原生 sql 的方法
*
* @param params
* @return sql
* @see RowMapper#insertBySql(String, Object...)
* @see RowMapper#deleteBySql(String, Object...)
* @see RowMapper#updateBySql(String, Object...)
*/
public static String providerRawSql(Map params) {
return ProviderUtil.getSqlString(params);
}
/**
* insertRow sql 构建
*
* @param params
* @return sql
* @see RowMapper#insertRow(String, Row)
*/
public static String insertRow(Map params) {
String tableName = ProviderUtil.getTableName(params);
Row row = ProviderUtil.getRow(params);
ProviderUtil.setSqlArgs(params, row.obtainModifyValues());
return DialectFactory.getDialect().forInsertRow(tableName, row);
}
/**
* insertBatch sql 构建
*
* @param params
* @return sql
* @see RowMapper#insertBatchWithFirstRowColumns(String, List)
*/
public static String insertBatchWithFirstRowColumns(Map params) {
String tableName = ProviderUtil.getTableName(params);
List<Row> rows = ProviderUtil.getRows(params);
if (rows == null || rows.isEmpty()) {
throw FlexExceptions.wrap("rows can not be null or empty.");
}
//让所有 row 的列顺序和值的数量与第条数据保持一致
Set<String> modifyAttrs = rows.get(0).obtainModifyAttrs();
rows.forEach(row -> row.keep(modifyAttrs));
Object[] values = new Object[]{};
for (Row row : rows) {
values = ArrayUtil.concat(values, row.obtainModifyValues());
}
ProviderUtil.setSqlArgs(params, values);
//sql: INSERT INTO `tb_table`(`name`, `sex`) VALUES (?, ?),(?, ?),(?, ?)
return DialectFactory.getDialect().forInsertBatchWithFirstRowColumns(tableName, rows);
}
/**
* deleteById sql 构建
*
* @param params
* @return sql
* @see RowMapper#deleteById(String, String, Object)
*/
public static String deleteById(Map params) {
String tableName = ProviderUtil.getTableName(params);
String[] primaryKeys = ProviderUtil.getPrimaryKeys(params);
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
if (primaryValues.length == 0) {
throw FlexExceptions.wrap("primaryValue can not be null");
} else {
ProviderUtil.setSqlArgs(params, primaryValues);
}
return DialectFactory.getDialect().forDeleteById(tableName, primaryKeys);
}
/**
* deleteBatchByIds sql 构建
*
* @param params
* @return sql
* @see RowMapper#deleteBatchByIds(String, String, Collection)
*/
public static String deleteBatchByIds(Map params) {
String tableName = ProviderUtil.getTableName(params);
String[] primaryKeys = ProviderUtil.getPrimaryKeys(params);
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
ProviderUtil.setSqlArgs(params, primaryValues);
return DialectFactory.getDialect().forDeleteBatchByIds(tableName, primaryKeys, primaryValues);
}
/**
* deleteByQuery sql 构建
*
* @param params
* @return sql
* @see RowMapper#deleteByQuery(String, QueryWrapper)
*/
public static String deleteByQuery(Map params) {
String tableName = ProviderUtil.getTableName(params);
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
Object[] valueArray = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, valueArray);
queryWrapper.from(tableName);
return DialectFactory.getDialect().forDeleteByQuery(queryWrapper);
}
/**
* updateById sql 构建
*
* @param params
* @return sql
* @see RowMapper#updateById(String, Row)
*/
public static String updateById(Map params) {
String tableName = ProviderUtil.getTableName(params);
Row row = ProviderUtil.getRow(params);
ProviderUtil.setSqlArgs(params, row.obtainModifyValuesAndPrimaryValues());
return DialectFactory.getDialect().forUpdateById(tableName, row);
}
/**
* updateByQuery sql 构建
*
* @param params
* @return sql
* @see RowMapper#updateByQuery(String, Row, QueryWrapper)
*/
public static String updateByQuery(Map params) {
String tableName = ProviderUtil.getTableName(params);
Row data = ProviderUtil.getRow(params);
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
Object[] modifyValues = data.obtainModifyValues();
Object[] valueArray = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, ArrayUtil.concat(modifyValues, valueArray));
return DialectFactory.getDialect().forUpdateByQuery(tableName, data, queryWrapper);
}
/**
* updateBatchById sql 构建
* mysql 等链接配置需要开启 allowMultiQueries=true
*
* @param params
* @return sql
* @see RowMapper#updateBatchById(String, List)
*/
public static String updateBatchById(Map params) {
String tableName = ProviderUtil.getTableName(params);
List<Row> rows = ProviderUtil.getRows(params);
if (CollectionUtil.isEmpty(rows)) {
throw FlexExceptions.wrap("rows can not be null or empty.");
}
Object[] values = new Object[0];
for (Row row : rows) {
values = ArrayUtil.concat(values, row.obtainModifyValuesAndPrimaryValues());
}
ProviderUtil.setSqlArgs(params, values);
return DialectFactory.getDialect().forUpdateBatchById(tableName, rows);
}
/**
* selectOneById sql 构建
*
* @param params
* @return sql
* @see RowMapper#selectOneById(String, String, Object)
*/
public static String selectOneById(Map params) {
String tableName = ProviderUtil.getTableName(params);
String[] primaryKeys = ProviderUtil.getPrimaryKeys(params);
Object[] primaryValues = ProviderUtil.getPrimaryValues(params);
ProviderUtil.setSqlArgs(params, primaryValues);
return DialectFactory.getDialect().forSelectOneById(tableName, primaryKeys, primaryValues);
}
/**
* selectListByQuery sql 构建
*
* @param params
* @return sql
* @see RowMapper#selectListByQuery(String, QueryWrapper)
*/
public static String selectListByQuery(Map params) {
String tableName = ProviderUtil.getTableName(params);
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
Object[] valueArray = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, valueArray);
queryWrapper.from(tableName);
return DialectFactory.getDialect().forSelectListByQuery(queryWrapper);
}
/**
* selectCountByQuery sql 构建
*
* @param params
* @return sql
* @see RowMapper#selectCountByQuery(String, QueryWrapper)
*/
public static String selectCountByQuery(Map params) {
String tableName = ProviderUtil.getTableName(params);
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
Object[] valueArray = CPI.getValueArray(queryWrapper);
ProviderUtil.setSqlArgs(params, valueArray);
return DialectFactory.getDialect().forSelectCountByQuery(queryWrapper);
}
}

View File

@ -0,0 +1,204 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class BaseQueryWrapper<T> implements Serializable {
protected List<QueryTable> queryTables;
protected String datasource ;
protected List<QueryColumn> selectColumns;
protected List<Join> joins;
protected List<QueryTable> joinTables;
protected QueryCondition whereQueryCondition;
protected List<QueryColumn> groupByColumns;
protected QueryCondition havingQueryCondition;
protected List<QueryOrderBy> orderBys;
protected Integer limitOffset;
protected Integer limitRows;
protected T addSelectColumn(QueryColumn queryColumn){
if (selectColumns == null){
selectColumns = new LinkedList<>();
}
selectColumns.add(queryColumn);
return (T) this;
}
protected T AddJoin(Join join){
if (joins == null){
joins = new LinkedList<>();
}
joins.add(join);
return (T) this;
}
protected T setWhereQueryCondition(QueryCondition queryCondition){
if (whereQueryCondition != null){
queryCondition.connect(whereQueryCondition,SqlConnector.AND);
}
whereQueryCondition = queryCondition;
return (T) this;
}
protected T addWhereQueryCondition(QueryCondition queryCondition,SqlConnector connector){
if (queryCondition != null) {
if (whereQueryCondition == null) {
whereQueryCondition = queryCondition;
} else {
whereQueryCondition.connect(queryCondition, connector);
}
}
return (T) this;
}
protected T addGroupByColumns(QueryColumn queryColumn){
if (groupByColumns == null){
groupByColumns = new LinkedList<>();
}
groupByColumns.add(queryColumn);
return (T) this;
}
protected T addHavingQueryCondition(QueryCondition queryCondition,SqlConnector connector){
if (havingQueryCondition == null){
havingQueryCondition = queryCondition;
}else {
havingQueryCondition.connect(queryCondition,connector);
}
return (T) this;
}
protected T addOrderBy(QueryOrderBy queryOrderBy){
if (orderBys == null){
orderBys = new LinkedList<>();
}
orderBys.add(queryOrderBy);
return (T) this;
}
protected void addJoinTable(QueryTable queryTable){
if (joinTables == null){
joinTables = new ArrayList<>();
}
joinTables.add(queryTable);
}
protected List<QueryTable> getQueryTables() {
return queryTables;
}
protected void setQueryTables(List<QueryTable> queryTables) {
this.queryTables = queryTables;
}
protected String getDatasource() {
return datasource;
}
protected void setDatasource(String datasource) {
this.datasource = datasource;
}
protected List<QueryColumn> getSelectColumns() {
return selectColumns;
}
protected void setSelectColumns(List<QueryColumn> selectColumns) {
this.selectColumns = selectColumns;
}
protected List<Join> getJoins() {
return joins;
}
protected void setJoins(List<Join> joins) {
this.joins = joins;
}
protected List<QueryTable> getJoinTables() {
return joinTables;
}
protected void setJoinTables(List<QueryTable> joinTables) {
this.joinTables = joinTables;
}
protected QueryCondition getWhereQueryCondition() {
return whereQueryCondition;
}
protected List<QueryColumn> getGroupByColumns() {
return groupByColumns;
}
protected void setGroupByColumns(List<QueryColumn> groupByColumns) {
this.groupByColumns = groupByColumns;
}
protected QueryCondition getHavingQueryCondition() {
return havingQueryCondition;
}
protected void setHavingQueryCondition(QueryCondition havingQueryCondition) {
this.havingQueryCondition = havingQueryCondition;
}
protected List<QueryOrderBy> getOrderBys() {
return orderBys;
}
protected void setOrderBys(List<QueryOrderBy> orderBys) {
this.orderBys = orderBys;
}
protected Integer getLimitOffset() {
return limitOffset;
}
protected void setLimitOffset(Integer limitOffset) {
this.limitOffset = limitOffset;
}
protected Integer getLimitRows() {
return limitRows;
}
protected void setLimitRows(Integer limitRows) {
this.limitRows = limitRows;
}
}

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.util.List;
/**
* 括号
*/
public class Brackets extends QueryCondition {
private QueryCondition childCondition;
public Brackets(QueryCondition childCondition) {
this.childCondition = childCondition;
}
@Override
public QueryCondition and(QueryCondition nextCondition) {
connectToChild(nextCondition, SqlConnector.AND);
return this;
}
@Override
public QueryCondition or(QueryCondition nextCondition) {
connectToChild(nextCondition, SqlConnector.OR);
return this;
}
protected void connectToChild(QueryCondition nextCondition, SqlConnector connector) {
childCondition.connect(nextCondition, connector);
}
@Override
public Object getValue() {
return checkEffective() ? WrapperUtil.getValues(childCondition) : null;
}
@Override
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
StringBuilder sql = new StringBuilder();
if (checkEffective()) {
String childSql = childCondition.toSql(queryTables, dialect);
QueryCondition effectiveBefore = getEffectiveBefore();
if (effectiveBefore != null) {
childSql = effectiveBefore.connector + "(" + childSql + ")";
}
sql.append(childSql);
}
if (this.next != null) {
return sql + next.toSql(queryTables, dialect);
}
return sql.toString();
}
@Override
public String toString() {
return "Brackets{" +
"childCondition=" + childCondition +
'}';
}
}

View File

@ -0,0 +1,130 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.util.List;
/**
* Cross Package Invoke
* 夸包调用工具类这么设计的原因是需要保证 QueryWrapper 方法对于用户的纯净性
* framework 又可以通过 CPI 来调用 QueryWrapper 的其他方法
*/
public class CPI {
public static Object[] getValueArray(QueryWrapper queryWrapper) {
return queryWrapper.getValueArray();
}
public static List<QueryTable> getQueryTables(QueryWrapper queryWrapper) {
return queryWrapper.getQueryTables();
}
public static void setQueryTable(QueryWrapper queryWrapper, List<QueryTable> queryTables) {
queryWrapper.setQueryTables(queryTables);
}
public static String getDatasource(QueryWrapper queryWrapper) {
return queryWrapper.getDatasource();
}
public static void setDatasource(QueryWrapper queryWrapper, String datasource) {
queryWrapper.setDatasource(datasource);
}
public static List<QueryColumn> getSelectColumns(QueryWrapper queryWrapper) {
return queryWrapper.getSelectColumns();
}
public static void setSelectColumns(QueryWrapper queryWrapper, List<QueryColumn> selectColumns) {
queryWrapper.setSelectColumns(selectColumns);
}
public static List<Join> getJoins(QueryWrapper queryWrapper) {
return queryWrapper.getJoins();
}
public static void setJoins(QueryWrapper queryWrapper, List<Join> joins) {
queryWrapper.setJoins(joins);
}
public static List<QueryTable> getJoinTables(QueryWrapper queryWrapper) {
return queryWrapper.getJoinTables();
}
public static void setJoinTables(QueryWrapper queryWrapper,List<QueryTable> joinTables) {
queryWrapper.setJoinTables(joinTables);
}
public static QueryCondition getWhereQueryCondition(QueryWrapper queryWrapper) {
return queryWrapper.getWhereQueryCondition();
}
public static List<QueryColumn> getGroupByColumns(QueryWrapper queryWrapper) {
return queryWrapper.getGroupByColumns();
}
public static void setGroupByColumns(QueryWrapper queryWrapper, List<QueryColumn> groupByColumns) {
queryWrapper.setGroupByColumns(groupByColumns);
}
public static QueryCondition getHavingQueryCondition(QueryWrapper queryWrapper) {
return queryWrapper.getHavingQueryCondition();
}
public static void setHavingQueryCondition(QueryWrapper queryWrapper, QueryCondition havingQueryCondition) {
queryWrapper.setHavingQueryCondition(havingQueryCondition);
}
public static List<QueryOrderBy> getOrderBys(QueryWrapper queryWrapper) {
return queryWrapper.getOrderBys();
}
public static void setOrderBys(QueryWrapper queryWrapper, List<QueryOrderBy> orderBys) {
queryWrapper.setOrderBys(orderBys);
}
public static Integer getLimitOffset(QueryWrapper queryWrapper) {
return queryWrapper.getLimitOffset();
}
public static void setLimitOffset(QueryWrapper queryWrapper, Integer limitOffset) {
queryWrapper.setLimitOffset(limitOffset);
}
public static Integer getLimitRows(QueryWrapper queryWrapper) {
return queryWrapper.getLimitRows();
}
public static void setLimitRows(QueryWrapper queryWrapper, Integer limitRows) {
queryWrapper.setLimitRows(limitRows);
}
public static String toConditionSql(QueryColumn queryColumn,List<QueryTable> queryTables, IDialect dialect) {
return queryColumn.toConditionSql(queryTables,dialect);
}
public static String toSelectSql(QueryColumn queryColumn,List<QueryTable> queryTables, IDialect dialect) {
return queryColumn.toSelectSql(queryTables,dialect);
}
}

View File

@ -0,0 +1,25 @@
package com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import java.util.List;
public class DistinctQueryColumn extends QueryColumn {
private List<QueryColumn> queryColumns;
public DistinctQueryColumn(QueryColumn... queryColumns) {
this.queryColumns = CollectionUtil.newArrayList(queryColumns);
}
@Override
public String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
if (CollectionUtil.isEmpty(queryTables)) {
return "";
}
return " DISTINCT " + StringUtil.join(",", queryColumns, queryColumn ->
queryColumn.toSelectSql(queryTables, dialect));
}
}

View File

@ -0,0 +1,62 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.util.StringUtil;
import java.util.List;
/**
* 数据库 聚合函数例如 count(id) max(account.age) 等等
*/
public class FunctionQueryColumn extends QueryColumn {
protected String fnName;
protected QueryColumn column;
public FunctionQueryColumn(String fnName, String column) {
this.fnName = fnName;
this.column = new QueryColumn(column);
}
public FunctionQueryColumn(String fnName, QueryColumn column) {
this.fnName = fnName;
this.column = column;
}
public String getFnName() {
return fnName;
}
public void setFnName(String fnName) {
this.fnName = fnName;
}
public QueryColumn getColumn() {
return column;
}
public void setColumn(QueryColumn column) {
this.column = column;
}
@Override
public String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
String sql = column.toSelectSql(queryTables, dialect);
return StringUtil.isBlank(sql) ? "" : fnName + "(" + sql + ")" + WrapperUtil.buildAsAlias(alias);
}
}

View File

@ -0,0 +1,92 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
/**
* @author michael yang (fuhai999@gmail.com)
* @Date: 2020/1/14
*/
public class Join implements Serializable {
private static final long serialVersionUID = 1L;
static final String TYPE_LEFT = " LEFT JOIN ";
static final String TYPE_RIGHT = " RIGHT JOIN ";
static final String TYPE_INNER = " INNER JOIN ";
static final String TYPE_FULL = " FULL JOIN ";
static final String TYPE_CROSS = " CROSS JOIN ";
private String type;
private QueryTable queryTable;
private QueryCondition on;
private boolean effective;
public Join(String type, String table, boolean when) {
this.type = type;
this.queryTable = new QueryTable(table);
this.effective = when;
}
public Join(String type, QueryWrapper queryWrapper, boolean when) {
this.type = type;
this.queryTable = new SelectQueryTable(queryWrapper);
this.effective = when;
}
QueryTable getQueryTable() {
return queryTable;
}
public void on(QueryCondition condition) {
this.on = condition;
}
public boolean checkEffective() {
return effective;
}
public void when(boolean when) {
this.effective = when;
}
public void when(Supplier<Boolean> fn) {
this.effective = fn.get();
}
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
//left join, right join, inner join ...
StringBuilder sql = new StringBuilder(type);
sql.append(queryTable.toSql(dialect));
//left join xxx as xxx2 on xxx2.id = xxx3.other
List<QueryTable> newQueryTables = new ArrayList<>(queryTables);
newQueryTables.add(queryTable);
sql.append(" ON ").append(on.toSql(newQueryTables, dialect));
return sql.toString();
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
/**
* @author michael yang (fuhai999@gmail.com)
* @Date: 2020/1/14
*/
public class Joiner<M> {
private M queryWrapper;
private Join join;
public Joiner(M queryWrapper, Join join) {
this.queryWrapper = queryWrapper;
this.join = join;
}
public Joiner<M> as(String alias) {
join.getQueryTable().as(alias);
return this;
}
public M on(String on) {
join.on(new StringQueryCondition(on));
return queryWrapper;
}
public M on(QueryCondition on) {
join.on(on);
return queryWrapper;
}
}

View File

@ -0,0 +1,49 @@
package com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.util.StringUtil;
import java.util.List;
/**
* 操作类型的操作
* 示例1and not ( id > 100 and name like %%)
*/
public class OperatorQueryCondition extends QueryCondition {
private String operator;
private QueryCondition child;
public OperatorQueryCondition(String operator, QueryCondition child) {
this.operator = operator;
this.child = child;
}
@Override
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
StringBuilder sql = new StringBuilder();
//检测是否生效
if (checkEffective()) {
String childSql = child.toSql(queryTables, dialect);
if (StringUtil.isNotBlank(childSql)) {
QueryCondition effectiveBefore = getEffectiveBefore();
if (effectiveBefore != null) {
sql.append(effectiveBefore.connector);
}
sql.append(operator).append("(").append(childSql).append(")");
}
}
if (this.next != null) {
return sql + next.toSql(queryTables, dialect);
}
return sql.toString();
}
@Override
public Object getValue() {
return WrapperUtil.getValues(child);
}
}

View File

@ -0,0 +1,51 @@
package com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.util.StringUtil;
import java.util.List;
/**
* 操作类型的操作
* 示例1and exist (select 1 from ... where ....)
* 示例2and not exist (select ... from ... where ....)
*/
public class OperatorSelectCondition extends QueryCondition {
//操作符例如 exist, not exist
private String operator;
private QueryWrapper queryWrapper;
public OperatorSelectCondition(String operator, QueryWrapper queryWrapper) {
this.operator = operator;
this.queryWrapper = queryWrapper;
}
@Override
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
StringBuilder sql = new StringBuilder();
//检测是否生效
if (checkEffective()) {
String childSql = dialect.buildSelectSql(queryWrapper);
if (StringUtil.isNotBlank(childSql)) {
QueryCondition effectiveBefore = getEffectiveBefore();
if (effectiveBefore != null) {
sql.append(effectiveBefore.connector);
}
sql.append(operator).append("(").append(childSql).append(")");
}
}
if (this.next != null) {
return sql + next.toSql(queryTables, dialect);
}
return sql.toString();
}
@Override
public Object getValue() {
return queryWrapper.getValueArray();
}
}

View File

@ -0,0 +1,325 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.table.TableDef;
import com.mybatisflex.core.util.StringUtil;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* 查询列描述的是一张表的字段
*/
public class QueryColumn implements Serializable {
protected QueryTable table;
protected String name;
protected String alias;
public QueryColumn() {
}
public QueryColumn(String name) {
this.name = name;
}
public QueryColumn(String tableName, String name) {
this.table = new QueryTable(tableName);
this.name = name;
}
public QueryColumn(TableDef tableDef, String name) {
this.table = new QueryTable(tableDef.getTableName());
this.name = name;
}
public QueryTable getTable() {
return table;
}
public void setTable(QueryTable table) {
this.table = table;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public QueryColumn as(String alias) {
QueryColumn newColumn = new QueryColumn();
newColumn.table = this.table;
newColumn.name = this.name;
newColumn.alias = alias;
return newColumn;
}
// query methods ///////
/**
* equals
*
* @param value
* @return
*/
public QueryCondition eq(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_EQUALS, value);
}
/**
* not equals !=
*
* @param value
* @return
*/
public QueryCondition ne(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_NOT_EQUALS, value);
}
public QueryCondition like(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_LIKE, "%" + value + "%");
}
public QueryCondition likeLeft(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_LIKE, "%" + value);
}
public QueryCondition likeRight(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_LIKE, value + "%");
}
/**
* 大于 greater than
*
* @param value
* @return
*/
public QueryCondition gt(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_GT, value);
}
/**
* 大于等于 greater or equal
*
* @param value
* @return
*/
public QueryCondition ge(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_GE, value);
}
/**
* 小于 less than
*
* @param value
* @return
*/
public QueryCondition lt(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_LT, value);
}
/**
* 小于等于 less or equal
*
* @param value
* @return
*/
public QueryCondition le(Object value) {
return QueryCondition.create(this, QueryCondition.LOGIC_LE, value);
}
/**
* IS NULL
*
* @param name
* @return
*/
public QueryCondition isNull(String name) {
return QueryCondition.create(this, QueryCondition.LOGIC_IS_NULL, null);
}
/**
* IS NOT NULL
*
* @param name
* @return
*/
public QueryCondition isNotNull(String name) {
return QueryCondition.create(this, QueryCondition.LOGIC_IS_NOT_NULL, null);
}
/**
* in arrays
*
* @param arrays
* @return
*/
public QueryCondition in(Object... arrays) {
//忽略 QueryWrapper.in("name", null) 的情况
if (arrays != null && arrays.length == 1 && arrays[0] == null) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, QueryCondition.LOGIC_IN, arrays);
}
/**
* in child select
* @param queryWrapper
* @return
*/
public QueryCondition in(QueryWrapper queryWrapper) {
return QueryCondition.create(this, QueryCondition.LOGIC_IN, queryWrapper);
}
/**
* in Collection
*
* @param collection
* @return
*/
public QueryCondition in(Collection<?> collection) {
if (collection != null && !collection.isEmpty()) {
return in(collection.toArray());
}
return QueryCondition.createEmpty();
}
/**
* not int arrays
*
* @param arrays
* @return
*/
public QueryCondition notIn(Object... arrays) {
//忽略 QueryWrapper.notIn("name", null) 的情况
if (arrays != null && arrays.length == 1 && arrays[0] == null) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, QueryCondition.LOGIC_NOT_IN, arrays);
}
/**
* not in Collection
*
* @param collection
* @return
*/
public QueryCondition notIn(Collection<?> collection) {
if (collection != null && !collection.isEmpty()) {
return notIn(collection.toArray());
}
return QueryCondition.createEmpty();
}
/**
* not in child select
* @param queryWrapper
*/
public QueryCondition notIn(QueryWrapper queryWrapper) {
return QueryCondition.create(this, QueryCondition.LOGIC_NOT_IN, queryWrapper);
}
/**
* between
* @param start
* @param end
*/
public QueryCondition between(Object start, Object end) {
return QueryCondition.create(this, QueryCondition.LOGIC_BETWEEN, new Object[]{start, end});
}
/**
* not between
*
* @param start
* @param end
*/
public QueryCondition notBetween(Object start, Object end) {
return QueryCondition.create(this, QueryCondition.LOGIC_NOT_BETWEEN, new Object[]{start, end});
}
////orrder by ////
public QueryOrderBy asc() {
QueryOrderBy queryOrderBy = new QueryOrderBy(this);
return queryOrderBy;
}
public QueryOrderBy desc() {
return new QueryOrderBy(this, "DESC");
}
public String wrap(IDialect dialect, String table, String column) {
if (StringUtil.isNotBlank(table)) {
return dialect.wrap(table) + "." + dialect.wrap(column);
} else {
return dialect.wrap(column);
}
}
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
String tableName = WrapperUtil.getRealTableName(queryTables, table);
return wrap(dialect, tableName, name);
}
String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
String tableName = WrapperUtil.getRealTableName(queryTables, table);
return wrap(dialect, tableName, name) + WrapperUtil.buildAsAlias(dialect.wrap(alias));
}
@Override
public String toString() {
return "QueryColumn{" +
"table=" + table +
", name='" + name + '\'' +
", alias='" + alias + '\'' +
'}';
}
}

View File

@ -0,0 +1,252 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.io.Serializable;
import java.util.List;
import java.util.function.Supplier;
public class QueryCondition implements Serializable {
public static final String LOGIC_LIKE = "LIKE";
public static final String LOGIC_GT = ">";
public static final String LOGIC_GE = ">=";
public static final String LOGIC_LT = "<";
public static final String LOGIC_LE = "<=";
public static final String LOGIC_EQUALS = "=";
public static final String LOGIC_NOT_EQUALS = "!=";
public static final String LOGIC_IS_NULL = "IS NULL";
public static final String LOGIC_IS_NOT_NULL = "IS NOT NULL";
public static final String LOGIC_IN = "IN";
public static final String LOGIC_NOT_IN = "NOT IN";
public static final String LOGIC_BETWEEN = "BETWEEN";
public static final String LOGIC_NOT_BETWEEN = "NOT BETWEEN";
protected QueryColumn column;
protected String logic;
protected Object value;
protected boolean effective = true;
//当前条件的上个条件
protected QueryCondition before;
//当前条件的上个下一个
protected QueryCondition next;
//两个条件直接的连接符
protected SqlConnector connector;
public static QueryCondition createEmpty() {
return new QueryCondition().when(false);
}
public static QueryCondition create(QueryColumn queryColumn, Object value) {
return create(queryColumn, LOGIC_EQUALS, value);
}
public static QueryCondition create(QueryColumn queryColumn, String logic, Object value) {
QueryCondition column = new QueryCondition();
column.setColumn(queryColumn);
column.setLogic(logic);
column.setValue(value);
return column;
}
public QueryCondition() {
}
public QueryColumn getColumn() {
return column;
}
public void setColumn(QueryColumn column) {
this.column = column;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getLogic() {
return logic;
}
public void setLogic(String logic) {
this.logic = logic;
}
/**
* 计算问号?的数量
*
* @return 问号的数量
*/
public int calculateQuestionMarkCount() {
if (LOGIC_IS_NULL.equals(logic)
|| LOGIC_IS_NOT_NULL.equals(logic)
|| value instanceof QueryColumn
|| value instanceof QueryWrapper) {
return 0;
}
//between, not between
else if (LOGIC_BETWEEN.equals(logic) || LOGIC_NOT_BETWEEN.equals(logic)) {
return 2;
}
//in, not in
else if (LOGIC_IN.equals(logic) || LOGIC_NOT_IN.equals(logic)) {
return calculateValueArrayCount();
}
//
else {
return 1;
}
}
private int calculateValueArrayCount() {
Object[] values = (Object[]) value;
int paramsCount = 0;
for (Object v : values) {
if (v.getClass() == int[].class) {
paramsCount += ((int[]) v).length;
} else if (v.getClass() == long[].class) {
paramsCount += ((long[]) v).length;
} else if (v.getClass() == short[].class) {
paramsCount += ((short[]) v).length;
} else {
paramsCount++;
}
}
return paramsCount;
}
public QueryCondition when(boolean effective) {
this.effective = effective;
return this;
}
public void when(Supplier<Boolean> fn) {
this.effective = fn.get();
}
public boolean checkEffective() {
return effective;
}
public QueryCondition and(QueryCondition nextCondition) {
return new Brackets(this).and(nextCondition);
}
public QueryCondition or(QueryCondition nextCondition) {
return new Brackets(this).or(nextCondition);
}
protected void connect(QueryCondition nextCondition, SqlConnector connector) {
if (this.next != null) {
this.next.connect(nextCondition, connector);
} else {
this.next = nextCondition;
this.connector = connector;
nextCondition.before = this;
}
}
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
StringBuilder sql = new StringBuilder();
//检测是否生效
if (checkEffective()) {
QueryCondition effectiveBefore = getEffectiveBefore();
if (effectiveBefore != null) {
sql.append(effectiveBefore.connector);
}
sql.append(getColumn().toConditionSql(queryTables, dialect));
sql.append(" ").append(logic).append(" ");
if (value instanceof QueryColumn) {
sql.append(((QueryColumn) value).toConditionSql(queryTables, dialect));
}
//子查询
else if (value instanceof QueryWrapper) {
sql.append("(").append(dialect.buildSelectSql((QueryWrapper) value)).append(")");
}
//正常查询构建问号
else {
appendQuestionMark(sql, calculateQuestionMarkCount());
}
}
if (this.next != null) {
return sql + next.toSql(queryTables, dialect);
}
return sql.toString();
}
protected QueryCondition getEffectiveBefore() {
if (before != null && before.checkEffective()) {
return before;
} else if (before != null) {
return before.getEffectiveBefore();
} else {
return null;
}
}
protected static void appendQuestionMark(StringBuilder sqlBuilder, int paramsCount) {
if (paramsCount == 1) {
sqlBuilder.append(" ? ");
}
//between, not between
else if (paramsCount == 2) {
sqlBuilder.append(" ? AND ? ");
}
//in, not in
else if (paramsCount > 0) {
sqlBuilder.append('(');
for (int i = 0; i < paramsCount; i++) {
sqlBuilder.append('?');
if (i != paramsCount - 1) {
sqlBuilder.append(',');
}
}
sqlBuilder.append(')');
} else {
// paramsCount == 0, ignore
}
}
@Override
public String toString() {
return "QueryCondition{" +
"column=" + column +
", logic='" + logic + '\'' +
", value=" + value +
", effective=" + effective +
'}';
}
}

View File

@ -0,0 +1,98 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
public class QueryMethods {
public static FunctionQueryColumn count() {
return new FunctionQueryColumn("COUNT", "*");
}
public static FunctionQueryColumn count(String column) {
return new FunctionQueryColumn("COUNT", column);
}
public static FunctionQueryColumn count(QueryColumn column) {
return new FunctionQueryColumn("COUNT", column);
}
public static FunctionQueryColumn max(String column) {
return new FunctionQueryColumn("MAX", column);
}
public static FunctionQueryColumn max(QueryColumn column) {
return new FunctionQueryColumn("MAX", column);
}
public static FunctionQueryColumn min(String column) {
return new FunctionQueryColumn("MIN", column);
}
public static FunctionQueryColumn min(QueryColumn column) {
return new FunctionQueryColumn("MIN", column);
}
public static FunctionQueryColumn avg(String column) {
return new FunctionQueryColumn("AVG", column);
}
public static FunctionQueryColumn avg(QueryColumn column) {
return new FunctionQueryColumn("AVG", column);
}
public static FunctionQueryColumn sum(String column) {
return new FunctionQueryColumn("SUM", column);
}
public static FunctionQueryColumn sum(QueryColumn column) {
return new FunctionQueryColumn("SUM", column);
}
public static DistinctQueryColumn distinct(QueryColumn... columns) {
return new DistinctQueryColumn(columns);
}
public static QueryCondition exist(QueryWrapper queryWrapper) {
return new OperatorSelectCondition(" EXIST ", queryWrapper);
}
public static QueryCondition notExist(QueryWrapper queryWrapper) {
return new OperatorSelectCondition(" NOT EXIST ", queryWrapper);
}
public static QueryCondition not(QueryCondition childCondition) {
return new OperatorQueryCondition(" NOT ", childCondition);
}
public static QueryCondition noCondition(){
return QueryCondition.createEmpty();
}
private static QueryWrapper newWrapper() {
return new QueryWrapper();
}
public static QueryWrapper select(QueryColumn... queryColumns) {
return newWrapper().select(queryColumns);
}
public static QueryWrapper selectOne() {
return select(new StringQueryColumn("1"));
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.io.Serializable;
import java.util.List;
/**
* 排序字段
*/
public class QueryOrderBy implements Serializable {
private QueryColumn queryColumn;
private String orderType = "ASC"; //asc desc
private boolean nullsFirst = false;
private boolean nullsLast = false;
protected QueryOrderBy() {
}
public QueryOrderBy(QueryColumn queryColumn, String orderType) {
this.queryColumn = queryColumn;
this.orderType = orderType;
}
public QueryOrderBy(QueryColumn queryColumn) {
this.queryColumn = queryColumn;
}
public QueryOrderBy nullsFirst() {
this.nullsFirst = true;
this.nullsLast = false;
return this;
}
public QueryOrderBy nullsLast() {
this.nullsFirst = false;
this.nullsLast = true;
return this;
}
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
String sql = queryColumn.toConditionSql(queryTables, dialect) + " " + orderType;
if (nullsFirst) {
sql = sql + " NULLS FIRST";
} else if (nullsLast) {
sql = sql + " NULLS LAST";
}
return sql;
}
}

View File

@ -0,0 +1,73 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.io.Serializable;
import java.util.Objects;
/**
* 查询列描述的是一张表的字段
*/
public class QueryTable implements Serializable {
protected String name;
protected String alias;
public QueryTable() {
}
public QueryTable(String name) {
this.name = name;
}
public QueryTable(String table, String alias) {
this.name = table;
this.alias = alias;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public QueryTable as(String alias) {
this.alias = alias;
return this;
}
boolean isSameTable(QueryTable table) {
return table != null && Objects.equals(name, table.name);
}
public String toSql(IDialect dialect) {
return dialect.wrap(name) + WrapperUtil.buildAsAlias(dialect.wrap(alias));
}
@Override
public String toString() {
return "QueryTable{" +
"name='" + name + '\'' +
", alias='" + alias + '\'' +
'}';
}
}

View File

@ -0,0 +1,315 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.table.TableDef;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.CollectionUtil;
import java.util.ArrayList;
import java.util.Map;
public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
public static QueryWrapper create() {
return new QueryWrapper();
}
public QueryWrapper select(QueryColumn... queryColumns) {
for (QueryColumn column : queryColumns) {
if (column != null) {
addSelectColumn(column);
}
}
return this;
}
public QueryWrapper from(TableDef tableDef) {
return from(tableDef.getTableName());
}
public QueryWrapper from(String table) {
return from(new QueryTable(table));
}
public QueryWrapper from(QueryTable table) {
if (CollectionUtil.isEmpty(queryTables)) {
queryTables = new ArrayList<>();
queryTables.add(table);
} else {
boolean contains = false;
for (QueryTable queryTable : queryTables) {
if (queryTable.isSameTable(table)) {
contains = true;
}
}
if (!contains) {
queryTables.add(table);
}
}
return this;
}
public QueryWrapper from(QueryWrapper queryWrapper) {
return from(new SelectQueryTable(queryWrapper));
}
public QueryWrapper as(String alias) {
if (CollectionUtil.isEmpty(queryTables)) {
throw new IllegalArgumentException("query table must not be empty.");
}
if (queryTables.size() > 1) {
throw FlexExceptions.wrap("QueryWrapper.as(...) only support 1 table");
}
queryTables.get(0).alias = alias;
return this;
}
public QueryWrapper where(QueryCondition queryCondition) {
this.setWhereQueryCondition(queryCondition);
return this;
}
public QueryWrapper where(String sql) {
this.setWhereQueryCondition(new StringQueryCondition(sql));
return this;
}
public QueryWrapper where(String sql, Object... params) {
this.setWhereQueryCondition(new StringQueryCondition(sql, params));
return this;
}
public QueryWrapper where(Map<String, Object> whereConditions) {
if (whereConditions != null) {
whereConditions.forEach((s, o) -> and(QueryCondition.create(new QueryColumn(s), o)));
}
return this;
}
public QueryWrapper and(QueryCondition queryCondition) {
return addWhereQueryCondition(queryCondition, SqlConnector.AND);
}
public QueryWrapper and(String sql) {
this.addWhereQueryCondition(new StringQueryCondition(sql), SqlConnector.AND);
return this;
}
public QueryWrapper and(String sql, Object... params) {
this.addWhereQueryCondition(new StringQueryCondition(sql, params), SqlConnector.AND);
return this;
}
public QueryWrapper or(QueryCondition queryCondition) {
return addWhereQueryCondition(queryCondition, SqlConnector.OR);
}
public Joiner<QueryWrapper> leftJoin(String table) {
return joining(Join.TYPE_LEFT, table, true);
}
public Joiner<QueryWrapper> leftJoinIf(String table, boolean condition) {
return joining(Join.TYPE_LEFT, table, condition);
}
public Joiner<QueryWrapper> leftJoin(TableDef table) {
return joining(Join.TYPE_LEFT, table.getTableName(), true);
}
public Joiner<QueryWrapper> leftJoinIf(TableDef table, boolean condition) {
return joining(Join.TYPE_LEFT, table.getTableName(), condition);
}
public Joiner<QueryWrapper> leftJoin(QueryWrapper table) {
return joining(Join.TYPE_LEFT, table, true);
}
public Joiner<QueryWrapper> leftJoinIf(QueryWrapper table, boolean condition) {
return joining(Join.TYPE_LEFT, table, condition);
}
public Joiner<QueryWrapper> rightJoin(String table) {
return joining(Join.TYPE_RIGHT, table, true);
}
public Joiner<QueryWrapper> rightJoinIf(String table, boolean condition) {
return joining(Join.TYPE_RIGHT, table, condition);
}
public Joiner<QueryWrapper> rightJoin(QueryWrapper table) {
return joining(Join.TYPE_RIGHT, table, true);
}
public Joiner<QueryWrapper> rightJoinIf(QueryWrapper table, boolean condition) {
return joining(Join.TYPE_RIGHT, table, condition);
}
public Joiner<QueryWrapper> innerJoin(String table) {
return joining(Join.TYPE_INNER, table, true);
}
public Joiner<QueryWrapper> innerJoinIf(String table, boolean condition) {
return joining(Join.TYPE_INNER, table, condition);
}
public Joiner<QueryWrapper> innerJoin(QueryWrapper table) {
return joining(Join.TYPE_INNER, table, true);
}
public Joiner<QueryWrapper> innerJoinIf(QueryWrapper table, boolean condition) {
return joining(Join.TYPE_INNER, table, condition);
}
public Joiner<QueryWrapper> fullJoin(String table) {
return joining(Join.TYPE_FULL, table, true);
}
public Joiner<QueryWrapper> fullJoinIf(String table, boolean condition) {
return joining(Join.TYPE_FULL, table, condition);
}
public Joiner<QueryWrapper> fullJoin(QueryWrapper table) {
return joining(Join.TYPE_FULL, table, true);
}
public Joiner<QueryWrapper> fullJoinIf(QueryWrapper table, boolean condition) {
return joining(Join.TYPE_FULL, table, condition);
}
public Joiner<QueryWrapper> crossJoin(String table) {
return joining(Join.TYPE_CROSS, table, true);
}
public Joiner<QueryWrapper> crossJoinIf(String table, boolean condition) {
return joining(Join.TYPE_CROSS, table, condition);
}
public Joiner<QueryWrapper> crossJoin(QueryWrapper table) {
return joining(Join.TYPE_CROSS, table, true);
}
public Joiner<QueryWrapper> crossJoinIf(QueryWrapper table, boolean condition) {
return joining(Join.TYPE_CROSS, table, condition);
}
protected Joiner<QueryWrapper> joining(String type, String table, boolean condition) {
Join join = new Join(type, table, condition);
addJoinTable(join.getQueryTable());
return new Joiner<>(AddJoin(join), join);
}
protected Joiner<QueryWrapper> joining(String type, QueryWrapper queryWrapper, boolean condition) {
Join join = new Join(type, queryWrapper, condition);
addJoinTable(join.getQueryTable());
return new Joiner<>(AddJoin(join), join);
}
public QueryWrapper groupBy(String name) {
addGroupByColumns(new QueryColumn(name));
return this;
}
public QueryWrapper groupBy(String... names) {
for (String name : names) {
groupBy(name);
}
return this;
}
public QueryWrapper groupBy(QueryColumn column) {
addGroupByColumns(column);
return this;
}
public QueryWrapper groupBy(QueryColumn... columns) {
for (QueryColumn column : columns) {
groupBy(column);
}
return this;
}
public QueryWrapper having(QueryCondition queryCondition) {
addHavingQueryCondition(queryCondition, SqlConnector.AND);
return this;
}
public QueryWrapper orderBy(QueryOrderBy... orderBys) {
for (QueryOrderBy queryOrderBy : orderBys) {
addOrderBy(queryOrderBy);
}
return this;
}
public QueryWrapper orderBy(String... orderBys) {
for (String queryOrderBy : orderBys) {
addOrderBy(new StringQueryOrderBy(queryOrderBy));
}
return this;
}
public QueryWrapper limit(Integer rows) {
setLimitRows(rows);
return this;
}
public QueryWrapper offset(Integer offset) {
setLimitOffset(offset);
return this;
}
public QueryWrapper limit(Integer offset, Integer rows) {
setLimitOffset(offset);
setLimitRows(rows);
return this;
}
public QueryWrapper datasource(String datasource) {
setDatasource(datasource);
return this;
}
/**
* 获取 queryWrapper 的参数
* 在构建 sql 的时候需要保证 where having 的前面
*/
Object[] getValueArray() {
Object[] whereValues = WrapperUtil.getValues(whereQueryCondition);
Object[] havingValues = WrapperUtil.getValues(havingQueryCondition);
return ArrayUtil.concat(whereValues, havingValues);
}
}

View File

@ -0,0 +1,37 @@
package com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.util.StringUtil;
/**
* 查询的 table
* 实例1用于构建 select * from (select ...) 中的第二个 select
* 实例2用于构建 left join (select ...) 中的 select
*/
public class SelectQueryTable extends QueryTable {
private QueryWrapper queryWrapper;
public SelectQueryTable(QueryWrapper queryWrapper) {
super();
this.queryWrapper = queryWrapper;
}
public QueryWrapper getQueryWrapper() {
return queryWrapper;
}
public void setQueryWrapper(QueryWrapper queryWrapper) {
this.queryWrapper = queryWrapper;
}
@Override
public String toSql(IDialect dialect) {
String sql = dialect.buildSelectSql(queryWrapper);
if (StringUtil.isNotBlank(alias)) {
return "(" + sql + ") AS " + dialect.wrap(alias);
} else {
return sql;
}
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
public enum SqlConnector {
AND(" AND "),
// AND_NOT(" AND NOT "),
// AND_EXISTS(" AND EXISTS "),
// AND_NOT_EXISTS(" AND NOT EXISTS "),
OR(" OR "),
// OR_NOT(" OR NOT "),
// OR_EXISTS(" OR EXISTS "),
// OR_NOT_EXISTS(" OR NOT EXISTS "),
// NOT(" NOT "),
;
private String value;
SqlConnector(String value) {
this.value = value;
}
@Override
public String toString() {
return value;
}
}

View File

@ -0,0 +1,51 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.util.List;
/**
* 自定义字符串列用于扩展
*/
public class StringQueryColumn extends QueryColumn {
protected String content;
public StringQueryColumn(String content) {
this.content = content;
}
@Override
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
return content;
}
@Override
String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
return content;
}
@Override
public String toString() {
return "StringQueryColumn{" +
"content='" + content + '\'' +
'}';
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.util.List;
/**
* 自定义字符串列用于扩展
*/
public class StringQueryCondition extends QueryCondition {
protected String sqlContent;
public StringQueryCondition(String content) {
this.sqlContent = content;
}
public StringQueryCondition(String content, Object... paras) {
this.sqlContent = content;
this.setValue(paras);
}
@Override
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
StringBuilder sql = new StringBuilder();
//检测是否生效
if (checkEffective()) {
QueryCondition effectiveBefore = getEffectiveBefore();
if (effectiveBefore != null) {
sql.append(effectiveBefore.connector);
}
sql.append(" ").append(sqlContent).append(" ");
}
if (this.next != null) {
return sql + next.toSql(queryTables, dialect);
}
return sql.toString();
}
}

View File

@ -0,0 +1,38 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.dialect.IDialect;
import java.util.List;
/**
* 排序字段
*/
public class StringQueryOrderBy extends QueryOrderBy {
private String orderBy;
public StringQueryOrderBy(String orderBy) {
this.orderBy = orderBy;
}
@Override
public String toSql(List<QueryTable> queryTables, IDialect dialect) {
return orderBy;
}
}

View File

@ -0,0 +1,123 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.querywrapper;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
class WrapperUtil {
static String buildAsAlias(String alias) {
return StringUtil.isBlank(alias) ? "" : " AS " + alias;
}
static final Object[] NULL_PARA_ARRAY = new Object[0];
static Object[] getValues(QueryCondition condition) {
if (condition == null) {
return NULL_PARA_ARRAY;
}
List<Object> paras = new LinkedList<>();
getValues(condition, paras);
return paras.isEmpty() ? NULL_PARA_ARRAY : paras.toArray();
}
private static void getValues(QueryCondition condition, List<Object> paras) {
if (condition == null) {
return;
}
Object value = condition.getValue();
if (value != null) {
if (value.getClass().isArray()) {
Object[] values = (Object[]) value;
for (Object v : values) {
if (v.getClass() == int[].class) {
addAll(paras, (int[]) v);
} else if (v.getClass() == long[].class) {
addAll(paras, (long[]) v);
} else if (v.getClass() == short[].class) {
addAll(paras, (short[]) v);
} else {
paras.add(v);
}
}
} else if (value instanceof QueryWrapper) {
Object[] valueArray = ((QueryWrapper) value).getValueArray();
paras.addAll(Arrays.asList(valueArray));
} else {
paras.add(value);
}
}
getValues(condition.next, paras);
}
private static void addAll(List<Object> paras, int[] ints) {
for (int i : ints) {
paras.add(i);
}
}
private static void addAll(List<Object> paras, long[] longs) {
for (long i : longs) {
paras.add(i);
}
}
private static void addAll(List<Object> paras, short[] shorts) {
for (short i : shorts) {
paras.add(i);
}
}
public static String getRealTableName(List<QueryTable> queryTables, QueryTable queryTable) {
QueryTable realTable = getRealTable(queryTables, queryTable);
if (realTable == null) {
return "";
}
return StringUtil.isNotBlank(realTable.alias) ? realTable.alias : realTable.name;
}
public static QueryTable getRealTable(List<QueryTable> queryTables, QueryTable queryTable) {
if (CollectionUtil.isEmpty(queryTables)) {
return queryTable;
}
if (queryTable == null && queryTables.size() == 1) {
return queryTables.get(0);
}
for (QueryTable table : queryTables) {
if (table.isSameTable(queryTable)) {
return table;
}
}
return queryTable;
}
}

View File

@ -0,0 +1,175 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.row;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.util.MapUtil;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 针对 RowMapper 的静态方法进行封装
*/
public class Db {
private static final Map<String, RowMapperInvoker> INVOKER_MAP = new ConcurrentHashMap<>();
static RowMapperInvoker defaultRowMapperInvoker;
public static RowMapperInvoker invoker() {
if (defaultRowMapperInvoker == null) {
SqlSessionFactory sqlSessionFactory = FlexGlobalConfig.getDefaultConfig().getSqlSessionFactory();
defaultRowMapperInvoker = new RowMapperInvoker(sqlSessionFactory);
}
return defaultRowMapperInvoker;
}
public static RowMapperInvoker invoker(String environmentId) {
return MapUtil.computeIfAbsent(INVOKER_MAP, environmentId, key -> {
SqlSessionFactory sqlSessionFactory = FlexGlobalConfig.getConfig(key).getSqlSessionFactory();
return new RowMapperInvoker(sqlSessionFactory);
});
}
public static int insertBySql(String sql, Object... args) {
return invoker().insertBySql(sql, args);
}
public static int insertRow(String tableName, Row row) {
return invoker().insertRow(tableName, row);
}
public static int[] insertBatch(String tableName, Collection<Row> rows) {
return insertBatch(tableName, rows, rows.size());
}
public static int[] insertBatch(String tableName, Collection<Row> rows, int batchSize) {
return invoker().insertBatch(tableName, rows, batchSize);
}
public static int insertBatchWithFirstRowColumns(String tableName, List<Row> rows) {
return invoker().insertBatchWithFirstRowColumns(tableName, rows);
}
public static int deleteBySql(String sql, Object... args) {
return invoker().deleteBySql(sql, args);
}
public static int deleteById(String tableName, Row row) {
return invoker().deleteById(tableName, row);
}
public static int deleteById(String tableName, String primaryKey, Object id) {
return invoker().deleteById(tableName, primaryKey, id);
}
public static int deleteBatchByIds(String tableName, String primaryKey, Collection<?> ids) {
return invoker().deleteBatchByIds(tableName, primaryKey, ids);
}
public static int deleteByByMap(String tableName, Map<String, Object> whereColumns) {
return invoker().deleteByByMap(tableName, whereColumns);
}
public static int deleteByQuery(String tableName, QueryWrapper queryWrapper) {
return invoker().deleteByQuery(tableName, queryWrapper);
}
public static int updateBySql(String sql, Object... args) {
return invoker().updateBySql(sql, args);
}
public static int updateById(String tableName, Row row) {
return invoker().updateById(tableName, row);
}
public static int updateByMap(String tableName, Row data, Map<String, Object> whereColumns) {
return invoker().updateByMap(tableName, data, whereColumns);
}
public static int updateByQuery(String tableName, Row data, QueryWrapper queryWrapper) {
return invoker().updateByQuery(tableName, data, queryWrapper);
}
public static int updateBatchById(String tableName, List<Row> rows) {
return invoker().updateBatchById(tableName, rows);
}
public static Row selectOneBySql(String sql, Object... args) {
return invoker().selectOneBySql(sql, args);
}
public static Row selectOneById(String tableName, Row row) {
return invoker().selectOneById(tableName, row);
}
public static Row selectOneById(String tableName, String primaryKey, Object id) {
return invoker().selectOneById(tableName, primaryKey, id);
}
public static Row selectOneByMap(String tableName, Map whereColumns) {
return invoker().selectOneByMap(tableName, whereColumns);
}
public static Row selectOneByQuery(String tableName, QueryWrapper queryWrapper) {
return invoker().selectOneByQuery(tableName, queryWrapper);
}
public static List<Row> selectListBySql(String sql, Object... args) {
return invoker().selectListBySql(sql, args);
}
public static List<Row> selectListByMap(String tableName, Map<String, Object> whereColumns) {
return invoker().selectListByMap(tableName, whereColumns);
}
public static List<Row> selectListByQuery(String tableName, QueryWrapper queryWrapper) {
return invoker().selectListByQuery(tableName, queryWrapper);
}
public static List<Row> selectAll(String tableName) {
return invoker().selectAll(tableName);
}
public static Object selectObject(String sql, Object... args) {
return invoker().selectObject(sql, args);
}
public static List<Object> selectObjectList(String sql, Object... args) {
return invoker().selectObjectList(sql, args);
}
public static long selectCount(String sql, Object... args) {
return invoker().selectCount(sql, args);
}
public static long selectCountByQuery(String tableName, QueryWrapper queryWrapper) {
return invoker().selectCountByQuery(tableName, queryWrapper);
}
public static Page<Row> paginate(String tableName, int pageNumber, int pageSize, QueryWrapper queryWrapper) {
return invoker().paginate(tableName, pageNumber, pageSize, queryWrapper);
}
public static Page<Row> paginate(String tableName, Page<Row> page, QueryWrapper queryWrapper) {
return invoker().paginate(tableName, page, queryWrapper);
}
}

View File

@ -0,0 +1,182 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.row;
import com.mybatisflex.core.javassist.ModifyAttrsRecord;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfos;
import com.mybatisflex.core.util.ArrayUtil;
import java.util.HashMap;
import java.util.Set;
public class Row extends HashMap<String, Object> implements ModifyAttrsRecord {
private static final Object[] NULL_ARGS = new Object[0];
//主键多个主键用英文逗号隔开
private RowKey[] primaryKeys;
public static Row of(String key, Object value) {
Row row = new Row();
return row.set(key, value);
}
public static Row ofKey(String primaryKey, Object value) {
Row row = new Row();
String[] primaryKeyStrings = primaryKey.split(",");
row.primaryKeys = new RowKey[primaryKeyStrings.length];
for (int i = 0; i < primaryKeyStrings.length; i++) {
row.primaryKeys[i] = RowKey.of(primaryKeyStrings[i].trim());
}
if (primaryKeyStrings.length > 0 && !value.getClass().isArray()) {
throw new IllegalArgumentException("the type of value[\"" + value + "\"] must be an array.");
}
if (primaryKeyStrings.length == 1) {
row.put(primaryKey.trim(), value);
} else {
Object[] values = (Object[]) value;
for (int i = 0; i < primaryKeyStrings.length; i++) {
row.put(primaryKeyStrings[i].trim(), values[i]);
}
}
return row;
}
public static Row ofKey(RowKey ...rowKeys) {
Row row = new Row();
row.primaryKeys = rowKeys;
return row;
}
public static Row ofKey(RowKey rowKey, Object value) {
Row row = new Row();
row.primaryKeys = new RowKey[]{rowKey};
row.put(rowKey.keyColumn, value);
return row;
}
public static Row ofKey(RowKey[] rowKeys, Object[] value) {
Row row = new Row();
row.primaryKeys = rowKeys;
for (int i = 0; i < rowKeys.length; i++) {
row.put(rowKeys[i].keyColumn, value[i]);
}
return row;
}
public Row set(String key, Object value) {
put(key, value);
boolean isPrimaryKey = false;
if (this.primaryKeys != null){
for (RowKey rowKey : primaryKeys) {
if (rowKey.getKeyColumn().equals(key)){
isPrimaryKey = true;
break;
}
}
}
if (!isPrimaryKey){
addModifyAttr(key);
}
return this;
}
public Object get(Object key, Object defaultValue) {
Object result = super.get(key);
return result != null ? result : defaultValue;
}
@Override
public Object remove(Object key) {
removeModifyAttr(key.toString());
return super.remove(key);
}
public <T> T toEntity(Class<T> entityClass) {
TableInfo tableInfo = TableInfos.ofEntityClass(entityClass);
return tableInfo.newInstanceByRow(this);
}
public void keep(Set<String> attrs) {
if (attrs == null) {
throw new NullPointerException("attrs is null.");
}
clearModifyFlag();
modifyAttrs.addAll(attrs);
}
/**
* 获取修改的值值需要保持顺序
* 返回的内容不包含主键的值
*
* @return values 数组
*/
public Object[] obtainModifyValues() {
Object[] values = new Object[modifyAttrs.size()];
int index = 0;
for (String modifyAttr : modifyAttrs) {
values[index++] = get(modifyAttr);
}
return values;
}
public String[] obtainsPrimaryKeyStrings() {
String[] returnKeys = new String[primaryKeys.length];
for (int i = 0; i < primaryKeys.length; i++) {
returnKeys[i] = primaryKeys[i].keyColumn;
}
return returnKeys;
}
public RowKey[] obtainsPrimaryKeys() {
return this.primaryKeys;
}
public Object[] obtainsPrimaryValues() {
if (ArrayUtil.isEmpty(primaryKeys)) {
return NULL_ARGS;
}
Object[] values = new Object[primaryKeys.length];
for (int i = 0; i < primaryKeys.length; i++) {
values[i] = get(primaryKeys[i].keyColumn);
}
return values;
}
public Object[] obtainModifyValuesAndPrimaryValues() {
return ArrayUtil.concat(obtainModifyValues(), obtainsPrimaryValues());
}
}

View File

@ -0,0 +1,149 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.row;
import com.mybatisflex.core.enums.KeyType;
/**
* row 的主键策略
*/
public class RowKey {
/**
* 自增 ID
*/
public static final RowKey ID_AUTO = new UnModifiableRowKey("id",KeyType.Auto,null,false);
/**
* UUID ID
*/
public static final RowKey ID_UUID = new UnModifiableRowKey("id",KeyType.Generator,"uuid",true);
public static RowKey of(String keyColumn) {
RowKey rowKey = new RowKey();
rowKey.keyColumn = keyColumn;
return rowKey;
}
public static RowKey of(String keyColumn, KeyType keyType) {
RowKey rowKey = new RowKey();
rowKey.keyColumn = keyColumn;
rowKey.keyType = keyType;
return rowKey;
}
public static RowKey of(String keyColumn, KeyType keyType, String keyTypeValue) {
RowKey rowKey = new RowKey();
rowKey.keyColumn = keyColumn;
rowKey.keyType = keyType;
rowKey.value = keyTypeValue;
return rowKey;
}
public static RowKey of(String keyColumn, KeyType keyType, String keyTypeValue, boolean before) {
RowKey rowKey = new RowKey();
rowKey.keyColumn = keyColumn;
rowKey.keyType = keyType;
rowKey.value = keyTypeValue;
rowKey.before = before;
return rowKey;
}
/**
* 主键字段
*/
protected String keyColumn;
/**
* 主键类型
*/
protected KeyType keyType = KeyType.Auto;
/**
* 主键类型为 Sequence Generator 时的对应的内容
*/
protected String value;
/**
* 是否前执行
*/
protected boolean before = true;
public String getKeyColumn() {
return keyColumn;
}
public void setKeyColumn(String keyColumn) {
this.keyColumn = keyColumn;
}
public KeyType getKeyType() {
return keyType;
}
public void setKeyType(KeyType keyType) {
this.keyType = keyType;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public boolean isBefore() {
return before;
}
public void setBefore(boolean before) {
this.before = before;
}
static class UnModifiableRowKey extends RowKey{
public UnModifiableRowKey(String keyColumn, KeyType keyType, String value, boolean before) {
super();
this.keyColumn = keyColumn;
this.keyType = keyType;
this.value = value;
this.before = before;
}
@Override
public void setKeyColumn(String keyColumn) {
throw new UnsupportedOperationException("unsupported setKeyColumn!");
}
@Override
public void setKeyType(KeyType keyType) {
throw new UnsupportedOperationException("unsupported setKeyType!");
}
@Override
public void setValue(String value) {
throw new UnsupportedOperationException("unsupported setValue!");
}
@Override
public void setBefore(boolean before) {
throw new UnsupportedOperationException("unsupported setBefore!");
}
}
}

View File

@ -0,0 +1,446 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.row;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.exception.FlexExceptions;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.provider.RowSqlProvider;
import com.mybatisflex.core.querywrapper.CPI;
import com.mybatisflex.core.querywrapper.QueryColumn;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.exceptions.TooManyResultsException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public interface RowMapper {
//////insert //////
/**
* 执行 insert sql 语句
*
* @param sql insert sql 语句
* @param args 参数
* @return 执行影响的行数
* @see Db#insertBySql(String, Object...)
*/
@InsertProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
int insertBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 插入 row 到数据表
*
* @param tableName 表名
* @param row 数据内容当设置有主键时主键会自动填充
* @return 执行影响的行数
* @see RowSqlProvider#insertRow(Map)
*/
@InsertProvider(value = RowSqlProvider.class, method = "insertRow")
int insertRow(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row);
/**
* 批量插入 rows 到数据表
* <p>
* 注意批量插入中只会根据第一条 row 数据来构建 Sql 插入字段若每条数据字段不一致可能造成个别字段无法插入的情况
*
* @param tableName 表名
* @param rows 数据内容当设置有主键时主键会自动填充
* @return 执行影响的行数
* @see RowSqlProvider#insertBatchWithFirstRowColumns(Map)
*/
@InsertProvider(value = RowSqlProvider.class, method = "insertBatchWithFirstRowColumns")
int insertBatchWithFirstRowColumns(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROWS) List<Row> rows);
/////// delete /////
/**
* 执行 delete sql 语言
*
* @param sql delete sql 语句
* @param args 参数
* @return 执行影响的行数
*/
@DeleteProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
int deleteBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 根据 id 删除数据
*
* @param tableName 表名
* @param row id 值的数据可以通过 {@link Row#ofKey(String, Object)} 来创建
* @return 执行影响的行数
*/
default int deleteById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row) {
return deleteById(tableName, StringUtil.join(",", row.obtainsPrimaryKeyStrings()), row.obtainsPrimaryValues());
}
/**
* 根据 id 删除数据
*
* @param tableName 表名
* @param primaryKey 主键多个主键用英文逗号隔开
* @param id 数据多个主键时传入数组例如 new Object[]{1,2}
* @return 执行影响的行数
* @see RowSqlProvider#deleteById(Map)
*/
@DeleteProvider(value = RowSqlProvider.class, method = "deleteById")
int deleteById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Object id);
/**
* 根据 多个 id 值删除多条数据
*
* @param tableName 表名
* @param primaryKey 主键
* @param ids id 的集合
* @return 执行影响的行数
* @see RowSqlProvider#deleteBatchByIds(Map)
*/
@DeleteProvider(value = RowSqlProvider.class, method = "deleteBatchByIds")
int deleteBatchByIds(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Collection<?> ids);
/**
* 根据 map 来构建条件删除数据
* <p>
* <b>注意</b>
* 删除 map 不允许为 null 或者 空内容否则可能造成数据全部删除的情况
* 若想删除全部数据请执行 {@link RowMapper#deleteBySql(String, Object...)} 方法
*
* @param tableName 表名
* @param whereConditions 条件通过 map key:value 来构建都是 and 的关系
* @return 执行影响的行数
*/
default int deleteByByMap(String tableName, Map<String, Object> whereConditions) {
if (whereConditions == null || whereConditions.isEmpty()) {
throw FlexExceptions.wrap("whereConditions can not be null or empty.");
}
return deleteByQuery(tableName, new QueryWrapper().where(whereConditions));
}
/**
* 根据 queryWrapper 构建 where 条件来删除数据
*
* @param tableName 表名
* @param queryWrapper queryWrapper
* @return 执行影响的行数
* @see RowSqlProvider#deleteByQuery(Map)
*/
@DeleteProvider(value = RowSqlProvider.class, method = "deleteByQuery")
int deleteByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
////////update ////
/**
* 执行 update sql 语句
*
* @param sql sql 语句
* @param args 参数内容
* @return 执行影响的行数
*/
@UpdateProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
int updateBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 根据主键来更新数据
*
* @param tableName 表名
* @param row 数据其必须包含主键数据列名和值
* @return 执行影响的行数
* @see RowSqlProvider#updateById(Map)
*/
@UpdateProvider(value = RowSqlProvider.class, method = "updateById")
int updateById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row);
/**
* 根据 map 来更新数据
*
* @param tableName 表名
* @param data 要更新的数据
* @param whereConditions 条件通过 map key:value 来构建都是 and 的关系
* @return 执行影响的行数
*/
default int updateByMap(String tableName, Row data, Map<String, Object> whereConditions) {
return updateByQuery(tableName, data, new QueryWrapper().where(whereConditions));
}
/**
* 根据 queryWrapper 来构建 where 条件更新数据
*
* @param tableName 表名
* @param data 更新数据
* @param queryWrapper queryWrapper
* @return 执行影响的行数
* @see RowSqlProvider#updateByQuery(Map)
*/
@UpdateProvider(value = RowSqlProvider.class, method = "updateByQuery")
int updateByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row data, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 根据主键来批量更新数据
* 注意
* 1此方法需要在 mysql 等链接配置需要开启 allowMultiQueries=true
* 2更新成功返回的结果也可能为 0
*
* @param tableName 表名
* @param rows 数据其必须包含主键数据列名和值
* @return 执行影响的行数
* @see RowSqlProvider#updateBatchById(Map)
*/
@UpdateProvider(value = RowSqlProvider.class, method = "updateBatchById")
int updateBatchById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROWS) List<Row> rows);
///////select /////
/**
* 通过原生 SQL 查询 1 条数据要求数据必须返回 1 条内容否则会报错
*
* @param sql select sql 语句
* @param args 参数
* @return 返回一条数据
*/
default Row selectOneBySql(String sql, Object... args) {
List<Row> rows = selectListBySql(sql, args);
if (rows == null || rows.isEmpty()) {
return null;
} else if (rows.size() == 1) {
return rows.get(0);
} else {
/** 当返回多条数据时抛出异常, 保持和 Mybatis DefaultSqlSession 的统一逻辑,
* see: {@link org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(String, Object)} **/
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOneBySql(), but found: " + rows.size());
}
}
/**
* 通过主键来查询数据
*
* @param tableName 表名
* @param row 主键和ID的描述通过 {@link Row#ofKey(String, Object)} 来进行构建
* @return 返回一条数据或者 null
*/
default Row selectOneById(String tableName, Row row) {
return selectOneById(tableName, StringUtil.join(",", row.obtainsPrimaryKeyStrings()), row.obtainsPrimaryValues());
}
/**
* 根据主键来查询数据
*
* @param tableName 表名
* @param primaryKey 主键
* @param id id
* @return row or null
* @see RowSqlProvider#selectOneById(Map)
*/
@SelectProvider(value = RowSqlProvider.class, method = "selectOneById")
Row selectOneById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.PRIMARY_KEY) String primaryKey, @Param(FlexConsts.PRIMARY_VALUE) Object id);
/**
* 根据 map 组成的条件查询 1 条数据
*
* @param tableName
* @param whereConditions
* @return
*/
default Row selectOneByMap(String tableName, Map whereConditions) {
return selectOneByQuery(tableName, new QueryWrapper().where(whereConditions));
}
/**
* 根据 queryWrapper 来查询 1 条数据
*
* @param tableName 表名
* @param queryWrapper queryWrapper
* @return row or null
*/
default Row selectOneByQuery(String tableName, QueryWrapper queryWrapper) {
List<Row> rows = selectListByQuery(tableName, queryWrapper.limit(1));
if (rows == null || rows.isEmpty()) {
return null;
} else {
return rows.get(0);
}
}
/**
* 通过自定义 sql 来查询一个 Row 列表
*
* @param sql 自定义的 sql
* @param args sql 参数
* @return row 列表
*/
@SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
List<Row> selectListBySql(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 根据 map 来查询一个 Row 列表
*
* @param tableName 表名
* @param whereConditions 条件
* @return row 列表
*/
default List<Row> selectListByMap(String tableName, Map<String, Object> whereConditions) {
return selectListByQuery(tableName, new QueryWrapper().where(whereConditions));
}
/**
* 根据 queryWrapper 来查询一个 row 列表
*
* @param tableName 表名
* @param queryWrapper queryWrapper
* @return row 列表
* @see RowSqlProvider#selectListByQuery(Map)
*/
@SelectProvider(value = RowSqlProvider.class, method = "selectListByQuery")
List<Row> selectListByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 查询某张表的全部数据
*
* @param tableName 表名
* @return row 列表
*/
default List<Row> selectAll(@Param(FlexConsts.TABLE_NAME) String tableName) {
return selectListByMap(tableName, null);
}
/**
* 通过 sql 查询某一个数据sql 执行的结果应该只有 1 1
* 若返回有多列则只取第一列的值若有多行则会出现 TooManyResultsException 错误
*
* @param sql sql
* @param args sql 参数
* @return object
*/
@SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
Object selectObject(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 通过 sql 查询多行数据sql 执行的结果应该只有 1
*
* @param sql sql 语句
* @param args sql 参数
* @return object list
*/
@SelectProvider(value = RowSqlProvider.class, method = RowSqlProvider.METHOD_RAW_SQL)
List<Object> selectObjectList(@Param(FlexConsts.SQL) String sql, @Param(FlexConsts.SQL_ARGS) Object... args);
/**
* 查询数据一般用于 select count(*)... 的语言也可用于执行的结果只有一个数值的其他 sql
*
* @param sql sql 语句
* @param args sql 参数
* @return 返回数据
*/
default long selectCount(String sql, Object... args) {
Object object = selectObject(sql, args);
if (object == null) {
return 0;
} else if (object instanceof Number) {
return ((Number) object).longValue();
} else {
throw FlexExceptions.wrap("selectCount error, Can not get number value for sql: %s", sql);
}
}
/**
* 根据 queryWrapper 来查询数据
*
* @param tableName 表名
* @param queryWrapper queryWrapper
* @return 数量
* @see RowSqlProvider#selectCountByQuery(Map)
*/
@SelectProvider(value = RowSqlProvider.class, method = "selectCountByQuery")
long selectCountByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
/**
* 分页查询某张表的数据
*
* @param tableName 表名
* @param pageNumber 当前页码
* @param pageSize 每页的数据量
* @param queryWrapper 条件封装
* @return 一页数据
*/
default Page<Row> paginate(String tableName, int pageNumber, int pageSize, QueryWrapper queryWrapper) {
Page<Row> page = new Page<>(pageNumber, pageSize);
return paginate(tableName, page, queryWrapper);
}
/**
* 分页查询数据
*
* @param tableName 表名
* @param page page 封装类
* @param queryWrapper 条件
* @return
*/
default Page<Row> paginate(String tableName, Page<Row> page, QueryWrapper queryWrapper) {
List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
// 只有 totalRow 小于 0 的时候才会去查询总量
// 这样方便用户做总数缓存而非每次都要去查询总量
// 一般的分页场景中只有第一页的时候有必要去查询总量第二页以后是不需要的
if (page.getTotalRow() < 0) {
//清除group by 去查询数据
CPI.setGroupByColumns(queryWrapper, null);
long count = selectCountByQuery(tableName, queryWrapper);
page.setTotalRow(count);
}
if (page.getTotalRow() == 0 || page.getPageNumber() > page.getTotalPage()) {
return page;
}
//恢复数量查询清除的 groupBy
CPI.setGroupByColumns(queryWrapper, groupByColumns);
int offset = page.getPageSize() * (page.getPageNumber() - 1);
queryWrapper.limit(offset, page.getPageSize());
List<Row> rows = selectListByQuery(tableName, queryWrapper);
page.setList(rows);
return page;
}
}

View File

@ -0,0 +1,211 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.row;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.dialect.DbType;
import com.mybatisflex.core.dialect.DialectFactory;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import org.apache.ibatis.executor.BatchResult;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class RowMapperInvoker {
private final SqlSessionFactory sqlSessionFactory;
private final DbType dbType;
private RowSessionManager rowSessionManager = RowSessionManager.DEFAULT;
public RowMapperInvoker(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
this.dbType = FlexGlobalConfig.getConfig(sqlSessionFactory.getConfiguration().getEnvironment().getId()).getDbType();
}
public RowSessionManager getRowSessionManager() {
return rowSessionManager;
}
public void setRowSessionManager(RowSessionManager rowSessionManager) {
this.rowSessionManager = rowSessionManager;
}
private <R> R execute(Function<RowMapper, R> function) {
SqlSession sqlSession = rowSessionManager.getSqlSession(sqlSessionFactory);
try {
DialectFactory.setHintDbType(dbType);
RowMapper mapper = sqlSession.getMapper(RowMapper.class);
return function.apply(mapper);
} finally {
DialectFactory.clearHintDbType();
rowSessionManager.releaseSqlSession(sqlSession, sqlSessionFactory);
}
}
public int insertBySql(String sql, Object... args) {
return execute(mapper -> mapper.insertBySql(sql, args));
}
public int insertRow(String tableName, Row row) {
return execute(mapper -> mapper.insertRow(tableName, row));
}
public int[] insertBatch(String tableName, Collection<Row> rows, int batchSize) {
int[] results = new int[rows.size()];
SqlSession sqlSession = rowSessionManager.getSqlSession(sqlSessionFactory, ExecutorType.BATCH);
try {
DialectFactory.setHintDbType(dbType);
RowMapper mapper = sqlSession.getMapper(RowMapper.class);
int counter = 0;
int resultsPos = 0;
for (Row row : rows) {
if (++counter >= batchSize) {
counter = 0;
List<BatchResult> batchResults = sqlSession.flushStatements();
for (BatchResult batchResult : batchResults) {
int[] updateCounts = batchResult.getUpdateCounts();
for (int updateCount : updateCounts) {
results[resultsPos++] = updateCount;
}
}
} else {
mapper.insertRow(tableName, row);
}
}
} finally {
DialectFactory.clearHintDbType();
rowSessionManager.releaseSqlSession(sqlSession, sqlSessionFactory);
}
return results;
}
public int insertBatchWithFirstRowColumns(String tableName, List<Row> rows) {
return execute(mapper -> mapper.insertBatchWithFirstRowColumns(tableName, rows));
}
public int deleteBySql(String sql, Object... args) {
return execute(mapper -> mapper.deleteBySql(sql, args));
}
public int deleteById(String tableName, Row row) {
return execute(mapper -> mapper.deleteById(tableName, row));
}
public int deleteById(String tableName, String primaryKey, Object id) {
return execute(mapper -> mapper.deleteById(tableName, primaryKey, id));
}
public int deleteBatchByIds(String tableName, String primaryKey, Collection<?> ids) {
return execute(mapper -> mapper.deleteBatchByIds(tableName, primaryKey, ids));
}
public int deleteByByMap(String tableName, Map<String, Object> whereColumns) {
return execute(mapper -> mapper.deleteByByMap(tableName, whereColumns));
}
public int deleteByQuery(String tableName, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.deleteByQuery(tableName, queryWrapper));
}
public int updateBySql(String sql, Object... args) {
return execute(mapper -> mapper.updateBySql(sql, args));
}
public int updateById(String tableName, Row row) {
return execute(mapper -> mapper.updateById(tableName, row));
}
public int updateByMap(String tableName, Row data, Map<String, Object> whereColumns) {
return execute(mapper -> mapper.updateByMap(tableName, data, whereColumns));
}
public int updateByQuery(String tableName, Row data, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.updateByQuery(tableName, data, queryWrapper));
}
public int updateBatchById(String tableName, List<Row> rows) {
return execute(mapper -> mapper.updateBatchById(tableName, rows));
}
public Row selectOneBySql(String sql, Object... args) {
return execute(mapper -> mapper.selectOneBySql(sql, args));
}
public Row selectOneById(String tableName, Row row) {
return execute(mapper -> mapper.selectOneById(tableName, row));
}
public Row selectOneById(String tableName, String primaryKey, Object id) {
return execute(mapper -> mapper.selectOneById(tableName, primaryKey, id));
}
public Row selectOneByMap(String tableName, Map whereColumns) {
return execute(mapper -> mapper.selectOneByMap(tableName, whereColumns));
}
public Row selectOneByQuery(String tableName, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.selectOneByQuery(tableName, queryWrapper));
}
public List<Row> selectListBySql(String sql, Object... args) {
return execute(mapper -> mapper.selectListBySql(sql, args));
}
public List<Row> selectListByMap(String tableName, Map<String, Object> whereColumns) {
return execute(mapper -> mapper.selectListByMap(tableName, whereColumns));
}
public List<Row> selectListByQuery(String tableName, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.selectListByQuery(tableName, queryWrapper));
}
public List<Row> selectAll(String tableName) {
return execute(mapper -> mapper.selectAll(tableName));
}
public Object selectObject(String sql, Object... args) {
return execute(mapper -> mapper.selectObject(sql, args));
}
public List<Object> selectObjectList(String sql, Object... args) {
return execute(mapper -> mapper.selectObjectList(sql, args));
}
public long selectCount(String sql, Object... args) {
return execute(mapper -> mapper.selectCount(sql, args));
}
public long selectCountByQuery(String tableName, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.selectCountByQuery(tableName, queryWrapper));
}
public Page<Row> paginate(String tableName, int pageNumber, int pageSize, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.paginate(tableName, pageNumber, pageSize, queryWrapper));
}
public Page<Row> paginate(String tableName, Page<Row> page, QueryWrapper queryWrapper) {
return execute(mapper -> mapper.paginate(tableName, page, queryWrapper));
}
}

View File

@ -0,0 +1,45 @@
package com.mybatisflex.core.row;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public interface RowSessionManager {
RowSessionManager DEFAULT = new RowSessionManager() {
@Override
public SqlSession getSqlSession(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
return sqlSessionFactory.openSession(executorType);
}
@Override
public void releaseSqlSession(SqlSession sqlSession, SqlSessionFactory sqlSessionFactory) {
sqlSession.commit();
sqlSession.close();
}
};
/**
* 获取 sqlSession
*
* @param sqlSessionFactory
*/
default SqlSession getSqlSession(SqlSessionFactory sqlSessionFactory){
return getSqlSession(sqlSessionFactory,sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
/**
* 获取 sqlSession
* @param sqlSessionFactory
* @param executorType
*/
SqlSession getSqlSession(SqlSessionFactory sqlSessionFactory, ExecutorType executorType);
/**
* 释放 sqlSession
*
* @param sqlSession
*/
void releaseSqlSession(SqlSession sqlSession, SqlSessionFactory sqlSessionFactory);
}

View File

@ -0,0 +1,31 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import org.apache.ibatis.reflection.ReflectorFactory;
public abstract class BaseReflectorFactory implements ReflectorFactory {
@Override
public boolean isClassCacheEnabled() {
return true;
}
@Override
public void setClassCacheEnabled(boolean classCacheEnabled) {
}
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
public class ColumnInfo {
/**
* 数据库列名
*/
protected String column;
/**
* java entity 定义的属性名称
*/
protected String property;
/**
* 属性类型
*/
protected Class<?> propertyType;
public String getColumn() {
return column;
}
public void setColumn(String column) {
this.column = column;
}
public String getProperty() {
return property;
}
public void setProperty(String property) {
this.property = property;
}
public Class<?> getPropertyType() {
return propertyType;
}
public void setPropertyType(Class<?> propertyType) {
this.propertyType = propertyType;
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
public final class EntityMetaObject {
public static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
public static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private EntityMetaObject() {
// Prevent Instantiation of Static Class
}
public static MetaObject forObject(Object object, ReflectorFactory reflectorFactory) {
return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, reflectorFactory);
}
}

View File

@ -0,0 +1,91 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.core.enums.KeyType;
public class IdInfo extends ColumnInfo {
/**
* id 生成策略
*/
private KeyType keyType;
/**
* keyType 类型是 sequence value 则代表的是
* sequence 序列的 sql 内容
* 例如select SEQ_USER_ID.nextval as id from dual
*
* keyType Generatorvalue 则代表的是使用的那个 keyGenerator 的名称
*/
private String value;
/**
* sequence 序列内容执行顺序
*
* @see org.apache.ibatis.executor.keygen.SelectKeyGenerator
*/
private boolean before;
public IdInfo(ColumnInfo columnInfo) {
this.setColumn(columnInfo.getColumn());
this.setProperty(columnInfo.getProperty());
this.setPropertyType(columnInfo.getPropertyType());
// id 的类型为数值时默认设置为自增的方式
if (Number.class.isAssignableFrom(columnInfo.getPropertyType())) {
keyType = KeyType.Auto;
} else {
keyType = KeyType.None;
}
}
public IdInfo(String column, String property, Class<?> propertyType, Id id) {
this.column = column;
this.property = property;
this.propertyType = propertyType;
this.keyType = id.keyType();
this.value = id.value();
this.before = id.before();
}
public KeyType getKeyType() {
return keyType;
}
public void setKeyType(KeyType keyType) {
this.keyType = keyType;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public boolean isBefore() {
return before;
}
public void setBefore(boolean before) {
this.before = before;
}
}

View File

@ -0,0 +1,37 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import com.mybatisflex.core.querywrapper.QueryTable;
import java.io.Serializable;
public class TableDef implements Serializable {
private String tableName;
public TableDef(String tableName) {
this.tableName = tableName;
}
public String getTableName() {
return tableName;
}
public QueryTable as(String alias) {
return new QueryTable(tableName, alias);
}
}

View File

@ -0,0 +1,382 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.enums.KeyType;
import com.mybatisflex.core.javassist.ModifyAttrsRecord;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.util.ArrayUtil;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import org.apache.ibatis.mapping.ResultFlag;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.session.Configuration;
import java.util.*;
public class TableInfo {
private String schema; //schema
private String tableName; //表名
private Class<?> entityClass; //实体类
private boolean useCached = false;
private boolean camelToUnderline = true;
private String[] columns = new String[0]; // 所有的字段但除了主键的列
private String[] primaryKeys = new String[0]; //主键字段
//在插入数据的时候支持主动插入的主键字段
//排除主键为 auto before false 的主键字段
private String[] insertPrimaryKeys;
private List<ColumnInfo> columnInfoList;
private List<IdInfo> primaryKeyList;
//column java 属性的称的关系映射
private Map<String, String> columnPropertyMapping = new HashMap<>();
private Map<String, String> propertyColumnMapping = new HashMap<>();
private final ReflectorFactory reflectorFactory = new BaseReflectorFactory() {
@Override
public Reflector findForClass(Class<?> type) {
return getReflector();
}
};
private Reflector reflector; //反射工具
public String getSchema() {
return schema;
}
public void setSchema(String schema) {
this.schema = schema;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Class<?> getEntityClass() {
return entityClass;
}
public void setEntityClass(Class<?> entityClass) {
this.entityClass = entityClass;
}
public boolean isUseCached() {
return useCached;
}
public void setUseCached(boolean useCached) {
this.useCached = useCached;
}
public boolean isCamelToUnderline() {
return camelToUnderline;
}
public void setCamelToUnderline(boolean camelToUnderline) {
this.camelToUnderline = camelToUnderline;
}
public Reflector getReflector() {
return reflector;
}
public ReflectorFactory getReflectorFactory() {
return reflectorFactory;
}
public void setReflector(Reflector reflector) {
this.reflector = reflector;
}
public String[] getColumns() {
return columns;
}
public void setColumns(String[] columns) {
this.columns = columns;
}
public String[] getPrimaryKeys() {
return primaryKeys;
}
public void setPrimaryKeys(String[] primaryKeys) {
this.primaryKeys = primaryKeys;
}
public List<ColumnInfo> getColumnInfoList() {
return columnInfoList;
}
void setColumnInfoList(List<ColumnInfo> columnInfoList) {
this.columnInfoList = columnInfoList;
this.columns = new String[columnInfoList.size()];
for (int i = 0; i < columnInfoList.size(); i++) {
ColumnInfo columnInfo = columnInfoList.get(i);
columns[i] = columnInfo.getColumn();
columnPropertyMapping.put(columnInfo.column, columnInfo.property);
propertyColumnMapping.put(columnInfo.property, columnInfo.column);
}
}
public List<IdInfo> getPrimaryKeyList() {
return primaryKeyList;
}
void setPrimaryKeyList(List<IdInfo> primaryKeyList) {
this.primaryKeyList = primaryKeyList;
this.primaryKeys = new String[primaryKeyList.size()];
List<String> insertIdFields = new ArrayList<>();
for (int i = 0; i < primaryKeyList.size(); i++) {
IdInfo idInfo = primaryKeyList.get(i);
primaryKeys[i] = idInfo.getColumn();
if (idInfo.getKeyType() != KeyType.Auto && idInfo.isBefore()) {
insertIdFields.add(idInfo.getColumn());
}
columnPropertyMapping.put(idInfo.column, idInfo.property);
propertyColumnMapping.put(idInfo.property, idInfo.column);
}
this.insertPrimaryKeys = insertIdFields.toArray(new String[0]);
}
/**
* 插入新增数据时获取所有要插入的字段
*
* @return 字段列表
*/
public String[] obtainInsertColumns() {
return ArrayUtil.concat(insertPrimaryKeys, columns);
}
/**
* 根据 插入字段 获取所有插入的值
*
* @param entity entity 中获取
* @return 数组
*/
public Object[] obtainInsertValues(Object entity) {
MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
String[] insertColumns = obtainInsertColumns();
Object[] values = new Object[insertColumns.length];
for (int i = 0; i < insertColumns.length; i++) {
Object value = getColumnValue(metaObject, insertColumns[i]);
values[i] = value;
}
return values;
}
/**
* 获取要修改的值
*
* @param entity
* @param ignoreNulls
* @return
*/
public Set<String> obtainUpdateColumns(Object entity, boolean ignoreNulls, boolean includePrimary) {
MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
Set<String> columns = new LinkedHashSet<>(); //需使用 LinkedHashSet 保证 columns 的顺序
if (entity instanceof ModifyAttrsRecord) {
Set<String> properties = ((ModifyAttrsRecord) entity).obtainModifyAttrs();
if (properties.isEmpty()) {
return Collections.emptySet();
}
for (String property : properties) {
String column = getColumnByProperty(property);
if (!includePrimary && ArrayUtil.contains(primaryKeys, column)) {
continue;
}
Object value = getPropertyValue(metaObject, property);
if (ignoreNulls && value == null) {
continue;
}
columns.add(column);
}
}
//not ModifyAttrsRecord
else {
for (String column : this.columns) {
Object value = getColumnValue(metaObject, column);
if (ignoreNulls && value == null) {
continue;
}
columns.add(column);
}
// 普通 entity ModifyAttrsRecord 忽略 includePrimary 的设置
// if (includePrimary) {
// for (String column : this.primaryKeys) {
// Object value = getColumnValue(metaObject, column);
// if (ignoreNulls && value == null) {
// continue;
// }
// columns.add(column);
// }
// }
}
return columns;
}
/**
* 获取所有要修改的值默认为全部除了主键以外的字段
*
* @param entity 实体对象
* @return 数组
*/
public Object[] obtainUpdateValues(Object entity, boolean ignoreNulls, boolean includePrimary) {
MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
List<Object> values = new ArrayList<>();
if (entity instanceof ModifyAttrsRecord) {
Set<String> properties = ((ModifyAttrsRecord) entity).obtainModifyAttrs();
if (properties.isEmpty()) {
return values.toArray();
}
for (String property : properties) {
String column = getColumnByProperty(property);
if (!includePrimary && ArrayUtil.contains(primaryKeys, column)) {
continue;
}
Object value = getPropertyValue(metaObject, property);
if (ignoreNulls && value == null) {
continue;
}
values.add(value);
}
}
// not ModifyAttrsRecord
else {
for (String column : this.columns) {
Object value = getColumnValue(metaObject, column);
if (ignoreNulls && value == null) {
continue;
}
values.add(value);
}
// 普通 entity 忽略 includePrimary 的设置
// if (includePrimary) {
// }
}
return values.toArray();
}
public Object[] obtainPrimaryValues(Object entity) {
MetaObject metaObject = EntityMetaObject.forObject(entity, reflectorFactory);
Object[] values = new Object[primaryKeys.length];
for (int i = 0; i < primaryKeys.length; i++) {
values[i] = getColumnValue(metaObject, primaryKeys[i]);
}
return values;
}
public String getMappedStatementKeyProperties() {
StringJoiner joiner = new StringJoiner(",");
for (IdInfo value : primaryKeyList) {
joiner.add(FlexConsts.ENTITY + "." + value.getProperty());
}
return joiner.toString();
}
public String getMappedStatementKeyColumns() {
StringJoiner joiner = new StringJoiner(",");
for (IdInfo value : primaryKeyList) {
joiner.add(FlexConsts.ENTITY + "." + value.getColumn());
}
return joiner.toString();
}
public ResultMap buildResultMap(Configuration configuration) {
String resultMapId = entityClass.getName();
List<ResultMapping> resultMappings = new ArrayList<>();
for (ColumnInfo columnInfo : columnInfoList) {
ResultMapping mapping = new ResultMapping.Builder(configuration, columnInfo.getProperty(),
columnInfo.getColumn(), columnInfo.getPropertyType()).build();
resultMappings.add(mapping);
}
for (IdInfo idInfo : primaryKeyList) {
ResultMapping mapping = new ResultMapping.Builder(configuration, idInfo.getProperty(),
idInfo.getColumn(), idInfo.getPropertyType())
.flags(CollectionUtil.newArrayList(ResultFlag.ID))
// .typeHandler()
.build();
resultMappings.add(mapping);
}
return new ResultMap.Builder(configuration, resultMapId, entityClass, resultMappings).build();
}
private Object getColumnValue(MetaObject metaObject, String column) {
return getPropertyValue(metaObject, columnPropertyMapping.get(column));
}
private Object getPropertyValue(MetaObject metaObject, String property) {
if (property != null && metaObject.hasGetter(property)) {
return metaObject.getValue(property);
}
return null;
}
public String getColumnByProperty(String property) {
return propertyColumnMapping.get(property);
}
/**
* 通过 row 实例类转换为一个 entity
* @return entity
*/
public <T> T newInstanceByRow(Row row) {
Object instance = ClassUtil.newInstance(entityClass);
MetaObject metaObject = EntityMetaObject.forObject(instance, reflectorFactory);
for (String column : row.keySet()) {
String property = columnPropertyMapping.get(column);
if (metaObject.hasSetter(property)) {
metaObject.setValue(property, row.get(column));
}
}
return (T) instance;
}
}

View File

@ -0,0 +1,166 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.table;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.util.MapUtil;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class TableInfos {
private static final Set<Class<?>> defaultSupportColumnTypes = CollectionUtil.newHashSet(
int.class, Integer.class,
short.class, Short.class,
long.class, Long.class,
float.class, Float.class,
double.class, Double.class,
boolean.class, Boolean.class,
Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class,
byte[].class, Byte[].class,
BigInteger.class, BigDecimal.class,
char.class, String.class
);
private static Map<Class<?>, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
public static TableInfo ofMapperClass(Class<?> mapperClass) {
return MapUtil.computeIfAbsent(tableInfoMap, mapperClass, key -> {
Class<?> entityClass = getEntityClass(key);
return entityClass != null ? ofEntityClass(entityClass) : null;
});
}
public static TableInfo ofEntityClass(Class<?> entityClass) {
return MapUtil.computeIfAbsent(tableInfoMap, entityClass, key -> createTableInfo(entityClass));
}
private static Class<?> getEntityClass(Class<?> mapperClass) {
Type[] genericInterfaces = mapperClass.getGenericInterfaces();
if (genericInterfaces.length == 1) {
Type type = genericInterfaces[0];
if (type instanceof ParameterizedType) {
return (Class<?>) ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
return getEntityClass((Class<?>) type);
}
}
return null;
}
private static TableInfo createTableInfo(Class<?> entityClass) {
TableInfo tableInfo = new TableInfo();
tableInfo.setEntityClass(entityClass);
tableInfo.setReflector(new Reflector(entityClass));
//初始化表名
Table table = entityClass.getAnnotation(Table.class);
if (table != null) {
tableInfo.setTableName(table.value());
tableInfo.setSchema(table.schema());
tableInfo.setUseCached(table.useCached());
tableInfo.setCamelToUnderline(table.camelToUnderline());
} else {
//默认为类名转驼峰下划线
tableInfo.setTableName(StringUtil.camelToUnderline(entityClass.getSimpleName()));
}
//初始化字段相关
List<ColumnInfo> columnInfoList = new ArrayList<>();
List<IdInfo> idInfos = new ArrayList<>();
Field idField = null;
List<Field> entityFields = ClassUtil.getAllFields(entityClass);
for (Field field : entityFields) {
//只支持基本数据类型不支持比如 list set 或者自定义的类等
if (!defaultSupportColumnTypes.contains(field.getType())) {
continue;
}
Column column = field.getAnnotation(Column.class);
if (column != null && column.ignore()) {
continue; // ignore
}
String columnName = column != null && StringUtil.isNotBlank(column.value())
? column.value()
: (tableInfo.isCamelToUnderline() ? StringUtil.camelToUnderline(field.getName()) : field.getName());
Id id = field.getAnnotation(Id.class);
ColumnInfo columnInfo;
if (id != null) {
columnInfo = new IdInfo(columnName, field.getName(), field.getType(), id);
idInfos.add((IdInfo) columnInfo);
} else {
columnInfo = new ColumnInfo();
columnInfoList.add(columnInfo);
}
columnInfo.setColumn(columnName);
columnInfo.setProperty(field.getName());
columnInfo.setPropertyType(field.getType());
if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(field.getName())) {
idField = field;
}
}
if (idInfos.isEmpty() && idField != null) {
int index = -1;
for (int i = 0; i < columnInfoList.size(); i++) {
ColumnInfo columnInfo = columnInfoList.get(i);
if (FlexConsts.DEFAULT_PRIMARY_FIELD.equals(columnInfo.getProperty())) {
index = i;
break;
}
}
if (index >= 0) {
ColumnInfo removedColumnInfo = columnInfoList.remove(index);
idInfos.add(new IdInfo(removedColumnInfo));
}
}
tableInfo.setColumnInfoList(columnInfoList);
tableInfo.setPrimaryKeyList(idInfos);
return tableInfo;
}
}

View File

@ -0,0 +1,99 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
import java.util.Arrays;
import java.util.Objects;
public class ArrayUtil {
/**
* 判断数组是否为空
*
* @param array
* @param <T>
* @return true
*/
public static <T> boolean isEmpty(T[] array) {
return array == null || array.length == 0;
}
/**
* 判断数组是否不为空
*
* @param array
* @param <T>
* @return
*/
public static <T> boolean isNotEmpty(T[] array) {
return !isEmpty(array);
}
/**
* 合并两个数组为一个新的数组
*
* @param first 第一个数组
* @param second 第二个数组
* @param <T>
* @return 新的数组
*/
public static <T> T[] concat(T[] first, T[] second) {
if (first == null && second == null) {
throw new IllegalArgumentException("not allow first and second are null.");
} else if (isEmpty(first)) {
return second;
} else if (isEmpty(second)) {
return first;
} else {
T[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}
}
/**
* 查看数组中是否包含某一个值
*
* @param arrays 数组
* @param object 用于检测的值
* @param <T>
* @return true 包含
*/
public static <T> boolean contains(T[] arrays, T object) {
if (isEmpty(arrays)) {
return false;
}
for (T array : arrays) {
if (Objects.equals(array, object)) {
return true;
}
}
return false;
}
/**
* 急速构建数组
*/
public static <T> T[] asArray(T... elements) {
return elements;
}
}

View File

@ -0,0 +1,177 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 类实例创建者创建者
* Created by michael on 17/3/21.
*/
public class ClassUtil {
//proxy frameworks
private static final List<String> PROXY_CLASS_NAMES = Arrays.asList("net.sf.cglib.proxy.Factory"
// cglib
, "org.springframework.cglib.proxy.Factory"
, "javassist.util.proxy.ProxyObject"
// javassist
, "org.apache.ibatis.javassist.util.proxy.ProxyObject");
public static boolean isProxy(Class<?> clazz) {
for (Class<?> cls : clazz.getInterfaces()) {
if (PROXY_CLASS_NAMES.contains(cls.getName())) {
return true;
}
}
//java proxy
return Proxy.isProxyClass(clazz);
}
private static final String ENHANCER_BY = "$$EnhancerBy";
private static final String JAVASSIST_BY = "_$$_";
public static <T> Class<T> getUsefulClass(Class<T> clazz) {
if (isProxy(clazz)) {
return (Class<T>) clazz.getSuperclass();
}
//ControllerTest$ServiceTest$$EnhancerByGuice$$40471411#hello -------> Guice
//com.demo.blog.Blog$$EnhancerByCGLIB$$69a17158 ----> CGLIB
//io.jboot.test.app.TestAppListener_$$_jvstb9f_0 ------> javassist
final String name = clazz.getName();
if (name.contains(ENHANCER_BY) || name.contains(JAVASSIST_BY)) {
return (Class<T>) clazz.getSuperclass();
}
return clazz;
}
public static <T> T newInstance(Class<T> clazz) {
try {
Constructor constructor = clazz.getDeclaredConstructor();
return (T) constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static <T> T newInstance(Class<T> clazz, Object... paras) {
try {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
if (isMatchedParas(constructor, paras)) {
Object ret = constructor.newInstance(paras);
return (T) ret;
}
}
throw new IllegalArgumentException("Can not matched constructor by paras" + Arrays.toString(paras) + " for class: " + clazz.getName());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static boolean isMatchedParas(Constructor<?> constructor, Object[] paras) {
if (constructor.getParameterCount() == 0) {
return paras == null || paras.length == 0;
}
if (constructor.getParameterCount() > 0
&& (paras == null || paras.length != constructor.getParameterCount())) {
return false;
}
Class<?>[] parameterTypes = constructor.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Object paraObject = paras[i];
if (paraObject != null && !parameterType.isAssignableFrom(paraObject.getClass())) {
return false;
}
}
return true;
}
public static String buildMethodString(Method method) {
StringBuilder sb = new StringBuilder()
.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName())
.append("(");
Class<?>[] params = method.getParameterTypes();
int in = 0;
for (Class<?> clazz : params) {
sb.append(clazz.getName());
if (++in < params.length) {
sb.append(",");
}
}
return sb.append(")").toString();
}
public static boolean hasClass(String className) {
try {
Class.forName(className, false, getClassLoader());
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
public static ClassLoader getClassLoader() {
ClassLoader ret = Thread.currentThread().getContextClassLoader();
return ret != null ? ret : ClassUtil.class.getClassLoader();
}
public static List<Field> getAllFields(Class<?> cl) {
List<Field> fields = new ArrayList<>();
doGetFields(cl, fields);
return fields;
}
private static void doGetFields(Class<?> cl, List<Field> fields) {
if (cl == null || cl == Object.class) {
return;
}
Field[] declaredFields = cl.getDeclaredFields();
for (Field declaredField : declaredFields) {
fields.add(declaredField);
}
doGetFields(cl.getSuperclass(), fields);
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
import java.util.*;
import java.util.function.Function;
public class CollectionUtil {
public static boolean isEmpty(Collection<?> coll) {
return (coll == null || coll.isEmpty());
}
public static boolean isNotEmpty(Collection<?> coll) {
return !isEmpty(coll);
}
public static boolean isEmpty(Map<?, ?> map) {
return (map == null || map.isEmpty());
}
public static boolean isNotEmpty(Map<?, ?> map) {
return !isEmpty(map);
}
/**
* 合并 list
*/
public static <T> List<T> merge(List<T> list, List<T> other) {
if (list == null && other == null) {
return new ArrayList<>();
} else if (isEmpty(other)) {
return list;
} else if (isEmpty(list)) {
return other;
}
List<T> newList = new ArrayList<>(list);
newList.addAll(other);
return newList;
}
public static <K, V> HashMap<K, V> newHashMap() {
return new HashMap<>();
}
/**
* 主要是用于修复 concurrentHashMap jdk1.8 下的死循环问题
*
* @see <a href="https://bugs.openjdk.org/browse/JDK-8161372">https://bugs.openjdk.org/browse/JDK-8161372</a>
*/
public static <K, V> V computeIfAbsent(Map<K, V> concurrentHashMap, K key, Function<? super K, ? extends V> mappingFunction) {
V v = concurrentHashMap.get(key);
if (v != null) {
return v;
}
return concurrentHashMap.computeIfAbsent(key, mappingFunction);
}
public static <T> Set<T> newHashSet(T... elements) {
return new HashSet<>(Arrays.asList(elements));
}
public static <T> List<T> newArrayList(T... elements) {
return new ArrayList<>(Arrays.asList(elements));
}
}

View File

@ -0,0 +1,41 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
public class ObjectUtil {
public static <T> T requireNonNullElse(T t1, T t2) {
return t1 == null ? t2 : t1;
}
public static boolean areNotNull(Object ... objs){
for (Object obj : objs) {
if (obj == null){
return false;
}
}
return true;
}
public static boolean areNull(Object ... objs){
for (Object obj : objs) {
if (obj != null){
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,251 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
import java.util.Collection;
import java.util.function.Function;
import java.util.regex.Pattern;
public class StringUtil {
/**
* 第一个字符转换为小写
*
* @param string
* @return
*/
public static String firstCharToLowerCase(String string) {
char firstChar = string.charAt(0);
if (firstChar >= 'A' && firstChar <= 'Z') {
char[] arr = string.toCharArray();
arr[0] += ('a' - 'A');
return new String(arr);
}
return string;
}
/**
* 第一个字符转换为大写
*
* @param string
*/
public static String firstCharToUpperCase(String string) {
char firstChar = string.charAt(0);
if (firstChar >= 'a' && firstChar <= 'z') {
char[] arr = string.toCharArray();
arr[0] -= ('a' - 'A');
return new String(arr);
}
return string;
}
/**
* 驼峰转下划线格式
*
* @param string
*/
public static String camelToUnderline(String string) {
if (isBlank(string)) {
return "";
}
int len = string.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = string.charAt(i);
if (Character.isUpperCase(c) && i > 0) {
sb.append('_');
}
sb.append(Character.toLowerCase(c));
}
return sb.toString();
}
/**
* 下划线转驼峰格式
*
* @param string
*/
public static String underlineToCamel(String string) {
if (isBlank(string)) {
return "";
}
String temp = string.toLowerCase();
int len = temp.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char c = temp.charAt(i);
if (c == '_') {
if (++i < len) {
sb.append(Character.toUpperCase(temp.charAt(i)));
}
} else {
sb.append(c);
}
}
return sb.toString();
}
/**
* 字符串为 null 或者内部字符全部为 ' ', '\t', '\n', '\r' 这四类字符时返回 true
*/
public static boolean isBlank(String str) {
if (str == null) {
return true;
}
for (int i = 0, len = str.length(); i < len; i++) {
if (str.charAt(i) > ' ') {
return false;
}
}
return true;
}
public static boolean isAnyBlank(String... strings) {
if (strings == null || strings.length == 0) {
throw new IllegalArgumentException("args is empty.");
}
for (String str : strings) {
if (isBlank(str)) {
return true;
}
}
return false;
}
public static boolean isNotBlank(String str) {
return !isBlank(str);
}
public static boolean areNotBlank(String... strings) {
return !isAnyBlank();
}
public static boolean startsWithAny(String str, String... prefixes) {
if (isBlank(str) || prefixes == null || prefixes.length == 0) {
return false;
}
for (String prefix : prefixes) {
if (str.startsWith(prefix)) {
return true;
}
}
return false;
}
public static boolean endsWithAny(String str, String... suffixes) {
if (isBlank(str) || suffixes == null || suffixes.length == 0) {
return false;
}
for (String suffix : suffixes) {
if (str.endsWith(suffix)) {
return true;
}
}
return false;
}
/**
* 正则匹配
*
* @param regex
* @param input
* @return
*/
public static boolean matches(String regex, String input) {
if (null == regex || null == input) {
return false;
}
return Pattern.matches(regex, input);
}
/**
* 合并字符串优化 String.join() 方法
*
* @param delimiter
* @param elements
* @return 新拼接好的字符串
* @see String#join(CharSequence, CharSequence...)
*/
public static String join(String delimiter, CharSequence... elements) {
if (ArrayUtil.isEmpty(elements)) {
return "";
} else if (elements.length == 1) {
return String.valueOf(elements[0]);
} else {
return String.join(delimiter, elements);
}
}
/**
* 合并字符串优化 String.join() 方法
*
* @param delimiter
* @param elements
* @return 新拼接好的字符串
* @see String#join(CharSequence, CharSequence...)
*/
public static String join(String delimiter, Collection<? extends CharSequence> elements) {
if (CollectionUtil.isEmpty(elements)) {
return "";
} else if (elements.size() == 1) {
return String.valueOf(elements.iterator().next());
} else {
return String.join(delimiter, elements);
}
}
/**
* 合并字符串优化 String.join() 方法
*
* @param delimiter
* @param objs
* @param function
* @param <T>
* @return
*/
public static <T> String join(String delimiter, Collection<T> objs, Function<T, String> function) {
if (CollectionUtil.isEmpty(objs)) {
return "";
} else if (objs.size() == 1) {
T next = objs.iterator().next();
return String.valueOf(function.apply(next));
} else {
String[] strings = new String[objs.size()];
int index = 0;
for (T obj : objs) {
strings[index++] = function.apply(obj);
}
return String.join(delimiter, strings);
}
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.core.util;
import com.mybatisflex.core.javassist.ModifyAttrsRecordProxyFactory;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.ReflectorFactory;
public class UpdateEntity {
private static ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
public static <T> T wrap(Class<T> clazz) {
return ModifyAttrsRecordProxyFactory.getInstance().get(clazz);
}
public static <T> T ofNotNull(T entity) {
Class<?> usefulClass = ClassUtil.getUsefulClass(entity.getClass());
T newEntity = (T) wrap(usefulClass);
Reflector reflector = reflectorFactory.findForClass(usefulClass);
String[] propertyNames = reflector.getGetablePropertyNames();
for (String propertyName : propertyNames) {
try {
Object value = reflector.getGetInvoker(propertyName)
.invoke(entity, null);
if (value != null) {
reflector.getSetInvoker(propertyName).invoke(newEntity, new Object[]{value});
}
} catch (Exception e) {
//ignore();
}
}
return newEntity;
}
}

View File

@ -0,0 +1,61 @@
package com.mybatisflex.test;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import java.util.Date;
@Table("tb_account")
public class Account {
@Id
private Long id;
private String userName;
private Date birthday;
private int sex;
private boolean isNormal;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public boolean isNormal() {
return isNormal;
}
public void setNormal(boolean normal) {
isNormal = normal;
}
}

View File

@ -0,0 +1,75 @@
package com.mybatisflex.test;
import com.mybatisflex.core.dialect.IDialect;
import com.mybatisflex.core.dialect.CommonsDialectImpl;
import com.mybatisflex.core.querywrapper.CPI;
import com.mybatisflex.core.querywrapper.QueryWrapper;
import org.junit.Test;
import java.util.Arrays;
import static com.mybatisflex.core.querywrapper.QueryMethods.*;
import static com.mybatisflex.test.table.Tables.ACCOUNT;
import static com.mybatisflex.test.table.Tables.ARTICLE;
public class AccountSqlTester {
@Test
public void testJava(){
StringBuilder sql = new StringBuilder("select * from xxx");
// sql.(7," 你好 ");
System.out.println(sql);
}
@Test
public void testSelectCountSql() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(100))
.and(ACCOUNT.USER_NAME.like("michael"));
IDialect dialect = new CommonsDialectImpl();
String sql = dialect.forSelectCountByQuery(queryWrapper);
System.out.println(sql);
}
@Test
public void testrSelectLimitSql() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select(ACCOUNT.ALL_COLUMNS)
.select(ARTICLE.ID.as("article_id"))
.select(distinct(ARTICLE.ID))
.select(max(ACCOUNT.SEX))
.select(count(distinct(ARTICLE.ID)))
.from(ACCOUNT).as("a1")
// .leftJoin(newWrapper().select().from(ARTICLE).where(ARTICLE.ID.ge(100))).as("aaa")
.leftJoin(ARTICLE).as("b1")
.on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
.where(ACCOUNT.ID.ge(select(ARTICLE.ID).from(ARTICLE).as("cc").where(ARTICLE.ID.eq(111))))
.and((true ? noCondition() : ARTICLE.ID.ge(22211)).and(ACCOUNT.ID.eq(10011)).when(false))
.and(ACCOUNT.USER_NAME.like("michael"))
.and(ARTICLE.ID.in(select(ARTICLE.ID).from("aaa")))
.and(
notExist(
selectOne().from("aaa").where(ARTICLE.ID.ge(333))
)
)
.groupBy(ACCOUNT.ID).having(ARTICLE.ID.ge(0))
// .and("bbb.id > ?",100)
.orderBy(ACCOUNT.ID.desc())
.limit(10, 10);
String mysqlSql = new CommonsDialectImpl().forSelectListByQuery(queryWrapper);
System.out.println(">>>>> mysql: \n" + mysqlSql);
System.out.println(">>>>> mysql: \n" + Arrays.toString(CPI.getValueArray(queryWrapper)));
// String oracleSql = new OracleDialect().forSelectListByQuery(CPI.getQueryTable(queryWrapper).getName(), queryWrapper);
// System.out.println(">>>>> oracle: " + oracleSql);
//
// String informixSql = new InformixDialect().forSelectListByQuery(CPI.getQueryTable(queryWrapper).getName(), queryWrapper);
// System.out.println(">>>>> informix: " + informixSql);
}
}

View File

@ -0,0 +1,29 @@
package com.mybatisflex.test;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.Table;
import com.mybatisflex.core.enums.KeyType;
import java.util.Date;
@Table("tb_article")
public class Article {
@Id(keyType = KeyType.Auto)
private Long id;
private Long accountId;
private String title;
@Column(isLarge = true)
private String content;
@Column(insert = "now()")
private Date created;
@Column(update = "now()")
private Date modified;
}

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>parent</artifactId>
<groupId>com.mybatis-flex</groupId>
<version>1.0.0-beta.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring</artifactId>
<version>1.0.0-beta.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-freemarker</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-velocity</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mybatis.scripting</groupId>
<artifactId>mybatis-thymeleaf</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
<excludes>
<exclude>
*.properties
</exclude>
</excludes>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,28 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.spring.boot;
import com.mybatisflex.core.mybatis.FlexConfiguration;
/**
* FlexConfiguration 做自定义的配置支持 {@link FlexConfiguration}
*/
@FunctionalInterface
public interface ConfigurationCustomizer {
void customize(FlexConfiguration configuration);
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.spring.boot;
import com.mybatisflex.core.row.Db;
import com.mybatisflex.spring.SpringRowSessionManager;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
@ConditionalOnClass(Db.class)
@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter({MybatisFlexAutoConfiguration.class})
public class DbAutoConfiguration {
public DbAutoConfiguration() {
Db.invoker().setRowSessionManager(new SpringRowSessionManager());
}
}

View File

@ -0,0 +1,325 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.spring.boot;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.spring.FlexSqlSessionFactoryBean;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Mybatis-Flex 的核心配置
* <p>
* <p>
* 参考 {@link <a href="https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.java">
* https://github.com/mybatis/spring-boot-starter/blob/master/mybatis-spring-boot-autoconfigure/src/main/java/org/mybatis/spring/boot/autoconfigure/MybatisAutoConfiguration.java</a>}
* <p>
* Mybatis-Flex 开启自动配置功能主要修改一下几个方面:
* <p>
* 1替换配置为 mybatis-flex 的配置前缀
* 2修改 SqlSessionFactory FlexSqlSessionFactoryBean
* 3修改 Configuration FlexConfiguration
*/
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisFlexProperties.class)
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisFlexAutoConfiguration implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MybatisFlexAutoConfiguration.class);
private final MybatisFlexProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;
public MybatisFlexAutoConfiguration(MybatisFlexProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
}
@Override
public void afterPropertiesSet() {
checkConfigFileExists();
}
private void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(),
"Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
SqlSessionFactoryBean factory = new FlexSqlSessionFactoryBean();
factory.setDataSource(dataSource);
if (properties.getConfiguration() == null || properties.getConfiguration().getVfsImpl() == null) {
factory.setVfs(SpringBootVFS.class);
}
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
Set<String> factoryPropertyNames = Stream
.of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
// Need to mybatis-spring 2.0.2+
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
// Need to mybatis-spring 2.0.2+
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
applySqlSessionFactoryBeanCustomizers(factory);
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
MybatisFlexProperties.CoreConfiguration coreConfiguration = this.properties.getConfiguration();
// Configuration configuration = null;
FlexConfiguration configuration = null;
if (coreConfiguration != null || !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new FlexConfiguration();
}
if (configuration != null && coreConfiguration != null) {
coreConfiguration.applyTo(configuration);
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
customizer.customize(factory);
}
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
/**
* This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
* similar to using Spring Data JPA repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
if (!AutoConfigurationPackages.has(this.beanFactory)) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
return;
}
logger.debug("Searching for mappers annotated with @Mapper");
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
}
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
.collect(Collectors.toSet());
if (propertyNames.contains("lazyInitialization")) {
// Need to mybatis-spring 2.0.2+
builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
}
if (propertyNames.contains("defaultScope")) {
// Need to mybatis-spring 2.0.6+
builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
}
// for spring-native
boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class,
Boolean.TRUE);
if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
Optional<String> sqlSessionTemplateBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
Optional<String> sqlSessionFactoryBeanName = Optional
.ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
builder.addPropertyValue("sqlSessionTemplateBeanName",
sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
} else {
builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
}
}
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
String[] beanNames = factory.getBeanNamesForType(type);
return beanNames.length > 0 ? beanNames[0] : null;
}
}
/**
* If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
* mappers based on the same component-scanning path as Spring Boot itself.
*/
@org.springframework.context.annotation.Configuration(proxyBeanMethods = false)
@Import(AutoConfiguredMapperScannerRegistrar.class)
@ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class})
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
@Override
public void afterPropertiesSet() {
logger.debug(
"Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}
}
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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 com.mybatisflex.spring.boot;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.boot.sql.init.dependency.AbstractBeansOfTypeDependsOnDatabaseInitializationDetector;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitializationDetector;
import java.util.Collections;
import java.util.Set;
/**
* {@link DependsOnDatabaseInitializationDetector} for Mybatis-Flex.
*/
class MybatisFlexDependsOnDatabaseInitializationDetector
extends AbstractBeansOfTypeDependsOnDatabaseInitializationDetector {
@Override
protected Set<Class<?>> getDependsOnDatabaseInitializationBeanTypes() {
return Collections.singleton(SqlSessionTemplate.class);
}
}

Some files were not shown because too many files have changed in this diff Show More