Przeglądaj źródła

Merge branch '3.0' into tdengine-jdbc

spring.li 2 lat temu
rodzic
commit
60587795af
100 zmienionych plików z 4721 dodań i 963 usunięć
  1. 33 0
      CHANGELOG.md
  2. 15 5
      build.gradle
  3. 2 7
      changelog-temp.md
  4. 1 1
      gradle/wrapper/gradle-wrapper.properties
  5. BIN
      libs/DmJdbcDriver-1.7.0.jar
  6. BIN
      libs/ojdbc8.jar
  7. 6 1
      mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/DbType.java
  8. 8 2
      mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/KeySequence.java
  9. 0 4
      mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/OrderBy.java
  10. 1 0
      mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/Version.java
  11. 1 0
      mybatis-plus-boot-starter-test/src/main/resources/META-INF/spring.factories
  12. 1 0
      mybatis-plus-boot-starter-test/src/main/resources/META-INF/spring/com.baomidou.mybatisplus.test.autoconfigure.AutoConfigureMybatisPlus.imports
  13. 1 0
      mybatis-plus-boot-starter/build.gradle
  14. 54 0
      mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/DdlApplicationRunner.java
  15. 7 9
      mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/IdentifierGeneratorAutoConfiguration.java
  16. 28 4
      mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.java
  17. 27 0
      mybatis-plus-boot-starter/src/test/java/com/baomidou/mybatisplus/autoconfigure/ApplicationTestStartSuccess.java
  18. 5 0
      mybatis-plus-boot-starter/src/test/resources/application.yml
  19. 0 2
      mybatis-plus-core/build.gradle
  20. 3 2
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java
  21. 3 1
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisParameterHandler.java
  22. 11 2
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/AbstractWrapper.java
  23. 34 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/interfaces/Compare.java
  24. 8 2
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/query/LambdaQueryWrapper.java
  25. 10 3
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/query/QueryWrapper.java
  26. 15 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/segments/ColumnSegment.java
  27. 7 2
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/config/GlobalConfig.java
  28. 84 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/AnnotationHandler.java
  29. 0 34
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/JoinTableInfoInitHandler.java
  30. 49 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/PostInitTableInfoHandler.java
  31. 3 1
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/injector/methods/Insert.java
  32. 39 22
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableFieldInfo.java
  33. 6 26
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableInfo.java
  34. 131 40
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableInfoHelper.java
  35. 35 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/plugins/IgnoreStrategy.java
  36. 58 40
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/plugins/InterceptorIgnoreHelper.java
  37. 5 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/GlobalConfigUtils.java
  38. 1 1
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/LambdaUtils.java
  39. 2 12
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/Sequence.java
  40. 2 0
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/StringPool.java
  41. 5 4
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/StringUtils.java
  42. 12 1
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/sql/SqlInjectionUtils.java
  43. 5 32
      mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/support/ReflectLambdaMeta.java
  44. 2 2
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/conditions/QueryWrapperTest.java
  45. 1 4
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/metadata/TableInfoTest.java
  46. 2 2
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/plugins/InterceptorIgnoreHelperTest.java
  47. 17 0
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/toolkit/sql/SqlInjectionUtilsTest.java
  48. 2 4
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/EncryptTest.java
  49. 2 2
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/metadata/TableInfoHelperTest.java
  50. 1 1
      mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/pom/ReflectionKitTest.java
  51. 16 4
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/AbstractChainWrapper.java
  52. 24 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/ChainWrapper.java
  53. 7 7
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/ChainQuery.java
  54. 23 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/LambdaQueryChainWrapper.java
  55. 21 2
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/QueryChainWrapper.java
  56. 2 2
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/ChainUpdate.java
  57. 11 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/LambdaUpdateChainWrapper.java
  58. 14 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/UpdateChainWrapper.java
  59. 156 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlHelper.java
  60. 87 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScript.java
  61. 53 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/IDdl.java
  62. 43 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/SimpleDdl.java
  63. 82 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/IDdlGenerator.java
  64. 56 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/MysqlDdlGenerator.java
  65. 53 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/OracleDdlGenerator.java
  66. 69 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/PostgreDdlGenerator.java
  67. 4 2
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/injector/methods/InsertBatchSomeColumn.java
  68. 1 1
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/handler/DataPermissionHandler.java
  69. 53 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/handler/MultiDataPermissionHandler.java
  70. 424 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java
  71. 194 207
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DataChangeRecorderInnerInterceptor.java
  72. 70 5
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DataPermissionInterceptor.java
  73. 19 9
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/IllegalSQLInnerInterceptor.java
  74. 3 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/OptimisticLockerInnerInterceptor.java
  75. 39 388
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/TenantLineInnerInterceptor.java
  76. 2 1
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/DialectFactory.java
  77. 31 16
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/InformixDialect.java
  78. 1 1
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/SQLServer2005Dialect.java
  79. 62 2
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/SybaseDialect.java
  80. 12 1
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/service/IService.java
  81. 36 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/ChainWrappers.java
  82. 640 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/Db.java
  83. 5 3
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/JdbcUtils.java
  84. 27 29
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/SimpleQuery.java
  85. 6 5
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/SqlHelper.java
  86. 4 0
      mybatis-plus-extension/src/main/kotlin/com/baomidou/mybatisplus/extension/kotlin/KtQueryChainWrapper.kt
  87. 4 0
      mybatis-plus-extension/src/main/kotlin/com/baomidou/mybatisplus/extension/kotlin/KtUpdateChainWrapper.kt
  88. 1 1
      mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/DynamicTableNameInnerInterceptorTest.java
  89. 120 0
      mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/MultiDataPermissionInterceptorTest.java
  90. 30 2
      mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/TenantLineInnerInterceptorTest.java
  91. 19 0
      mybatis-plus-generator/build.gradle
  92. 221 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/AutoGenerator.java
  93. 231 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/FastAutoGenerator.java
  94. 35 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/IFill.java
  95. 34 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/ITemplate.java
  96. 90 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/ConstVal.java
  97. 470 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/DataSourceConfig.java
  98. 264 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/GlobalConfig.java
  99. 26 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/IConfigBuilder.java
  100. 81 0
      mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/IDbQuery.java

+ 33 - 0
CHANGELOG.md

@@ -1,5 +1,38 @@
 # CHANGELOG
 
+## [v3.5.3] 2022.12.29
+- bug:生成模块pg和dm语句模式名增加
+
+
+## [v3.5.3] 2022.12.28
+
+- 多租户插件:多表join表名必需起别名,否则追加的过滤条件不带前缀
+- InterceptorIgnore 不能过滤 selectKey 的问题
+- 分页新增`informix数据库`支持
+- 分页新增`优炫数据库`支持
+- 分页新增`TDengine数据库`支持
+- 分页新增`亚马逊redshift数据库`支持
+- 支持spring-boot 2.7以上版本
+- 雪花id新增反解时间戳方法`Sequence#parseIdTimestamp`
+- BaseMapper.selectCount生成语句加入中`AS total`
+- 修复IllegalSQLInnerInterceptor类ClassCastException异常,并优化日志
+- 移除注解`OrderBy`的过时属性`isDesc`
+- 移除`TableInfo`过时方法
+- 加入`JoinTableInfoInitHandler`类参与`TableInfo`初始化
+- 修复StringUtils.sqlInjectionReplaceBlank方法过滤sql不全,可能会导致sql注入的情况
+- 增加IService.lambdaQuery(entity)支持,写法更便捷
+- 新增数据变更记录(数据审计)插件`DataChangeRecorderInnerInterceptor`
+- 新增查询条件方法 notLikeLeft 和 notLikeRight
+- 数据权限多表解析部分处理优化
+- 允许子类重写 orderBy 基础方法 gitee issues/I61F51
+- 新增Db类,调整 SimpleQuery 类
+- 新增脚本自动维护功能
+- 新增支持手动拦截器忽略策略,例如 `InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());`
+- 支持 PG 数据字段大写 ID 自增 fixed issues/I4T0YJ
+- 代码生成器重构完成,合并回 MP 核心代码库
+- 代码生成器增加是否生成service接口的开关
+
+
 ## [v3.5.2] 2022.06.01
 
 - 升级 mybatis 3.5.10

+ 15 - 5
build.gradle

@@ -2,7 +2,7 @@ import java.time.LocalDateTime
 
 allprojects {
     group = 'com.baomidou'
-    version = "3.5.2.6-SNAPSHOT"
+    version = "3.5.3.2-SNAPSHOT"
 }
 
 ext {
@@ -16,6 +16,7 @@ ext {
         mybatisSpringBootStarterVersion = '2.2.2',
         springVersion = '5.3.15',
         springBootVersion = '2.5.3',
+        springCloudVersion = '3.1.1',
         jsqlparserVersion = '4.4', // 4.5 有bug
         junitVersion = '5.9.0',
     ]
@@ -37,12 +38,11 @@ ext {
         "aspectjrt"                  : "org.aspectj:aspectjrt:1.9.9.1",
         "cglib"                      : "cglib:cglib:3.3.0",
         "imadcn"                     : "com.imadcn.framework:idworker:1.5.0",
-        "spring-cloud-commons"       : "org.springframework.cloud:spring-cloud-commons:3.0.3",
+        "spring-cloud-commons"       : "org.springframework.cloud:spring-cloud-commons:${springCloudVersion}",
 
         "javax.servlet-api"          : "javax.servlet:javax.servlet-api:4.0.1",
         "aspectjweaver"              : "org.aspectj:aspectjweaver:1.9.9.1",
         "slf4j-api"                  : "org.slf4j:slf4j-api:1.7.36",
-        "logback-classic"            : "ch.qos.logback:logback-classic:1.2.11",
         //copy
         "mybatis-spring-boot-starter": "org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisSpringBootStarterVersion}",
         //test
@@ -67,7 +67,13 @@ ext {
         //cache
         "mybatis-ehcache"            : "org.mybatis.caches:mybatis-ehcache:1.2.1",
         "mybatis-redis"              : "org.mybatis.caches:mybatis-redis:1.0.0-beta2",
-        "mybatis-caffeine"           : "org.mybatis.caches:mybatis-caffeine:1.0.0"
+        "mybatis-caffeine"           : "org.mybatis.caches:mybatis-caffeine:1.0.0",
+        //code generator
+        "velocity"            : "org.apache.velocity:velocity-engine-core:2.3",
+        "freemarker"          : "org.freemarker:freemarker:2.3.31",
+        "beetl"               : "com.ibeetl:beetl:3.7.0.RELEASE",
+        "swagger-annotations" : "io.swagger:swagger-annotations:1.6.2",
+        "enjoy"               : "com.jfinal:enjoy:5.0.0",
     ]
 }
 
@@ -95,7 +101,6 @@ subprojects {
         testImplementation "${lib["junit-jupiter"]}"
         testImplementation "org.mockito:mockito-junit-jupiter:4.6.1"
         testImplementation "${lib["lagarto"]}"
-        testImplementation "${lib["logback-classic"]}"
     }
 
     tasks.withType(JavaCompile) {
@@ -169,7 +174,12 @@ subprojects {
     test {
         dependsOn("cleanTest", "generatePomFileForMavenJavaPublication")
         useJUnitPlatform()
+        // 解决 IdeaProxyLambdaMetaTest 和 LambdaUtilsTest 测试失败问题
+        jvmArgs += ["--add-opens", "java.base/java.lang=ALL-UNNAMED",
+                    "--add-opens", "java.base/java.lang.invoke=ALL-UNNAMED"]
         exclude("**/phoenix/**")
+        exclude("**/postgresql/**")
+        exclude("**/generator/**")
     }
 
     task javadocJar(type: Jar) {

+ 2 - 7
changelog-temp.md

@@ -1,7 +1,2 @@
-- 多租户插件:多表join表名必需起别名,否则追加的过滤条件不带前缀
-- fix: InterceptorIgnore 不能过滤 selectKey 的问题
-- fix: 补全`mybatis-plus-boot-starter-test`遗漏配置
-- feat: 分页新增informix数据库支持
-- feat: 支持spring-boot 2.7以上版本
-- feat: 雪花id新增反解时间戳方法`Sequence#parseIdTimestamp`
-- feat: BaseMapper.selectCount生成语句加入中`AS total`
+fix: 修复在选择springdoc文档注释时entity描述异常
+fix: 在主键的`IdType`为`AUTO`的情况下,`Table#getAllInsertSqlColumnMaybeIf("xx.")`所生成sql错误问题

+ 1 - 1
gradle/wrapper/gradle-wrapper.properties

@@ -1,5 +1,5 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://downloads.gradle-dn.com/distributions/gradle-7.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists

BIN
libs/DmJdbcDriver-1.7.0.jar


BIN
libs/ojdbc8.jar


+ 6 - 1
mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/DbType.java

@@ -188,7 +188,12 @@ public enum DbType {
     INFORMIX("informix", "Informix数据库"),
 
     /**
-     * UNKONWN DB
+     * uxdb
+     */
+    UXDB("uxdb", "优炫数据库"),
+
+    /**
+     * UNKNOWN DB
      */
     OTHER("other", "其他数据库");
 

+ 8 - 2
mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/KeySequence.java

@@ -15,7 +15,13 @@
  */
 package com.baomidou.mybatisplus.annotation;
 
-import java.lang.annotation.*;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
 
 /**
  * 序列主键策略
@@ -27,7 +33,7 @@ import java.lang.annotation.*;
 @Documented
 @Inherited
 @Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
+@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
 public @interface KeySequence {
 
     /**

+ 0 - 4
mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/OrderBy.java

@@ -33,12 +33,8 @@ public @interface OrderBy {
      */
     boolean asc() default false;
 
-    @Deprecated
-    boolean isDesc() default true;
-
     /**
      * 数字越小越靠前
      */
     short sort() default Short.MAX_VALUE;
-
 }

+ 1 - 0
mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/Version.java

@@ -28,6 +28,7 @@ import java.lang.annotation.*;
  * java.util.Date,
  * java.sql.Timestamp,
  * java.time.LocalDateTime
+ * java.time.Instant
  *
  * @author TaoYu
  * @since 2016-01-23

+ 1 - 0
mybatis-plus-boot-starter-test/src/main/resources/META-INF/spring.factories

@@ -7,5 +7,6 @@ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
 org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
 org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration,\
+com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
 com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
 com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

+ 1 - 0
mybatis-plus-boot-starter-test/src/main/resources/META-INF/spring/com.baomidou.mybatisplus.test.autoconfigure.AutoConfigureMybatisPlus.imports

@@ -6,5 +6,6 @@ org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration
 org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
 org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
 org.springframework.boot.autoconfigure.sql.init.SqlInitializationAutoConfiguration
+com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration
 com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration
 com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

+ 1 - 0
mybatis-plus-boot-starter/build.gradle

@@ -13,6 +13,7 @@ dependencies {
     implementation "${lib.'spring-cloud-commons'}"
     testImplementation "org.springframework.boot:spring-boot-starter-test"
     testImplementation "${lib.'mybatis-spring-boot-starter'}"
+    testImplementation "${lib.h2}"
 }
 
 compileJava.dependsOn(processResources)

+ 54 - 0
mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/DdlApplicationRunner.java

@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.autoconfigure;
+
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.extension.ddl.DdlHelper;
+import com.baomidou.mybatisplus.extension.ddl.IDdl;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+
+import java.util.List;
+
+/**
+ * DDL 启动应用后执行
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+@Slf4j
+public class DdlApplicationRunner implements ApplicationRunner {
+    private List<IDdl> ddlList;
+
+    public DdlApplicationRunner(List<IDdl> ddlList) {
+        this.ddlList = ddlList;
+    }
+
+    @Override
+    public void run(ApplicationArguments args) throws Exception {
+        log.debug("  ...  DDL start create  ...  ");
+        if (ObjectUtils.isNotEmpty(ddlList)) {
+            /**
+             * 执行 SQL 脚本
+             * <p>多数据源情况可按需初始化</p>
+             */
+            ddlList.forEach(ddl -> ddl.runScript(dataSource -> DdlHelper.runScript(ddl.getDdlGenerator(),
+                    dataSource, ddl.getSqlFiles(), true)));
+        }
+        log.debug("  ...  DDL end create  ...  ");
+    }
+}

+ 7 - 9
mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/IdentifierGeneratorAutoConfiguration.java

@@ -17,6 +17,7 @@ package com.baomidou.mybatisplus.autoconfigure;
 
 import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.cloud.commons.util.InetUtils;
@@ -29,17 +30,14 @@ import org.springframework.context.annotation.Lazy;
  * @since 3.4.3
  */
 @Lazy
+@ConditionalOnClass(InetUtils.class)
+@ConditionalOnBean(InetUtils.class)
 @Configuration(proxyBeanMethods = false)
 public class IdentifierGeneratorAutoConfiguration {
 
-    @Configuration(proxyBeanMethods = false)
-    @ConditionalOnClass(InetUtils.class)
-    public static class InetUtilsAutoConfig {
-
-        @Bean
-        @ConditionalOnMissingBean
-        public IdentifierGenerator identifierGenerator(InetUtils inetUtils) {
-            return new DefaultIdentifierGenerator(inetUtils.findFirstNonLoopbackAddress());
-        }
+    @Bean
+    @ConditionalOnMissingBean
+    public IdentifierGenerator identifierGenerator(InetUtils inetUtils) {
+        return new DefaultIdentifierGenerator(inetUtils.findFirstNonLoopbackAddress());
     }
 }

+ 28 - 4
mybatis-plus-boot-starter/src/main/java/com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.java

@@ -18,11 +18,13 @@ package com.baomidou.mybatisplus.autoconfigure;
 
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
-import com.baomidou.mybatisplus.core.handlers.JoinTableInfoInitHandler;
+import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.injector.ISqlInjector;
+import com.baomidou.mybatisplus.extension.ddl.IDdl;
 import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.mapping.DatabaseIdProvider;
@@ -40,7 +42,12 @@ 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.BeanFactory;
+import org.springframework.beans.factory.BeanFactoryAware;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
@@ -58,6 +65,7 @@ import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
+import org.springframework.core.annotation.Order;
 import org.springframework.core.env.Environment;
 import org.springframework.core.io.Resource;
 import org.springframework.core.io.ResourceLoader;
@@ -69,7 +77,11 @@ import org.springframework.util.StringUtils;
 
 import javax.sql.DataSource;
 import java.beans.PropertyDescriptor;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -208,8 +220,10 @@ public class MybatisPlusAutoConfiguration implements InitializingBean {
         GlobalConfig globalConfig = this.properties.getGlobalConfig();
         // TODO 注入填充器
         this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
+        // TODO 注入注解控制器
+        this.getBeanThen(AnnotationHandler.class, globalConfig::setAnnotationHandler);
         // TODO 注入参与器
-        this.getBeanThen(JoinTableInfoInitHandler.class, globalConfig::setJoinTableInfoInitHandler);
+        this.getBeanThen(PostInitTableInfoHandler.class, globalConfig::setPostInitTableInfoHandler);
         // TODO 注入主键生成器
         this.getBeansThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerators(i));
         // TODO 注入sql注入器
@@ -379,4 +393,14 @@ public class MybatisPlusAutoConfiguration implements InitializingBean {
                 "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
         }
     }
+
+    @Order
+    @Bean
+    @ConditionalOnMissingBean
+    public DdlApplicationRunner ddlApplicationRunner(@Autowired(required = false) List<IDdl> ddlList) {
+        if (ObjectUtils.isEmpty(ddlList)) {
+            return null;
+        }
+        return new DdlApplicationRunner(ddlList);
+    }
 }

+ 27 - 0
mybatis-plus-boot-starter/src/test/java/com/baomidou/mybatisplus/autoconfigure/ApplicationTestStartSuccess.java

@@ -0,0 +1,27 @@
+package com.baomidou.mybatisplus.autoconfigure;
+
+import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
+import org.apache.ibatis.session.Configuration;
+import org.apache.ibatis.session.SqlSessionFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author miemie
+ * @since 2022-11-29
+ */
+@SpringBootApplication
+public class ApplicationTestStartSuccess {
+
+    public static void main(String[] args) {
+        ConfigurableApplicationContext context = SpringApplication.run(ApplicationTestStartSuccess.class, args);
+        SqlSessionFactory bean = context.getBean(SqlSessionFactory.class);
+        Configuration configuration = bean.getConfiguration();
+        GlobalConfig config = GlobalConfigUtils.getGlobalConfig(configuration);
+        assertThat(config.getIdentifierGenerator()).isNotNull();
+    }
+}

+ 5 - 0
mybatis-plus-boot-starter/src/test/resources/application.yml

@@ -0,0 +1,5 @@
+spring:
+    datasource:
+        driver-class-name: org.h2.Driver
+        url: jdbc:h2:mem:test;MODE=mysql;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
+        username: sa

+ 0 - 2
mybatis-plus-core/build.gradle

@@ -1,5 +1,3 @@
-apply plugin: 'org.jetbrains.kotlin.jvm'
-
 dependencies {
     api project(":mybatis-plus-annotation")
     api "${lib.'jsqlparser'}"

+ 3 - 2
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisMapperAnnotationBuilder.java

@@ -16,6 +16,7 @@
 package com.baomidou.mybatisplus.core;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy;
 import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
@@ -97,7 +98,7 @@ public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
             assistant.setCurrentNamespace(mapperName);
             parseCache();
             parseCacheRef();
-            InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
+            IgnoreStrategy ignoreStrategy = InterceptorIgnoreHelper.initSqlParserInfoCache(type);
             for (Method method : type.getMethods()) {
                 if (!canHaveStatement(method)) {
                     continue;
@@ -108,7 +109,7 @@ public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
                 }
                 try {
                     // TODO 加入 注解过滤缓存
-                    InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
+                    InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, mapperName, method);
                     parseStatement(method);
                 } catch (IncompleteElementException e) {
                     // TODO 使用 MybatisMethodResolver 而不是 MethodResolver

+ 3 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/MybatisParameterHandler.java

@@ -146,8 +146,10 @@ public class MybatisParameterHandler implements ParameterHandler {
                         } else {
                             throw new MybatisPlusException("Key type '" + keyType + "' not supported");
                         }
-                    } else {
+                    } else if (String.class.isAssignableFrom(keyType)) {
                         metaObject.setValue(keyProperty, identifierGenerator.nextId(entity).toString());
+                    } else {
+                        metaObject.setValue(keyProperty, identifierGenerator.nextId(entity));
                     }
                 } else if (idType.getKey() == IdType.ASSIGN_UUID.getKey()) {
                     metaObject.setValue(keyProperty, identifierGenerator.nextUUID(entity));

+ 11 - 2
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/AbstractWrapper.java

@@ -188,6 +188,16 @@ public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T,
         return likeValue(condition, LIKE, column, val, SqlLike.RIGHT);
     }
 
+    @Override
+    public Children notLikeLeft(boolean condition, R column, Object val) {
+        return likeValue(condition, NOT_LIKE, column, val, SqlLike.LEFT);
+    }
+
+    @Override
+    public Children notLikeRight(boolean condition, R column, Object val) {
+        return likeValue(condition, NOT_LIKE, column, val, SqlLike.RIGHT);
+    }
+
     @Override
     public Children between(boolean condition, R column, Object val1, Object val2) {
         return maybeDo(condition, () -> appendSqlSegments(columnToSqlSegment(column), BETWEEN,
@@ -345,8 +355,7 @@ public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T,
     }
 
     @Override
-    @SafeVarargs
-    public final Children orderBy(boolean condition, boolean isAsc, R column, R... columns) {
+    public Children orderBy(boolean condition, boolean isAsc, R column, R... columns) {
         return maybeDo(condition, () -> {
             final SqlKeyword mode = isAsc ? ASC : DESC;
             appendSqlSegments(ORDER_BY, columnToSqlSegment(columnSqlInjectFilter(column)), mode);

+ 34 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/interfaces/Compare.java

@@ -249,6 +249,40 @@ public interface Compare<Children, R> extends Serializable {
      */
     Children notLike(boolean condition, R column, Object val);
 
+    /**
+     * ignore
+     */
+    default Children notLikeLeft(R column, Object val) {
+        return notLikeLeft(true, column, val);
+    }
+
+    /**
+     * NOT LIKE '%值'
+     *
+     * @param condition
+     * @param column
+     * @param val
+     * @return children
+     */
+    Children notLikeLeft(boolean condition, R column, Object val);
+
+    /**
+     * ignore
+     */
+    default Children notLikeRight(R column, Object val) {
+        return notLikeRight(true, column, val);
+    }
+
+    /**
+     * NOT LIKE '值%'
+     *
+     * @param condition
+     * @param column
+     * @param val
+     * @return children
+     */
+    Children notLikeRight(boolean condition, R column, Object val);
+
     /**
      * ignore
      */

+ 8 - 2
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/query/LambdaQueryWrapper.java

@@ -20,10 +20,12 @@ import com.baomidou.mybatisplus.core.conditions.SharedString;
 import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
 import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
-import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
 import com.baomidou.mybatisplus.core.toolkit.Assert;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Predicate;
@@ -80,7 +82,11 @@ public class LambdaQueryWrapper<T> extends AbstractLambdaWrapper<T, LambdaQueryW
     @SafeVarargs
     @Override
     public final LambdaQueryWrapper<T> select(SFunction<T, ?>... columns) {
-        if (ArrayUtils.isNotEmpty(columns)) {
+        return select(Arrays.asList(columns));
+    }
+
+    public LambdaQueryWrapper<T> select(List<SFunction<T, ?>> columns) {
+        if (CollectionUtils.isNotEmpty(columns)) {
             this.sqlSelect.setStringValue(columnsToString(false, columns));
         }
         return typedThis;

+ 10 - 3
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/query/QueryWrapper.java

@@ -20,10 +20,12 @@ import com.baomidou.mybatisplus.core.conditions.SharedString;
 import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
 import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
-import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Predicate;
@@ -78,8 +80,13 @@ public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>>
     }
 
     @Override
-    public QueryWrapper<T> select(String... columns) {
-        if (ArrayUtils.isNotEmpty(columns)) {
+    @SafeVarargs
+    public final QueryWrapper<T> select(String... columns) {
+        return select(Arrays.asList(columns));
+    }
+
+    public QueryWrapper<T> select(List<String> columns) {
+        if (CollectionUtils.isNotEmpty(columns)) {
             this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
         }
         return typedThis;

+ 15 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/conditions/segments/ColumnSegment.java

@@ -1,3 +1,18 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.baomidou.mybatisplus.core.conditions.segments;
 
 import com.baomidou.mybatisplus.core.conditions.ISqlSegment;

+ 7 - 2
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/config/GlobalConfig.java

@@ -18,8 +18,9 @@ package com.baomidou.mybatisplus.core.config;
 import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.core.handlers.JoinTableInfoInitHandler;
+import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
@@ -76,10 +77,14 @@ public class GlobalConfig implements Serializable {
      * 元对象字段填充控制器
      */
     private MetaObjectHandler metaObjectHandler;
+    /**
+     * 注解控制器
+     */
+    private AnnotationHandler annotationHandler = new AnnotationHandler(){};
     /**
      * 参与 TableInfo 的初始化
      */
-    private JoinTableInfoInitHandler joinTableInfoInitHandler = new JoinTableInfoInitHandler() {
+    private PostInitTableInfoHandler postInitTableInfoHandler = new PostInitTableInfoHandler() {
     };
     /**
      * 主键生成器

+ 84 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/AnnotationHandler.java

@@ -0,0 +1,84 @@
+package com.baomidou.mybatisplus.core.handlers;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * @author 唐振超
+ * @since 2023-02-25
+ */
+public interface AnnotationHandler {
+
+    /**
+     * 从类上获取注解
+     *
+     * @param beanClass       类的class
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 注解
+     */
+    default <T extends Annotation> T getAnnotation(Class<?> beanClass, Class<T> annotationClass) {
+        return beanClass.getAnnotation(annotationClass);
+    }
+
+    /**
+     * 判断类上是否存在注解
+     *
+     * @param beanClass       类的class
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 是否包含该注解
+     */
+    default <T extends Annotation> boolean isAnnotationPresent(Class<?> beanClass, Class<T> annotationClass) {
+        return beanClass.isAnnotationPresent(annotationClass);
+    }
+
+    /**
+     * 从字段上获取注解
+     *
+     * @param field           字段
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 注解
+     */
+    default <T extends Annotation> T getAnnotation(Field field, Class<T> annotationClass) {
+        return field.getAnnotation(annotationClass);
+    }
+
+    /**
+     * 判断字段上是否存在注解
+     *
+     * @param field           字段
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 是否包含该注解
+     */
+    default <T extends Annotation> boolean isAnnotationPresent(Field field, Class<T> annotationClass) {
+        return field.isAnnotationPresent(annotationClass);
+    }
+
+    /**
+     * 从方法上获取注解
+     *
+     * @param method          方法
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 注解
+     */
+    default <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) {
+        return method.getAnnotation(annotationClass);
+    }
+
+    /**
+     * 判断方法上是否存在注解
+     *
+     * @param method           方法
+     * @param annotationClass 要获取的注解class
+     * @param <T>             具体注解
+     * @return 是否包含该注解
+     */
+    default <T extends Annotation> boolean isAnnotationPresent(Method method, Class<T> annotationClass) {
+        return method.isAnnotationPresent(annotationClass);
+    }
+}

+ 0 - 34
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/JoinTableInfoInitHandler.java

@@ -1,34 +0,0 @@
-package com.baomidou.mybatisplus.core.handlers;
-
-import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
-import com.baomidou.mybatisplus.core.metadata.TableInfo;
-import org.apache.ibatis.session.Configuration;
-
-/**
- * 初始化 TableInfo 同时进行一些操作
- *
- * @author miemie
- * @since 2022-09-20
- */
-public interface JoinTableInfoInitHandler {
-
-    /**
-     * 参与 TableFieldInfo 初始化
-     *
-     * @param fieldInfo     TableFieldInfo
-     * @param configuration Configuration
-     */
-    default void joinTableFieldInfo(TableFieldInfo fieldInfo, Configuration configuration) {
-        // ignore
-    }
-
-    /**
-     * 参与 TableInfo 初始化
-     *
-     * @param tableInfo     TableInfo
-     * @param configuration Configuration
-     */
-    default void joinTableInfo(TableInfo tableInfo, Configuration configuration) {
-        // ignore
-    }
-}

+ 49 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/handlers/PostInitTableInfoHandler.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.core.handlers;
+
+import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import org.apache.ibatis.session.Configuration;
+
+/**
+ * 初始化 TableInfo 同时进行一些操作
+ *
+ * @author miemie
+ * @since 2022-09-20
+ */
+public interface PostInitTableInfoHandler {
+
+    /**
+     * 参与 TableFieldInfo 初始化
+     *
+     * @param fieldInfo     TableFieldInfo
+     * @param configuration Configuration
+     */
+    default void postFieldInfo(TableFieldInfo fieldInfo, Configuration configuration) {
+        // ignore
+    }
+
+    /**
+     * 参与 TableInfo 初始化
+     *
+     * @param tableInfo     TableInfo
+     * @param configuration Configuration
+     */
+    default void postTableInfo(TableInfo tableInfo, Configuration configuration) {
+        // ignore
+    }
+}

+ 3 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/injector/methods/Insert.java

@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
 import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
 import org.apache.ibatis.executor.keygen.KeyGenerator;
@@ -64,7 +65,8 @@ public class Insert extends AbstractMethod {
                 /* 自增主键 */
                 keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                 keyProperty = tableInfo.getKeyProperty();
-                keyColumn = tableInfo.getKeyColumn();
+                // 去除转义符
+                keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
             } else if (null != tableInfo.getKeySequence()) {
                 keyGenerator = TableInfoHelper.genKeyGenerator(methodName, tableInfo, builderAssistant);
                 keyProperty = tableInfo.getKeyProperty();

+ 39 - 22
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableFieldInfo.java

@@ -15,16 +15,30 @@
  */
 package com.baomidou.mybatisplus.core.metadata;
 
-import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
+import com.baomidou.mybatisplus.annotation.OrderBy;
+import com.baomidou.mybatisplus.annotation.SqlCondition;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.Version;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.core.toolkit.Constants;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
-import lombok.*;
+import lombok.AccessLevel;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
 import org.apache.ibatis.mapping.ResultMapping;
 import org.apache.ibatis.reflection.Reflector;
 import org.apache.ibatis.session.Configuration;
-import org.apache.ibatis.type.*;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.TypeAliasRegistry;
+import org.apache.ibatis.type.TypeHandler;
+import org.apache.ibatis.type.TypeHandlerRegistry;
+import org.apache.ibatis.type.UnknownTypeHandler;
 
 import java.lang.reflect.Field;
 import java.util.Map;
@@ -179,12 +193,12 @@ public class TableFieldInfo implements Constants {
      * 全新的 存在 TableField 注解时使用的构造函数
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableField tableField,
+    public TableFieldInfo(GlobalConfig globalConfig, TableInfo tableInfo, Field field, TableField tableField,
                           Reflector reflector, boolean existTableLogic, boolean isOrderBy) {
-        this(dbConfig, tableInfo, field, tableField, reflector, existTableLogic);
+        this(globalConfig, tableInfo, field, tableField, reflector, existTableLogic);
         this.isOrderBy = isOrderBy;
         if (isOrderBy) {
-            initOrderBy(field);
+            initOrderBy(globalConfig.getAnnotationHandler().getAnnotation(field, OrderBy.class));
         }
     }
 
@@ -192,11 +206,13 @@ public class TableFieldInfo implements Constants {
      * 全新的 存在 TableField 注解时使用的构造函数
      */
     @SuppressWarnings({"unchecked", "rawtypes"})
-    public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableField tableField,
+    public TableFieldInfo(GlobalConfig globalConfig, TableInfo tableInfo, Field field, TableField tableField,
                           Reflector reflector, boolean existTableLogic) {
+
+        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
         field.setAccessible(true);
         this.field = field;
-        this.version = field.getAnnotation(Version.class) != null;
+        this.version = globalConfig.getAnnotationHandler().isAnnotationPresent(field, Version.class);
         this.property = field.getName();
         this.propertyType = reflector.getGetterType(this.property);
         this.isPrimitive = this.propertyType.isPrimitive();
@@ -244,7 +260,7 @@ public class TableFieldInfo implements Constants {
         this.el = el;
         int index = el.indexOf(COMMA);
         this.mapping = index > 0 ? el.substring(++index) : null;
-        this.initLogicDelete(dbConfig, field, existTableLogic);
+        this.initLogicDelete(globalConfig, field, existTableLogic);
 
         String column = tableField.value();
         if (StringUtils.isBlank(column)) {
@@ -306,33 +322,34 @@ public class TableFieldInfo implements Constants {
     /**
      * 不存在 TableField 注解时, 使用的构造函数
      */
-    public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, Reflector reflector,
+    public TableFieldInfo(GlobalConfig globalConfig, TableInfo tableInfo, Field field, Reflector reflector,
                           boolean existTableLogic, boolean isOrderBy) {
-        this(dbConfig, tableInfo, field, reflector, existTableLogic);
+        this(globalConfig, tableInfo, field, reflector, existTableLogic);
         this.isOrderBy = isOrderBy;
         if (isOrderBy) {
-            initOrderBy(field);
+            initOrderBy(globalConfig.getAnnotationHandler().getAnnotation(field, OrderBy.class));
         }
     }
 
     /**
      * 不存在 TableField 注解时, 使用的构造函数
      */
-    public TableFieldInfo(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, Reflector reflector,
+    public TableFieldInfo(GlobalConfig globalConfig, TableInfo tableInfo, Field field, Reflector reflector,
                           boolean existTableLogic) {
         field.setAccessible(true);
         this.field = field;
-        this.version = field.getAnnotation(Version.class) != null;
+        this.version = globalConfig.getAnnotationHandler().isAnnotationPresent(field, Version.class);
         this.property = field.getName();
         this.propertyType = reflector.getGetterType(this.property);
         this.isPrimitive = this.propertyType.isPrimitive();
         this.isCharSequence = StringUtils.isCharSequence(this.propertyType);
         this.el = this.property;
         this.mapping = null;
+        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
         this.insertStrategy = dbConfig.getInsertStrategy();
         this.updateStrategy = dbConfig.getUpdateStrategy();
         this.whereStrategy = dbConfig.getWhereStrategy();
-        this.initLogicDelete(dbConfig, field, existTableLogic);
+        this.initLogicDelete(globalConfig, field, existTableLogic);
 
         String column = this.property;
         if (tableInfo.isUnderCamel()) {
@@ -366,15 +383,14 @@ public class TableFieldInfo implements Constants {
     /**
      * 排序初始化
      *
-     * @param field 字段
+     * @param orderBy 排序注解
      */
-    private void initOrderBy(Field field) {
-        OrderBy orderBy = field.getAnnotation(OrderBy.class);
+    private void initOrderBy(OrderBy orderBy) {
         if (null != orderBy) {
             this.isOrderBy = true;
             this.orderBySort = orderBy.sort();
             String _orderBy = Constants.DESC;
-            if (orderBy.asc() || !orderBy.isDesc()) {
+            if (orderBy.asc()) {
                 _orderBy = Constants.ASC;
             }
             this.orderByType = _orderBy;
@@ -386,12 +402,13 @@ public class TableFieldInfo implements Constants {
     /**
      * 逻辑删除初始化
      *
-     * @param dbConfig 数据库全局配置
+     * @param globalConfig 全局配置
      * @param field    字段属性对象
      */
-    private void initLogicDelete(GlobalConfig.DbConfig dbConfig, Field field, boolean existTableLogic) {
+    private void initLogicDelete(GlobalConfig globalConfig, Field field, boolean existTableLogic) {
+        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
         /* 获取注解属性,逻辑处理字段 */
-        TableLogic tableLogic = field.getAnnotation(TableLogic.class);
+        TableLogic tableLogic = globalConfig.getAnnotationHandler().getAnnotation(field, TableLogic.class);
         if (null != tableLogic) {
             if (StringUtils.isNotBlank(tableLogic.value())) {
                 this.logicNotDeleteValue = tableLogic.value();

+ 6 - 26
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableInfo.java

@@ -28,7 +28,6 @@ import org.apache.ibatis.mapping.ResultFlag;
 import org.apache.ibatis.mapping.ResultMap;
 import org.apache.ibatis.mapping.ResultMapping;
 import org.apache.ibatis.reflection.Reflector;
-import org.apache.ibatis.reflection.SystemMetaObject;
 import org.apache.ibatis.session.Configuration;
 
 import java.lang.reflect.Constructor;
@@ -102,6 +101,7 @@ public class TableInfo implements Constants {
      * MybatisConfiguration 标记 (Configuration内存地址值)
      */
     @Getter
+    @Setter(AccessLevel.NONE)
     private Configuration configuration;
     /**
      * 是否开启下划线转驼峰
@@ -182,16 +182,6 @@ public class TableInfo implements Constants {
     @Getter
     private Reflector reflector;
 
-    /**
-     * @param entityType 实体类型
-     * @deprecated 3.4.4 {@link #TableInfo(Configuration, Class)}
-     */
-    @Deprecated
-    public TableInfo(Class<?> entityType) {
-        this.entityType = entityType;
-        this.reflector = SystemMetaObject.NULL_META_OBJECT.getReflectorFactory().findForClass(entityType);
-    }
-
     /**
      * @param configuration 配置对象
      * @param entityType    实体类型
@@ -216,18 +206,6 @@ public class TableInfo implements Constants {
         return currentNamespace + DOT + sqlMethod;
     }
 
-    /**
-     * @deprecated 3.4.4 {@link #TableInfo(Configuration, Class)}
-     * 设置 Configuration
-     */
-    @Deprecated
-    void setConfiguration(Configuration configuration) {
-        Assert.notNull(configuration, "Error: You need Initialize MybatisConfiguration !");
-        this.configuration = configuration;
-        this.underCamel = configuration.isMapUnderscoreToCamelCase();
-        this.reflector = configuration.getReflectorFactory().findForClass(this.entityType);
-    }
-
     /**
      * 是否有主键
      *
@@ -319,14 +297,16 @@ public class TableInfo implements Constants {
      *
      * @return sql 脚本片段
      */
-    public String getKeyInsertSqlColumn(final boolean batch, final boolean newLine) {
+    public String getKeyInsertSqlColumn(final boolean batch, final String prefix, final boolean newLine) {
         if (havePK()) {
+            final String newPrefix = prefix == null ? EMPTY : prefix;
+            final String prefixKeyProperty = newPrefix + keyProperty;
             if (idType == IdType.AUTO) {
                 if (batch) {
                     // 批量插入必须返回空自增情况下
                     return EMPTY;
                 }
-                return SqlScriptUtils.convertIf(keyColumn + COMMA, String.format("%s != null", keyProperty), newLine);
+                return SqlScriptUtils.convertIf(keyColumn + COMMA, String.format("%s != null", prefixKeyProperty), newLine);
             }
             return keyColumn + COMMA + (newLine ? NEWLINE : EMPTY);
         }
@@ -359,7 +339,7 @@ public class TableInfo implements Constants {
      */
     public String getAllInsertSqlColumnMaybeIf(final String prefix) {
         final String newPrefix = prefix == null ? EMPTY : prefix;
-        return getKeyInsertSqlColumn(false, true) + fieldList.stream().map(i -> i.getInsertSqlColumnMaybeIf(newPrefix))
+        return getKeyInsertSqlColumn(false, newPrefix, true) + fieldList.stream().map(i -> i.getInsertSqlColumnMaybeIf(newPrefix))
             .filter(Objects::nonNull).collect(joining(NEWLINE));
     }
 

+ 131 - 40
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/metadata/TableInfoHelper.java

@@ -15,11 +15,26 @@
  */
 package com.baomidou.mybatisplus.core.metadata;
 
-import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.OrderBy;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
-import com.baomidou.mybatisplus.core.handlers.JoinTableInfoInitHandler;
+import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
-import com.baomidou.mybatisplus.core.toolkit.*;
+import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
+import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
+import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
+import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import org.apache.ibatis.builder.MapperBuilderAssistant;
 import org.apache.ibatis.builder.StaticSqlSource;
 import org.apache.ibatis.executor.keygen.KeyGenerator;
@@ -34,7 +49,12 @@ import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.type.SimpleTypeRegistry;
 
 import java.lang.reflect.Field;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static java.util.stream.Collectors.toList;
@@ -48,7 +68,6 @@ import static java.util.stream.Collectors.toList;
  * @since 2016-09-09
  */
 public class TableInfoHelper {
-
     private static final Log logger = LogFactory.getLog(TableInfoHelper.class);
 
     /**
@@ -140,7 +159,7 @@ public class TableInfoHelper {
      * @param clazz 反射实体类
      * @return 数据库表反射信息
      */
-    public synchronized static TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
+    public static synchronized TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
         TableInfo targetTableInfo = TABLE_INFO_CACHE.get(clazz);
         final Configuration configuration = builderAssistant.getConfiguration();
         if (targetTableInfo != null) {
@@ -162,7 +181,7 @@ public class TableInfoHelper {
      * @param clazz 反射实体类
      * @return 数据库表反射信息
      */
-    private synchronized static TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {
+    private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {
         /* 没有获取到缓存信息,则初始化 */
         TableInfo tableInfo = new TableInfo(configuration, clazz);
         tableInfo.setCurrentNamespace(currentNamespace);
@@ -178,8 +197,7 @@ public class TableInfoHelper {
 
         /* 自动构建 resultMap */
         tableInfo.initResultMapIfNeed();
-        JoinTableInfoInitHandler joinTableInfoInitHandler = globalConfig.getJoinTableInfoInitHandler();
-        joinTableInfoInitHandler.joinTableInfo(tableInfo, configuration);
+        globalConfig.getPostInitTableInfoHandler().postTableInfo(tableInfo, configuration);
         TABLE_INFO_CACHE.put(clazz, tableInfo);
         TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);
 
@@ -201,7 +219,8 @@ public class TableInfoHelper {
     private static String[] initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
         /* 数据库全局配置 */
         GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
-        TableName table = clazz.getAnnotation(TableName.class);
+        AnnotationHandler annotationHandler = globalConfig.getAnnotationHandler();
+        TableName table = annotationHandler.getAnnotation(clazz, TableName.class);
 
         String tableName = clazz.getSimpleName();
         String tablePrefix = dbConfig.getTablePrefix();
@@ -243,7 +262,7 @@ public class TableInfoHelper {
 
         /* 开启了自定义 KEY 生成器 */
         if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) {
-            tableInfo.setKeySequence(clazz.getAnnotation(KeySequence.class));
+            tableInfo.setKeySequence(annotationHandler.getAnnotation(clazz, KeySequence.class));
         }
         return excludeProperty;
     }
@@ -281,17 +300,16 @@ public class TableInfoHelper {
      * @param tableInfo    数据库表反射信息
      */
     private static void initTableFields(Configuration configuration, Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
-        /* 数据库全局配置 */
-        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
-        JoinTableInfoInitHandler initTableInfoHandler = globalConfig.getJoinTableInfoInitHandler();
+        AnnotationHandler annotationHandler = globalConfig.getAnnotationHandler();
+        PostInitTableInfoHandler postInitTableInfoHandler = globalConfig.getPostInitTableInfoHandler();
         Reflector reflector = tableInfo.getReflector();
-        List<Field> list = getAllFields(clazz);
+        List<Field> list = getAllFields(clazz, annotationHandler);
         // 标记是否读取到主键
         boolean isReadPK = false;
         // 是否存在 @TableId 注解
-        boolean existTableId = isExistTableId(list);
+        boolean existTableId = isExistTableId(list, annotationHandler);
         // 是否存在 @TableLogic 注解
-        boolean existTableLogic = isExistTableLogic(list);
+        boolean existTableLogic = isExistTableLogic(list, annotationHandler);
 
         List<TableFieldInfo> fieldList = new ArrayList<>(list.size());
         for (Field field : list) {
@@ -300,45 +318,44 @@ public class TableInfoHelper {
             }
 
             boolean isPK = false;
-            boolean isOrderBy = field.getAnnotation(OrderBy.class) != null;
+            boolean isOrderBy = annotationHandler.getAnnotation(field, OrderBy.class) != null;
 
             /* 主键ID 初始化 */
             if (existTableId) {
-                TableId tableId = field.getAnnotation(TableId.class);
+                TableId tableId = annotationHandler.getAnnotation(field, TableId.class);
                 if (tableId != null) {
                     if (isReadPK) {
                         throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", clazz.getName());
                     }
 
-                    initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId);
+                    initTableIdWithAnnotation(globalConfig, tableInfo, field, tableId);
                     isPK = isReadPK = true;
                 }
             } else if (!isReadPK) {
-                isPK = isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field);
-
+                isPK = isReadPK = initTableIdWithoutAnnotation(globalConfig, tableInfo, field);
             }
 
             if (isPK) {
                 if (isOrderBy) {
-                    tableInfo.getOrderByFields().add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, true));
+                    tableInfo.getOrderByFields().add(new TableFieldInfo(globalConfig, tableInfo, field, reflector, existTableLogic, true));
                 }
                 continue;
             }
 
-            final TableField tableField = field.getAnnotation(TableField.class);
+            final TableField tableField = annotationHandler.getAnnotation(field, TableField.class);
 
             /* 有 @TableField 注解的字段初始化 */
             if (tableField != null) {
-                TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);
+                TableFieldInfo tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);
                 fieldList.add(tableFieldInfo);
-                initTableInfoHandler.joinTableFieldInfo(tableFieldInfo, configuration);
+                postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
                 continue;
             }
 
             /* 无 @TableField  注解的字段初始化 */
-            TableFieldInfo tableFieldInfo = new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);
+            TableFieldInfo tableFieldInfo = new TableFieldInfo(globalConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);
             fieldList.add(tableFieldInfo);
-            initTableInfoHandler.joinTableFieldInfo(tableFieldInfo, configuration);
+            postInitTableInfoHandler.postFieldInfo(tableFieldInfo, configuration);
         }
 
         /* 字段列表 */
@@ -355,11 +372,41 @@ public class TableInfoHelper {
      * 判断主键注解是否存在
      * </p>
      *
+     * @param clazz 实体类
      * @param list 字段列表
      * @return true 为存在 {@link TableId} 注解;
      */
-    public static boolean isExistTableId(List<Field> list) {
-        return list.stream().anyMatch(field -> field.isAnnotationPresent(TableId.class));
+    public static boolean isExistTableId(Class<?> clazz, List<Field> list) {
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
+        AnnotationHandler annotationHandler = GlobalConfigUtils.getGlobalConfig(tableInfo.getConfiguration()).getAnnotationHandler();
+        return isExistTableId(list, annotationHandler);
+    }
+
+    /**
+     * <p>
+     * 判断主键注解是否存在
+     * </p>
+     *
+     * @param list 字段列表
+     * @return true 为存在 {@link TableId} 注解;
+     */
+    public static boolean isExistTableId(List<Field> list, AnnotationHandler annotationHandler) {
+        return list.stream().anyMatch(field -> annotationHandler.isAnnotationPresent(field, TableId.class));
+    }
+
+    /**
+     * <p>
+     * 判断逻辑删除注解是否存在
+     * </p>
+     *
+     * @param clazz 实体类
+     * @param list 字段列表
+     * @return true 为存在 {@link TableLogic} 注解;
+     */
+    public static boolean isExistTableLogic(Class<?> clazz, List<Field> list) {
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
+        AnnotationHandler annotationHandler = GlobalConfigUtils.getGlobalConfig(tableInfo.getConfiguration()).getAnnotationHandler();
+        return isExistTableLogic(list, annotationHandler);
     }
 
     /**
@@ -370,8 +417,8 @@ public class TableInfoHelper {
      * @param list 字段列表
      * @return true 为存在 {@link TableLogic} 注解;
      */
-    public static boolean isExistTableLogic(List<Field> list) {
-        return list.stream().anyMatch(field -> field.isAnnotationPresent(TableLogic.class));
+    public static boolean isExistTableLogic(List<Field> list, AnnotationHandler annotationHandler) {
+        return list.stream().anyMatch(field -> annotationHandler.isAnnotationPresent(field, TableLogic.class));
     }
 
     /**
@@ -379,11 +426,27 @@ public class TableInfoHelper {
      * 判断排序注解是否存在
      * </p>
      *
+     * @param clazz 实体类
      * @param list 字段列表
      * @return true 为存在 {@link OrderBy} 注解;
      */
-    public static boolean isExistOrderBy(List<Field> list) {
-        return list.stream().anyMatch(field -> field.isAnnotationPresent(OrderBy.class));
+    public static boolean isExistOrderBy(Class<?> clazz, List<Field> list) {
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
+        AnnotationHandler annotationHandler = GlobalConfigUtils.getGlobalConfig(tableInfo.getConfiguration()).getAnnotationHandler();
+        return isExistOrderBy(list, annotationHandler);
+    }
+
+    /**
+     * <p>
+     * 判断排序注解是否存在
+     * </p>
+     *
+     * @param list 字段列表
+     * @param annotationHandler 注解处理类
+     * @return true 为存在 {@link OrderBy} 注解;
+     */
+    public static boolean isExistOrderBy(List<Field> list, AnnotationHandler annotationHandler) {
+        return list.stream().anyMatch(field -> annotationHandler.isAnnotationPresent(field, OrderBy.class));
     }
 
     /**
@@ -391,15 +454,16 @@ public class TableInfoHelper {
      * 主键属性初始化
      * </p>
      *
-     * @param dbConfig  全局配置信息
+     * @param globalConfig  全局配置信息
      * @param tableInfo 表信息
      * @param field     字段
      * @param tableId   注解
      */
-    private static void initTableIdWithAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field, TableId tableId) {
+    private static void initTableIdWithAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field, TableId tableId) {
+        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
         boolean underCamel = tableInfo.isUnderCamel();
         final String property = field.getName();
-        if (field.getAnnotation(TableField.class) != null) {
+        if (globalConfig.getAnnotationHandler().isAnnotationPresent(field, TableField.class)) {
             logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!",
                 property, tableInfo.getEntityType().getName()));
         }
@@ -430,6 +494,12 @@ public class TableInfoHelper {
             logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"",
                 property, tableInfo.getEntityType().getName()));
         }
+        if (StringUtils.isEmpty(tableId.value())) {
+            String columnFormat = dbConfig.getColumnFormat();
+            if (StringUtils.isNotBlank(columnFormat)) {
+                column = String.format(columnFormat, column);
+            }
+        }
         tableInfo.setKeyRelated(checkRelated(underCamel, property, column))
             .setKeyColumn(column)
             .setKeyProperty(property)
@@ -441,14 +511,16 @@ public class TableInfoHelper {
      * 主键属性初始化
      * </p>
      *
+     * @param globalConfig 全局配置
      * @param tableInfo 表信息
      * @param field     字段
      * @return true 继续下一个属性判断,返回 continue;
      */
-    private static boolean initTableIdWithoutAnnotation(GlobalConfig.DbConfig dbConfig, TableInfo tableInfo, Field field) {
+    private static boolean initTableIdWithoutAnnotation(GlobalConfig globalConfig, TableInfo tableInfo, Field field) {
+        GlobalConfig.DbConfig dbConfig = globalConfig.getDbConfig();
         final String property = field.getName();
         if (DEFAULT_ID_NAME.equalsIgnoreCase(property)) {
-            if (field.getAnnotation(TableField.class) != null) {
+            if (globalConfig.getAnnotationHandler().isAnnotationPresent(field, TableField.class)) {
                 logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!",
                     property, tableInfo.getEntityType().getName()));
             }
@@ -461,6 +533,10 @@ public class TableInfoHelper {
                 logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"",
                     property, tableInfo.getEntityType().getName()));
             }
+            String columnFormat = dbConfig.getColumnFormat();
+            if (StringUtils.isNotBlank(columnFormat)) {
+                column = String.format(columnFormat, column);
+            }
             tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column))
                 .setIdType(dbConfig.getIdType())
                 .setKeyColumn(column)
@@ -504,11 +580,26 @@ public class TableInfoHelper {
      * @return 属性集合
      */
     public static List<Field> getAllFields(Class<?> clazz) {
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(clazz);
+        AnnotationHandler annotationHandler = GlobalConfigUtils.getGlobalConfig(tableInfo.getConfiguration()).getAnnotationHandler();
+        return getAllFields(clazz, annotationHandler);
+    }
+
+    /**
+     * <p>
+     * 获取该类的所有属性列表
+     * </p>
+     *
+     * @param clazz             反射类
+     * @param annotationHandler 注解处理类
+     * @return 属性集合
+     */
+    public static List<Field> getAllFields(Class<?> clazz, AnnotationHandler annotationHandler) {
         List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
         return fieldList.stream()
             .filter(field -> {
                 /* 过滤注解非表字段属性 */
-                TableField tableField = field.getAnnotation(TableField.class);
+                TableField tableField = annotationHandler.getAnnotation(field, TableField.class);
                 return (tableField == null || tableField.exist());
             }).collect(toList());
     }

+ 35 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/plugins/IgnoreStrategy.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.core.plugins;
+
+import lombok.Builder;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Map;
+
+@Getter
+@Setter
+@Builder
+public class IgnoreStrategy {
+    private Boolean tenantLine;
+    private Boolean dynamicTableName;
+    private Boolean blockAttack;
+    private Boolean illegalSql;
+    private Boolean dataPermission;
+    private Map<String, Boolean> others;
+
+}

+ 58 - 40
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/plugins/InterceptorIgnoreHelper.java

@@ -17,8 +17,6 @@ package com.baomidou.mybatisplus.core.plugins;
 
 import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import com.baomidou.mybatisplus.core.toolkit.*;
-import lombok.Builder;
-import lombok.Data;
 import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
 
 import java.lang.reflect.Method;
@@ -38,7 +36,33 @@ public abstract class InterceptorIgnoreHelper {
      * SQL 解析缓存
      * key 可能是 mappedStatement 的 ID,也可能是 class 的 name
      */
-    private static final Map<String, InterceptorIgnoreCache> INTERCEPTOR_IGNORE_CACHE = new ConcurrentHashMap<>();
+    private static final Map<String, IgnoreStrategy> IGNORE_STRATEGY_CACHE = new ConcurrentHashMap<>();
+    /**
+     *  本地线程拦截器忽略策略缓存
+     */
+    private static final ThreadLocal<IgnoreStrategy> IGNORE_STRATEGY_LOCAL = new ThreadLocal<>();
+
+    /**
+     * 手动设置拦截器忽略执行策略,权限大于注解权限
+     * <p>
+     * InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().tenantLine(true).build());
+     * </p>
+     * <p>
+     * 注意,需要手动关闭调用方法 InterceptorIgnoreHelper.clearIgnoreStrategy();
+     * </p>
+     *
+     * @param ignoreStrategy {@link IgnoreStrategy}
+     */
+    public static void handle(IgnoreStrategy ignoreStrategy) {
+        IGNORE_STRATEGY_LOCAL.set(ignoreStrategy);
+    }
+
+    /**
+     * 清空本地忽略策略
+     */
+    public static void clearIgnoreStrategy() {
+        IGNORE_STRATEGY_LOCAL.remove();
+    }
 
     /**
      * 初始化缓存
@@ -47,12 +71,12 @@ public abstract class InterceptorIgnoreHelper {
      *
      * @param mapperClass Mapper Class
      */
-    public synchronized static InterceptorIgnoreCache initSqlParserInfoCache(Class<?> mapperClass) {
+    public synchronized static IgnoreStrategy initSqlParserInfoCache(Class<?> mapperClass) {
         InterceptorIgnore ignore = mapperClass.getAnnotation(InterceptorIgnore.class);
         if (ignore != null) {
             String key = mapperClass.getName();
-            InterceptorIgnoreCache cache = buildInterceptorIgnoreCache(key, ignore);
-            INTERCEPTOR_IGNORE_CACHE.put(key, cache);
+            IgnoreStrategy cache = buildIgnoreStrategy(key, ignore);
+            IGNORE_STRATEGY_CACHE.put(key, cache);
             return cache;
         }
         return null;
@@ -66,62 +90,67 @@ public abstract class InterceptorIgnoreHelper {
      * @param mapperAnnotation Mapper Class Name
      * @param method           Method
      */
-    public static void initSqlParserInfoCache(InterceptorIgnoreCache mapperAnnotation, String mapperClassName, Method method) {
-        InterceptorIgnore ignore = method.getAnnotation(InterceptorIgnore.class);
+    public static void initSqlParserInfoCache(IgnoreStrategy mapperAnnotation, String mapperClassName, Method method) {
+        InterceptorIgnore ignoreStrategy = method.getAnnotation(InterceptorIgnore.class);
         String key = mapperClassName.concat(StringPool.DOT).concat(method.getName());
         String name = mapperClassName.concat(StringPool.HASH).concat(method.getName());
-        if (ignore != null) {
-            InterceptorIgnoreCache methodCache = buildInterceptorIgnoreCache(name, ignore);
+        if (ignoreStrategy != null) {
+            IgnoreStrategy methodCache = buildIgnoreStrategy(name, ignoreStrategy);
             if (mapperAnnotation == null) {
-                INTERCEPTOR_IGNORE_CACHE.put(key, methodCache);
+                IGNORE_STRATEGY_CACHE.put(key, methodCache);
                 return;
             }
-            INTERCEPTOR_IGNORE_CACHE.put(key, chooseCache(mapperAnnotation, methodCache));
+            IGNORE_STRATEGY_CACHE.put(key, chooseCache(mapperAnnotation, methodCache));
         }
     }
 
     public static boolean willIgnoreTenantLine(String id) {
-        return willIgnore(id, InterceptorIgnoreCache::getTenantLine);
+        return willIgnore(id, IgnoreStrategy::getTenantLine);
     }
 
     public static boolean willIgnoreDynamicTableName(String id) {
-        return willIgnore(id, InterceptorIgnoreCache::getDynamicTableName);
+        return willIgnore(id, IgnoreStrategy::getDynamicTableName);
     }
 
     public static boolean willIgnoreBlockAttack(String id) {
-        return willIgnore(id, InterceptorIgnoreCache::getBlockAttack);
+        return willIgnore(id, IgnoreStrategy::getBlockAttack);
     }
 
     public static boolean willIgnoreIllegalSql(String id) {
-        return willIgnore(id, InterceptorIgnoreCache::getIllegalSql);
+        return willIgnore(id, IgnoreStrategy::getIllegalSql);
     }
 
     public static boolean willIgnoreDataPermission(String id) {
-        return willIgnore(id, InterceptorIgnoreCache::getDataPermission);
+        return willIgnore(id, IgnoreStrategy::getDataPermission);
     }
 
     public static boolean willIgnoreOthersByKey(String id, String key) {
         return willIgnore(id, i -> CollectionUtils.isNotEmpty(i.getOthers()) && i.getOthers().getOrDefault(key, false));
     }
 
-    public static boolean willIgnore(String id, Function<InterceptorIgnoreCache, Boolean> function) {
-        InterceptorIgnoreCache cache = INTERCEPTOR_IGNORE_CACHE.get(id);
-        if (cache == null && id.endsWith(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
+    public static boolean willIgnore(String id, Function<IgnoreStrategy, Boolean> function) {
+        // 1,优化获取本地忽略策略
+        IgnoreStrategy ignoreStrategy = IGNORE_STRATEGY_LOCAL.get();
+        if (null == ignoreStrategy) {
+            // 2,不存在取注解策略
+            ignoreStrategy = IGNORE_STRATEGY_CACHE.get(id);
+        }
+        if (ignoreStrategy == null && id.endsWith(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
             // 支持一下 selectKey
-            cache = INTERCEPTOR_IGNORE_CACHE.get(id.substring(0, id.length() - SelectKeyGenerator.SELECT_KEY_SUFFIX.length()));
+            ignoreStrategy = IGNORE_STRATEGY_CACHE.get(id.substring(0, id.length() - SelectKeyGenerator.SELECT_KEY_SUFFIX.length()));
         }
-        if (cache == null) {
-            cache = INTERCEPTOR_IGNORE_CACHE.get(id.substring(0, id.lastIndexOf(StringPool.DOT)));
+        if (ignoreStrategy == null) {
+            ignoreStrategy = IGNORE_STRATEGY_CACHE.get(id.substring(0, id.lastIndexOf(StringPool.DOT)));
         }
-        if (cache != null) {
-            Boolean apply = function.apply(cache);
+        if (ignoreStrategy != null) {
+            Boolean apply = function.apply(ignoreStrategy);
             return apply != null && apply;
         }
         return false;
     }
 
-    private static InterceptorIgnoreCache chooseCache(InterceptorIgnoreCache mapper, InterceptorIgnoreCache method) {
-        return InterceptorIgnoreCache.builder()
+    private static IgnoreStrategy chooseCache(IgnoreStrategy mapper, IgnoreStrategy method) {
+        return IgnoreStrategy.builder()
             .tenantLine(chooseBoolean(mapper.getTenantLine(), method.getTenantLine()))
             .dynamicTableName(chooseBoolean(mapper.getDynamicTableName(), method.getDynamicTableName()))
             .blockAttack(chooseBoolean(mapper.getBlockAttack(), method.getBlockAttack()))
@@ -131,8 +160,8 @@ public abstract class InterceptorIgnoreHelper {
             .build();
     }
 
-    private static InterceptorIgnoreCache buildInterceptorIgnoreCache(String name, InterceptorIgnore ignore) {
-        return InterceptorIgnoreCache.builder()
+    private static IgnoreStrategy buildIgnoreStrategy(String name, InterceptorIgnore ignore) {
+        return IgnoreStrategy.builder()
             .tenantLine(getBoolean("tenantLine", name, ignore.tenantLine()))
             .dynamicTableName(getBoolean("dynamicTableName", name, ignore.dynamicTableName()))
             .blockAttack(getBoolean("blockAttack", name, ignore.blockAttack()))
@@ -204,15 +233,4 @@ public abstract class InterceptorIgnoreHelper {
         methodKeys.forEach(k -> map.put(k, chooseBoolean(mapper.get(k), method.get(k))));
         return map;
     }
-
-    @Data
-    @Builder
-    public static class InterceptorIgnoreCache {
-        private Boolean tenantLine;
-        private Boolean dynamicTableName;
-        private Boolean blockAttack;
-        private Boolean illegalSql;
-        private Boolean dataPermission;
-        private Map<String, Boolean> others;
-    }
 }

+ 5 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/GlobalConfigUtils.java

@@ -17,6 +17,7 @@ package com.baomidou.mybatisplus.core.toolkit;
 
 import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
+import com.baomidou.mybatisplus.core.handlers.AnnotationHandler;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
 import com.baomidou.mybatisplus.core.injector.ISqlInjector;
@@ -108,6 +109,10 @@ public class GlobalConfigUtils {
         return Optional.ofNullable(getGlobalConfig(configuration).getMetaObjectHandler());
     }
 
+    public static Optional<AnnotationHandler> getAnnotationHandler(Configuration configuration) {
+        return Optional.ofNullable(getGlobalConfig(configuration).getAnnotationHandler());
+    }
+
     public static Class<?> getSuperMapperClass(Configuration configuration) {
         return getGlobalConfig(configuration).getSuperMapperClass();
     }

+ 1 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/LambdaUtils.java

@@ -55,7 +55,7 @@ public final class LambdaUtils {
         // 2. 反射读取
         try {
             Method method = func.getClass().getDeclaredMethod("writeReplace");
-            return new ReflectLambdaMeta((SerializedLambda) ReflectionKit.setAccessible(method).invoke(func));
+            return new ReflectLambdaMeta((SerializedLambda) ReflectionKit.setAccessible(method).invoke(func), func.getClass().getClassLoader());
         } catch (Throwable e) {
             // 3. 反射失败使用序列化的方式读取
             return new ShadowLambdaMeta(com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda.extract(func));

+ 2 - 12
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/Sequence.java

@@ -174,7 +174,7 @@ public class Sequence {
                 timestamp = tilNextMillis(lastTimestamp);
             }
         } else {
-            // 不同毫秒内,序列号置为 1 - 3 随机数
+            // 不同毫秒内,序列号置为 1 - 2 随机数
             sequence = ThreadLocalRandom.current().nextLong(1, 3);
         }
 
@@ -203,16 +203,6 @@ public class Sequence {
      * 反解id的时间戳部分
      */
     public static long parseIdTimestamp(long id) {
-        String s = Long.toBinaryString(id);
-        int x = 64 - s.length();
-        StringBuilder b = new StringBuilder();
-        for (int i = 0; i < x; i++) {
-            b.append("0");
-        }
-        s = b + s;
-        s = s.substring(1, 42);
-        long l = Long.parseUnsignedLong(s, 2);
-        l += twepoch;
-        return l;
+        return (id>>22)+twepoch;
     }
 }

+ 2 - 0
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/StringPool.java

@@ -64,6 +64,7 @@ public interface StringPool {
     String N = "n";
     String NO = "no";
     String NULL = "null";
+    String NUM = "NUM";
     String OFF = "off";
     String ON = "on";
     String PERCENT = "%";
@@ -81,6 +82,7 @@ public interface StringPool {
     String SINGLE_QUOTE = "'";
     String BACKTICK = "`";
     String SPACE = " ";
+    String SQL = "sql";
     String TILDA = "~";
     String LEFT_SQ_BRACKET = "[";
     String RIGHT_SQ_BRACKET = "]";

+ 5 - 4
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/StringUtils.java

@@ -59,9 +59,9 @@ public final class StringUtils {
     /**
      * 字符串去除空白内容
      *
-     * <ul> <li>\n 回车</li> <li>\t 水平制表符</li> <li>\s 空格</li> <li>\r 换行</li> </ul>
+     * <ul> <li>'"<>&*+=#-; sql注入黑名单</li> <li>\n 回车</li> <li>\t 水平制表符</li> <li>\s 空格</li> <li>\r 换行</li> </ul>
      */
-    private static final Pattern REPLACE_BLANK = Pattern.compile("\\s*|\t|\r|\n");
+    private static final Pattern REPLACE_BLANK = Pattern.compile("'|\"|\\<|\\>|&|\\*|\\+|=|#|-|;|\\s*|\t|\r|\n");
 
     /**
      * 判断字符串中是否全是空白字符
@@ -594,10 +594,11 @@ public final class StringUtils {
     public static String sqlInjectionReplaceBlank(String str) {
         if (SqlInjectionUtils.check(str)) {
             /**
-             * 存在 SQL 注入,去除空白内容
+             * 过滤sql黑名单字符,存在 SQL 注入,去除空白内容
              */
             Matcher matcher = REPLACE_BLANK.matcher(str);
-            return matcher.replaceAll("");
+            str = matcher.replaceAll("");
+
         }
         return str;
     }

+ 12 - 1
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/sql/SqlInjectionUtils.java

@@ -29,7 +29,7 @@ public class SqlInjectionUtils {
      * SQL语法检查正则:符合两个关键字(有先后顺序)才算匹配
      */
     private static final Pattern SQL_SYNTAX_PATTERN = Pattern.compile("(insert|delete|update|select|create|drop|truncate|grant|alter|deny|revoke|call|execute|exec|declare|show|rename|set)" +
-        ".+(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)", Pattern.CASE_INSENSITIVE);
+        "\\s+.*(into|from|set|where|table|database|view|index|on|cursor|procedure|trigger|for|password|union|and|or)|(select\\s*\\*\\s*from\\s+)", Pattern.CASE_INSENSITIVE);
     /**
      * 使用'、;或注释截断SQL检查正则
      */
@@ -46,4 +46,15 @@ public class SqlInjectionUtils {
         // 处理是否包含SQL注释字符 || 检查是否包含SQL注入敏感字符
         return SQL_COMMENT_PATTERN.matcher(value).find() || SQL_SYNTAX_PATTERN.matcher(value).find();
     }
+
+    /**
+     * 刪除字段转义符单引号双引号
+     *
+     * @param text 待处理字段
+     * @return
+     */
+    public static String removeEscapeCharacter(String text) {
+        Objects.nonNull(text);
+        return text.replaceAll("\"", "").replaceAll("'", "");
+    }
 }

+ 5 - 32
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/support/ReflectLambdaMeta.java

@@ -15,39 +15,24 @@
  */
 package com.baomidou.mybatisplus.core.toolkit.support;
 
-import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
 import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
-import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import lombok.extern.slf4j.Slf4j;
 
 import java.lang.invoke.SerializedLambda;
-import java.lang.reflect.Field;
 
 /**
  * Created by hcl at 2021/5/14
  */
 @Slf4j
 public class ReflectLambdaMeta implements LambdaMeta {
-    private static final Field FIELD_CAPTURING_CLASS;
-
-    static {
-        Field fieldCapturingClass;
-        try {
-            Class<SerializedLambda> aClass = SerializedLambda.class;
-            fieldCapturingClass = ReflectionKit.setAccessible(aClass.getDeclaredField("capturingClass"));
-        } catch (Throwable e) {
-            // 解决高版本 jdk 的问题 gitee: https://gitee.com/baomidou/mybatis-plus/issues/I4A7I5
-            log.warn(e.getMessage());
-            fieldCapturingClass = null;
-        }
-        FIELD_CAPTURING_CLASS = fieldCapturingClass;
-    }
-
     private final SerializedLambda lambda;
 
-    public ReflectLambdaMeta(SerializedLambda lambda) {
+    private final ClassLoader classLoader;
+
+    public ReflectLambdaMeta(SerializedLambda lambda, ClassLoader classLoader) {
         this.lambda = lambda;
+        this.classLoader = classLoader;
     }
 
     @Override
@@ -59,19 +44,7 @@ public class ReflectLambdaMeta implements LambdaMeta {
     public Class<?> getInstantiatedClass() {
         String instantiatedMethodType = lambda.getInstantiatedMethodType();
         String instantiatedType = instantiatedMethodType.substring(2, instantiatedMethodType.indexOf(StringPool.SEMICOLON)).replace(StringPool.SLASH, StringPool.DOT);
-        return ClassUtils.toClassConfident(instantiatedType, getCapturingClassClassLoader());
-    }
-
-    private ClassLoader getCapturingClassClassLoader() {
-        // 如果反射失败,使用默认的 classloader
-        if (FIELD_CAPTURING_CLASS == null) {
-            return null;
-        }
-        try {
-            return ((Class<?>) FIELD_CAPTURING_CLASS.get(lambda)).getClassLoader();
-        } catch (IllegalAccessException e) {
-            throw new MybatisPlusException(e);
-        }
+        return ClassUtils.toClassConfident(instantiatedType, this.classLoader);
     }
 
 }

+ 2 - 2
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/conditions/QueryWrapperTest.java

@@ -90,9 +90,9 @@ class QueryWrapperTest extends BaseWrapperTest {
             .or().gt("id", 1).ge("id", 1)
             .lt("id", 1).le("id", 1)
             .or().between("id", 1, 2).notBetween("id", 1, 3)
-            .like("id", 1).notLike("id", 1)
+            .like("id", 1).notLike("id", 1).notLikeLeft("id", 3).notLikeRight("id", 4)
             .or().likeLeft("id", 1).likeRight("id", 1);
-        logSqlWhere("测试 Compare 下的方法", queryWrapper, "(column1 = ? AND column0 = ? AND nullColumn IS NULL AND column1 = ? AND column0 = ? AND nullColumn IS NULL AND id = ? AND id <> ? OR id > ? AND id >= ? AND id < ? AND id <= ? OR id BETWEEN ? AND ? AND id NOT BETWEEN ? AND ? AND id LIKE ? AND id NOT LIKE ? OR id LIKE ? AND id LIKE ?)");
+        logSqlWhere("测试 Compare 下的方法", queryWrapper, "(column1 = ? AND column0 = ? AND nullColumn IS NULL AND column1 = ? AND column0 = ? AND nullColumn IS NULL AND id = ? AND id <> ? OR id > ? AND id >= ? AND id < ? AND id <= ? OR id BETWEEN ? AND ? AND id NOT BETWEEN ? AND ? AND id LIKE ? AND id NOT LIKE ? AND id NOT LIKE ? AND id NOT LIKE ? OR id LIKE ? AND id LIKE ?)");
         logParams(queryWrapper);
     }
 

+ 1 - 4
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/metadata/TableInfoTest.java

@@ -21,13 +21,10 @@ public class TableInfoTest {
         TableInfo tableInfo;
         Configuration configuration = new Configuration();
         configuration.setMapUnderscoreToCamelCase(true);
-        tableInfo = new TableInfo(Demo.class);
+        tableInfo = new TableInfo(configuration, Demo.class);
         Demo demo = tableInfo.newInstance();
         tableInfo.setPropertyValue(demo, "name", "test");
         assertThat(tableInfo.getPropertyValue(demo, "name")).isEqualTo("test");
-        assertThat(tableInfo.isUnderCamel()).isFalse();
-        assertThat(tableInfo.getReflector()).isNotNull();
-        tableInfo.setConfiguration(configuration);
         assertThat(tableInfo.isUnderCamel()).isTrue();
         assertThat(tableInfo.getReflector()).isNotNull();
         tableInfo = new TableInfo(configuration, Object.class);

+ 2 - 2
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/plugins/InterceptorIgnoreHelperTest.java

@@ -46,9 +46,9 @@ class InterceptorIgnoreHelperTest {
     }
 
     private void init(Class<?> clazz) {
-        InterceptorIgnoreHelper.InterceptorIgnoreCache cache = InterceptorIgnoreHelper.initSqlParserInfoCache(clazz);
+        IgnoreStrategy ignoreStrategy = InterceptorIgnoreHelper.initSqlParserInfoCache(clazz);
         for (Method method : clazz.getMethods()) {
-            InterceptorIgnoreHelper.initSqlParserInfoCache(cache, clazz.getName(), method);
+            InterceptorIgnoreHelper.initSqlParserInfoCache(ignoreStrategy, clazz.getName(), method);
         }
     }
 

+ 17 - 0
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/core/toolkit/sql/SqlInjectionUtilsTest.java

@@ -23,6 +23,23 @@ public class SqlInjectionUtilsTest {
         assertSql(false, "trigger");
         assertSql(true, "and name like '%s123%s'");
         assertSql(false, "convert(name using GBK)");
+
+        // 无空格
+        assertSql(false, "insert_into");
+        assertSql(true, "SELECT aa FROM user");
+        // 无空格
+        assertSql(true, "SELECT*FROM user");
+        // 左空格
+        assertSql(true, "SELECT *FROM user");
+        // 右空格
+        assertSql(true, "SELECT* FROM user");
+        // 左tab
+        assertSql(true, "SELECT                 *FROM user");
+        // 右tab
+        assertSql(true, "SELECT*        FROM user");
+        assertSql(false, "SELECT*FROMuser");
+        // 该字符串里包含 setT or
+        assertSql(false, "databaseType desc,orderNum desc)");
     }
 
     private void assertSql(boolean injection, String sql) {

+ 2 - 4
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/EncryptTest.java

@@ -15,10 +15,7 @@
  */
 package com.baomidou.mybatisplus.test;
 
-import com.baomidou.mybatisplus.annotation.FieldFill;
-import com.baomidou.mybatisplus.annotation.FieldStrategy;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.*;
 import com.baomidou.mybatisplus.core.MybatisConfiguration;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
@@ -61,6 +58,7 @@ class EncryptTest {
 
     @Data
     private static class Xx {
+        @TableId(type = IdType.AUTO)
         private Long id;
         @TableField(fill = FieldFill.INSERT)
         private String x1;

+ 2 - 2
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/metadata/TableInfoHelperTest.java

@@ -74,8 +74,8 @@ class TableInfoHelperTest {
 
     @Test
     void testIsExistTableId() {
-        Assertions.assertThat(TableInfoHelper.isExistTableId(Arrays.asList(ModelOne.class.getDeclaredFields()))).isTrue();
-        assertThat(TableInfoHelper.isExistTableId(Arrays.asList(ModelTwo.class.getDeclaredFields()))).isFalse();
+        Assertions.assertThat(TableInfoHelper.isExistTableId(ModelOne.class, Arrays.asList(ModelOne.class.getDeclaredFields()))).isTrue();
+        assertThat(TableInfoHelper.isExistTableId(ModelTwo.class, Arrays.asList(ModelTwo.class.getDeclaredFields()))).isFalse();
     }
 
     @Test

+ 1 - 1
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/pom/ReflectionKitTest.java

@@ -8,7 +8,7 @@ import org.junit.jupiter.api.Test;
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * 反工具类测试
+ * 反工具类测试
  */
 public class ReflectionKitTest {
 

+ 16 - 4
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/AbstractChainWrapper.java

@@ -134,6 +134,18 @@ public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainW
         return typedThis;
     }
 
+    @Override
+    public Children likeLeft(boolean condition, R column, Object val) {
+        getWrapper().likeLeft(condition, column, val);
+        return typedThis;
+    }
+
+    @Override
+    public Children likeRight(boolean condition, R column, Object val) {
+        getWrapper().likeRight(condition, column, val);
+        return typedThis;
+    }
+
     @Override
     public Children notLike(boolean condition, R column, Object val) {
         getWrapper().notLike(condition, column, val);
@@ -141,14 +153,14 @@ public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainW
     }
 
     @Override
-    public Children likeLeft(boolean condition, R column, Object val) {
-        getWrapper().likeLeft(condition, column, val);
+    public Children notLikeLeft(boolean condition, R column, Object val) {
+        getWrapper().notLikeLeft(condition, column, val);
         return typedThis;
     }
 
     @Override
-    public Children likeRight(boolean condition, R column, Object val) {
-        getWrapper().likeRight(condition, column, val);
+    public Children notLikeRight(boolean condition, R column, Object val) {
+        getWrapper().notLikeRight(condition, column, val);
         return typedThis;
     }
 

+ 24 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/ChainWrapper.java

@@ -17,6 +17,8 @@ package com.baomidou.mybatisplus.extension.conditions;
 
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
 
 /**
  * 此接口没特殊意义,只是为了减少实现类的代码量,主要在 AbstractChainWrapper 抽象类上实现
@@ -40,4 +42,26 @@ public interface ChainWrapper<T> {
      * @return Wrapper
      */
     Wrapper<T> getWrapper();
+
+    /**
+     * 获取当前实体Class
+     *
+     * @return Class
+     */
+    Class<T> getEntityClass();
+
+    /**
+     * 执行baseMapper操作,有baseMapper操作时使用baseMapper,没有时通过entityClass获取baseMapper,再使用
+     *
+     * @param function 操作
+     * @param <R>      返回值
+     * @return 结果
+     */
+    default <R> R execute(SFunction<BaseMapper<T>, R> function) {
+        BaseMapper<T> baseMapper = getBaseMapper();
+        if (baseMapper != null) {
+            return function.apply(baseMapper);
+        }
+        return SqlHelper.execute(getEntityClass(), function);
+    }
 }

+ 7 - 7
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/ChainQuery.java

@@ -15,13 +15,13 @@
  */
 package com.baomidou.mybatisplus.extension.conditions.query;
 
+import java.util.List;
+import java.util.Optional;
+
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.conditions.ChainWrapper;
 import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
 
-import java.util.List;
-import java.util.Optional;
-
 /**
  * 具有查询方法的定义
  *
@@ -36,7 +36,7 @@ public interface ChainQuery<T> extends ChainWrapper<T> {
      * @return 集合
      */
     default List<T> list() {
-        return getBaseMapper().selectList(getWrapper());
+        return execute(mapper -> mapper.selectList(getWrapper()));
     }
 
     /**
@@ -45,7 +45,7 @@ public interface ChainQuery<T> extends ChainWrapper<T> {
      * @return 单个
      */
     default T one() {
-        return getBaseMapper().selectOne(getWrapper());
+        return execute(mapper -> mapper.selectOne(getWrapper()));
     }
 
     /**
@@ -64,7 +64,7 @@ public interface ChainQuery<T> extends ChainWrapper<T> {
      * @return count
      */
     default Long count() {
-        return SqlHelper.retCount(getBaseMapper().selectCount(getWrapper()));
+        return execute(mapper -> SqlHelper.retCount(mapper.selectCount(getWrapper())));
     }
 
     /**
@@ -83,6 +83,6 @@ public interface ChainQuery<T> extends ChainWrapper<T> {
      * @return 分页数据
      */
     default <E extends IPage<T>> E page(E page) {
-        return getBaseMapper().selectPage(page, getWrapper());
+        return execute(mapper -> mapper.selectPage(page, getWrapper()));
     }
 }

+ 23 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/LambdaQueryChainWrapper.java

@@ -41,6 +41,24 @@ public class LambdaQueryChainWrapper<T> extends AbstractChainWrapper<T, SFunctio
         super.wrapperChildren = new LambdaQueryWrapper<>();
     }
 
+    public LambdaQueryChainWrapper(Class<T> entityClass) {
+        super();
+        this.baseMapper = null;
+        super.wrapperChildren = new LambdaQueryWrapper<>(entityClass);
+    }
+
+    public LambdaQueryChainWrapper(BaseMapper<T> baseMapper, T entity) {
+        super();
+        this.baseMapper = baseMapper;
+        super.wrapperChildren = new LambdaQueryWrapper<>(entity);
+    }
+
+    public LambdaQueryChainWrapper(BaseMapper<T> baseMapper, Class<T> entityClass) {
+        super();
+        this.baseMapper = baseMapper;
+        super.wrapperChildren = new LambdaQueryWrapper<>(entityClass);
+    }
+
     @SafeVarargs
     @Override
     public final LambdaQueryChainWrapper<T> select(SFunction<T, ?>... columns) {
@@ -63,4 +81,9 @@ public class LambdaQueryChainWrapper<T> extends AbstractChainWrapper<T, SFunctio
     public BaseMapper<T> getBaseMapper() {
         return baseMapper;
     }
+
+    @Override
+    public Class<T> getEntityClass() {
+        return super.wrapperChildren.getEntityClass();
+    }
 }

+ 21 - 2
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/query/QueryChainWrapper.java

@@ -15,6 +15,8 @@
  */
 package com.baomidou.mybatisplus.extension.conditions.query;
 
+import java.util.function.Predicate;
+
 import com.baomidou.mybatisplus.core.conditions.query.Query;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -22,8 +24,6 @@ import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
 import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
 import com.baomidou.mybatisplus.extension.conditions.AbstractChainWrapper;
 
-import java.util.function.Predicate;
-
 /**
  * Query Chain Wrapper
  *
@@ -35,10 +35,19 @@ public class QueryChainWrapper<T> extends AbstractChainWrapper<T, String, QueryC
     implements ChainQuery<T>, Query<QueryChainWrapper<T>, T, String> {
 
     private final BaseMapper<T> baseMapper;
+    private final Class<T> entityClass;
 
     public QueryChainWrapper(BaseMapper<T> baseMapper) {
         super();
         this.baseMapper = baseMapper;
+        this.entityClass = null;
+        super.wrapperChildren = new QueryWrapper<>();
+    }
+
+    public QueryChainWrapper(Class<T> entityClass) {
+        super();
+        this.baseMapper = null;
+        this.entityClass = entityClass;
         super.wrapperChildren = new QueryWrapper<>();
     }
 
@@ -63,4 +72,14 @@ public class QueryChainWrapper<T> extends AbstractChainWrapper<T, String, QueryC
     public BaseMapper<T> getBaseMapper() {
         return baseMapper;
     }
+
+    /**
+     * 获取当前实体Class
+     *
+     * @return Class
+     */
+    @Override
+    public Class<T> getEntityClass() {
+        return entityClass;
+    }
 }

+ 2 - 2
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/ChainUpdate.java

@@ -42,7 +42,7 @@ public interface ChainUpdate<T> extends ChainWrapper<T> {
      * @return 是否成功
      */
     default boolean update(T entity) {
-        return SqlHelper.retBool(getBaseMapper().update(entity, getWrapper()));
+        return execute(mapper -> SqlHelper.retBool(mapper.update(entity, getWrapper())));
     }
 
     /**
@@ -51,6 +51,6 @@ public interface ChainUpdate<T> extends ChainWrapper<T> {
      * @return 是否成功
      */
     default boolean remove() {
-        return SqlHelper.retBool(getBaseMapper().delete(getWrapper()));
+        return execute(mapper -> SqlHelper.retBool(mapper.delete(getWrapper())));
     }
 }

+ 11 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/LambdaUpdateChainWrapper.java

@@ -40,6 +40,12 @@ public class LambdaUpdateChainWrapper<T> extends AbstractChainWrapper<T, SFuncti
         super.wrapperChildren = new LambdaUpdateWrapper<>();
     }
 
+    public LambdaUpdateChainWrapper(Class<T> entityClass) {
+        super();
+        this.baseMapper = null;
+        super.wrapperChildren = new LambdaUpdateWrapper<>(entityClass);
+    }
+
     @Override
     public LambdaUpdateChainWrapper<T> set(boolean condition, SFunction<T, ?> column, Object val, String mapping) {
         wrapperChildren.set(condition, column, val, mapping);
@@ -61,4 +67,9 @@ public class LambdaUpdateChainWrapper<T> extends AbstractChainWrapper<T, SFuncti
     public BaseMapper<T> getBaseMapper() {
         return baseMapper;
     }
+
+    @Override
+    public Class<T> getEntityClass() {
+        return super.wrapperChildren.getEntityClass();
+    }
 }

+ 14 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/conditions/update/UpdateChainWrapper.java

@@ -32,10 +32,19 @@ public class UpdateChainWrapper<T> extends AbstractChainWrapper<T, String, Updat
     implements ChainUpdate<T>, Update<UpdateChainWrapper<T>, String> {
 
     private final BaseMapper<T> baseMapper;
+    private final Class<T> entityClass;
 
     public UpdateChainWrapper(BaseMapper<T> baseMapper) {
         super();
         this.baseMapper = baseMapper;
+        this.entityClass = null;
+        super.wrapperChildren = new UpdateWrapper<>();
+    }
+
+    public UpdateChainWrapper(Class<T> entityClass) {
+        super();
+        this.baseMapper = null;
+        this.entityClass = entityClass;
         super.wrapperChildren = new UpdateWrapper<>();
     }
 
@@ -60,4 +69,9 @@ public class UpdateChainWrapper<T> extends AbstractChainWrapper<T, String, Updat
     public BaseMapper<T> getBaseMapper() {
         return baseMapper;
     }
+
+    @Override
+    public Class<T> getEntityClass() {
+        return entityClass;
+    }
 }

+ 156 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlHelper.java

@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl;
+
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.extension.ddl.history.IDdlGenerator;
+import com.baomidou.mybatisplus.extension.ddl.history.MysqlDdlGenerator;
+import com.baomidou.mybatisplus.extension.ddl.history.OracleDdlGenerator;
+import com.baomidou.mybatisplus.extension.ddl.history.PostgreDdlGenerator;
+import com.baomidou.mybatisplus.extension.plugins.pagination.DialectFactory;
+import com.baomidou.mybatisplus.extension.plugins.pagination.dialects.*;
+import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.jdbc.ScriptRunner;
+import org.apache.ibatis.jdbc.SqlRunner;
+import org.springframework.core.io.ClassPathResource;
+
+import javax.sql.DataSource;
+import java.io.*;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * DDL 辅助类
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+@Slf4j
+public class DdlHelper {
+
+    /**
+     * 允许 SQL 脚本文件
+     *
+     * @param ddlGenerator DDL 生成器
+     * @param connection   数据库连接
+     * @param sqlFiles     SQL 文件列表
+     * @param autoCommit   字段提交事务
+     * @throws Exception
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, Connection connection, List<String> sqlFiles, boolean autoCommit) throws SQLException {
+        // 执行自定义 DDL 信息
+        final String jdbcUrl = connection.getMetaData().getURL();
+        final String schema = DdlHelper.getDatabase(jdbcUrl);
+        SqlRunner sqlRunner = new SqlRunner(connection);
+        ScriptRunner scriptRunner = getScriptRunner(connection, autoCommit);
+        if (null == ddlGenerator) {
+            ddlGenerator = getDdlGenerator(jdbcUrl);
+        }
+        if (!ddlGenerator.existTable(schema, sql -> {
+            try {
+                Map<String, Object> resultMap = sqlRunner.selectOne(sql);
+                if (null != resultMap && !StringPool.ZERO.equals(String.valueOf(resultMap.get(StringPool.NUM)))) {
+                    return true;
+                }
+            } catch (SQLException e) {
+                log.error("run script sql:{} , error: {}", sql, e.getMessage());
+            }
+            return false;
+        })) {
+            scriptRunner.runScript(new StringReader(ddlGenerator.createDdlHistory()));
+        }
+        // 执行 SQL 脚本
+        for (String sqlFile : sqlFiles) {
+            try {
+                List<Map<String, Object>> objectMap = sqlRunner.selectAll(ddlGenerator.selectDdlHistory(sqlFile, StringPool.SQL));
+                if (null == objectMap || objectMap.isEmpty()) {
+                    log.debug("run script file: {}", sqlFile);
+                    File file = new File(sqlFile);
+                    if (file.exists()) {
+                        scriptRunner.runScript(new FileReader(file));
+                    } else {
+                        scriptRunner.runScript(new InputStreamReader(getInputStream(sqlFile)));
+                    }
+                    sqlRunner.insert(ddlGenerator.insertDdlHistory(sqlFile, StringPool.SQL, getNowTime()));
+                }
+            } catch (Exception e) {
+                log.error("run script sql:{} , error: {} , Please check if the table `ddl_history` exists", sqlFile, e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 允许 SQL 脚本文件
+     *
+     * @param ddlGenerator DDL 生成器
+     * @param dataSource   数据源
+     * @param sqlFiles     SQL 文件列表
+     * @param autoCommit   字段提交事务
+     * @throws Exception
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, DataSource dataSource, List<String> sqlFiles, boolean autoCommit) {
+        try (Connection connection = dataSource.getConnection()) {
+            runScript(ddlGenerator, connection, sqlFiles, autoCommit);
+        } catch (Exception e) {
+            log.error("run script error: {}", e.getMessage());
+        }
+    }
+
+    public static InputStream getInputStream(String path) throws Exception {
+        return new ClassPathResource(path).getInputStream();
+    }
+
+    protected static String getNowTime() {
+        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
+    }
+
+    public static ScriptRunner getScriptRunner(Connection connection, boolean autoCommit) {
+        ScriptRunner scriptRunner = new ScriptRunner(connection);
+        scriptRunner.setAutoCommit(autoCommit);
+        scriptRunner.setStopOnError(true);
+        return scriptRunner;
+    }
+
+    protected static IDdlGenerator getDdlGenerator(String jdbcUrl) throws RuntimeException {
+        IDialect dialect = DialectFactory.getDialect(JdbcUtils.getDbType(jdbcUrl));
+        if (dialect instanceof MySqlDialect) {
+            return MysqlDdlGenerator.newInstance();
+        }
+        if (dialect instanceof PostgreDialect) {
+            return PostgreDdlGenerator.newInstance();
+        }
+        if (dialect instanceof OracleDialect || dialect instanceof Oracle12cDialect) {
+            return OracleDdlGenerator.newInstance();
+        }
+        throw new RuntimeException("The database is not supported");
+    }
+
+    public static String getDatabase(String jdbcUrl) {
+        String[] urlArr = jdbcUrl.split("://");
+        if (urlArr.length == 2) {
+            String[] dataArr = urlArr[1].split("/");
+            if (dataArr.length > 1) {
+                return dataArr[1].split("\\?")[0];
+            }
+        }
+        return null;
+    }
+}

+ 87 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScript.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl;
+
+import com.baomidou.mybatisplus.extension.ddl.history.IDdlGenerator;
+
+import javax.sql.DataSource;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.List;
+
+/**
+ * Ddl 脚本执行
+ *
+ * @author hubin
+ * @since 2021-07-23
+ */
+public class DdlScript {
+    private DataSource dataSource;
+    private IDdlGenerator ddlGenerator;
+    private boolean autoCommit;
+
+    public DdlScript(DataSource dataSource) {
+        this(dataSource, null);
+    }
+
+    public DdlScript(DataSource dataSource, IDdlGenerator ddlGenerator) {
+        this(dataSource, ddlGenerator, false);
+    }
+
+    public DdlScript(DataSource dataSource, IDdlGenerator ddlGenerator, boolean autoCommit) {
+        this.dataSource = dataSource;
+        this.ddlGenerator = ddlGenerator;
+        this.autoCommit = autoCommit;
+    }
+
+    public void run(List<String> sqlFiles) {
+        this.run(sqlFiles, this.autoCommit);
+    }
+
+    /**
+     * 执行 SQL 脚本文件
+     *
+     * @param sqlFiles SQL 脚本文件列表
+     */
+    public void run(List<String> sqlFiles, boolean autoCommit) {
+        DdlHelper.runScript(this.ddlGenerator, this.dataSource, sqlFiles, autoCommit);
+    }
+
+    /**
+     * 执行 SQL 脚本
+     *
+     * @param sqlScript SQL 脚本内容
+     * @throws Exception
+     */
+    public void run(String sqlScript) throws Exception {
+        this.run(new StringReader(sqlScript));
+    }
+
+    public void run(Reader reader) throws Exception {
+        this.run(reader, this.autoCommit);
+    }
+
+    /**
+     * 执行 SQL 脚本
+     *
+     * @param reader     SQL 脚本内容
+     * @param autoCommit 自动提交事务
+     * @throws Exception
+     */
+    public void run(Reader reader, boolean autoCommit) throws Exception {
+        DdlHelper.getScriptRunner(dataSource.getConnection(), autoCommit).runScript(reader);
+    }
+}

+ 53 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/IDdl.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl;
+
+import com.baomidou.mybatisplus.extension.ddl.history.IDdlGenerator;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * DDL 处理器
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+public interface IDdl {
+
+    /**
+     * 执行 SQL 脚本
+     *
+     * @param consumer 指定数据源执行
+     */
+    void runScript(Consumer<DataSource> consumer);
+
+    /**
+     * DDL 生成器
+     */
+    default IDdlGenerator getDdlGenerator() {
+        return null;
+    }
+
+    /**
+     * 执行 SQL 脚本
+     * <p>Resources.getResourceAsReader("db/test.sql")</p>
+     *
+     * @return SQL 脚本文件列表
+     */
+    List<String> getSqlFiles();
+}

+ 43 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/SimpleDdl.java

@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.sql.DataSource;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * 非多数据源 DDL 实现
+ *
+ * @author hubin
+ * @since 2021-09-23
+ */
+public class SimpleDdl implements IDdl {
+    @Autowired
+    private DataSource dataSource;
+
+    @Override
+    public void runScript(Consumer<DataSource> consumer) {
+        consumer.accept(dataSource);
+    }
+
+    @Override
+    public List<String> getSqlFiles() {
+        return null;
+    }
+}

+ 82 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/IDdlGenerator.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl.history;
+
+import java.util.function.Function;
+
+/**
+ * DDL 生成器接口
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+public interface IDdlGenerator {
+
+    /**
+     * 表是否存在
+     *
+     * @param databaseName    数据库名称
+     * @param executeFunction 执行判断函数
+     * @return exist or no
+     */
+    boolean existTable(String databaseName, Function<String, Boolean> executeFunction);
+
+    /**
+     * 返回 DDL_HISTORY 表名
+     *
+     * @return SQL
+     */
+    default String getDdlHistory() {
+        return "ddl_history";
+    }
+
+    /**
+     * ddl_history sql
+     *
+     * @return SQL
+     */
+    String createDdlHistory();
+
+    /**
+     * select ddl_history sql
+     *
+     * @param script Sql Script
+     * @param type   Execute Type
+     * @return SQL
+     */
+    default String selectDdlHistory(String script, String type) {
+        StringBuffer sql = new StringBuffer();
+        sql.append("SELECT version FROM ").append(getDdlHistory()).append(" WHERE script='").append(script);
+        sql.append("' AND type='").append(type).append("'");
+        return sql.toString();
+    }
+
+    /**
+     * insert ddl_history sql
+     *
+     * @param script  Sql Script
+     * @param type    Execute Type
+     * @param version Execute Version
+     * @return SQL
+     */
+    default String insertDdlHistory(String script, String type, String version) {
+        StringBuffer sql = new StringBuffer();
+        sql.append("INSERT INTO ").append(getDdlHistory()).append("(script,type,version) VALUES ('");
+        sql.append(script).append("','").append(type).append("','").append(version).append("')");
+        return sql.toString();
+    }
+
+}

+ 56 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/MysqlDdlGenerator.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl.history;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import java.util.function.Function;
+
+/**
+ * Mysql DDL 生成器
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+public class MysqlDdlGenerator implements IDdlGenerator {
+
+    public static IDdlGenerator newInstance() {
+        return new MysqlDdlGenerator();
+    }
+
+    @Override
+    public boolean existTable(String databaseName, Function<String, Boolean> executeFunction) {
+        StringBuffer sql = new StringBuffer();
+        sql.append("SELECT COUNT(1) AS NUM from INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='");
+        sql.append(getDdlHistory()).append("' AND TABLE_TYPE='BASE TABLE'");
+        if (StringUtils.isNotBlank(databaseName)) {
+            sql.append(" AND TABLE_SCHEMA='").append(databaseName).append("'");
+        }
+        return executeFunction.apply(sql.toString());
+    }
+
+    @Override
+    public String createDdlHistory() {
+        StringBuffer sql = new StringBuffer();
+        sql.append("CREATE TABLE IF NOT EXISTS `").append(getDdlHistory()).append("` (");
+        sql.append("`script` varchar(500) NOT NULL COMMENT '脚本',");
+        sql.append("`type` varchar(30) NOT NULL COMMENT '类型',");
+        sql.append("`version` varchar(30) NOT NULL COMMENT '版本',");
+        sql.append("PRIMARY KEY (`script`)");
+        sql.append(") COMMENT = 'DDL 版本';");
+        return sql.toString();
+    }
+}

+ 53 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/OracleDdlGenerator.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl.history;
+
+import java.util.function.Function;
+
+/**
+ * Oracle DDL 生成器
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+public class OracleDdlGenerator implements IDdlGenerator {
+
+    public static IDdlGenerator newInstance() {
+        return new OracleDdlGenerator();
+    }
+
+    @Override
+    public boolean existTable(String databaseName, Function<String, Boolean> executeFunction) {
+        return executeFunction.apply("SELECT COUNT(1) AS NUM FROM user_tables WHERE table_name='"
+                + getDdlHistory() + "'");
+    }
+
+    @Override
+    public String getDdlHistory() {
+        return "DDL_HISTORY";
+    }
+
+    @Override
+    public String createDdlHistory() {
+        StringBuffer sql = new StringBuffer();
+        sql.append("CREATE TABLE ").append(getDdlHistory()).append("(");
+        sql.append("script NVARCHAR2(500) NOT NULL,");
+        sql.append("type NVARCHAR2(30) NOT NULL,");
+        sql.append("version NVARCHAR2(30) NOT NULL");
+        sql.append(");");
+        return sql.toString();
+    }
+}

+ 69 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/history/PostgreDdlGenerator.java

@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.ddl.history;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+
+import java.util.function.Function;
+
+
+/**
+ * PostgreSQL DDL 生成器
+ *
+ * @author hubin
+ * @since 2021-06-22
+ */
+public class PostgreDdlGenerator implements IDdlGenerator {
+
+    public static IDdlGenerator newInstance() {
+        return new PostgreDdlGenerator();
+    }
+
+    @Override
+    public boolean existTable(String databaseName, Function<String, Boolean> executeFunction) {
+        StringBuffer sql = new StringBuffer();
+        sql.append("SELECT COUNT(1) AS NUM from INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='ddl_history' AND TABLE_TYPE='BASE TABLE'");
+        if (StringUtils.isNotBlank(this.getSchema())) {
+            sql.append(" AND TABLE_SCHEMA='").append(this.getSchema()).append("'");
+        }
+        return executeFunction.apply(sql.toString());
+    }
+
+    @Override
+    public String getDdlHistory() {
+        return "\"" + this.getSchema() + "\".\"ddl_history\"";
+    }
+
+    @Override
+    public String createDdlHistory() {
+        StringBuffer sql = new StringBuffer();
+        String ddlHistory = this.getDdlHistory();
+        sql.append("CREATE TABLE IF NOT EXISTS ").append(ddlHistory).append(" (");
+        sql.append("\"script\" varchar(500) NOT NULL,");
+        sql.append("\"type\" varchar(30) NOT NULL,");
+        sql.append("\"version\" varchar(30) NOT NULL");
+        sql.append(");");
+        sql.append("COMMENT ON COLUMN ").append(ddlHistory).append(".\"script\" IS '脚本';");
+        sql.append("COMMENT ON COLUMN ").append(ddlHistory).append(".\"type\" IS '类型';");
+        sql.append("COMMENT ON COLUMN ").append(ddlHistory).append(".\"version\" IS '版本';");
+        sql.append("COMMENT ON TABLE ").append(ddlHistory).append(" IS 'DDL 版本';");
+        return sql.toString();
+    }
+
+    protected String getSchema() {
+        return "public";
+    }
+}

+ 4 - 2
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/injector/methods/InsertBatchSomeColumn.java

@@ -21,6 +21,7 @@ import com.baomidou.mybatisplus.core.injector.AbstractMethod;
 import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.core.toolkit.sql.SqlInjectionUtils;
 import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
 import lombok.Setter;
 import lombok.experimental.Accessors;
@@ -102,7 +103,7 @@ public class InsertBatchSomeColumn extends AbstractMethod {
         KeyGenerator keyGenerator = NoKeyGenerator.INSTANCE;
         SqlMethod sqlMethod = SqlMethod.INSERT_ONE;
         List<TableFieldInfo> fieldList = tableInfo.getFieldList();
-        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, false) +
+        String insertSqlColumn = tableInfo.getKeyInsertSqlColumn(true, null, false) +
             this.filterTableFieldInfo(fieldList, predicate, TableFieldInfo::getInsertSqlColumn, EMPTY);
         String columnScript = LEFT_BRACKET + insertSqlColumn.substring(0, insertSqlColumn.length() - 1) + RIGHT_BRACKET;
         String insertSqlProperty = tableInfo.getKeyInsertSqlProperty(true, ENTITY_DOT, false) +
@@ -117,7 +118,8 @@ public class InsertBatchSomeColumn extends AbstractMethod {
                 /* 自增主键 */
                 keyGenerator = Jdbc3KeyGenerator.INSTANCE;
                 keyProperty = tableInfo.getKeyProperty();
-                keyColumn = tableInfo.getKeyColumn();
+                // 去除转义符
+                keyColumn = SqlInjectionUtils.removeEscapeCharacter(tableInfo.getKeyColumn());
             } else {
                 if (null != tableInfo.getKeySequence()) {
                     keyGenerator = TableInfoHelper.genKeyGenerator(this.methodName, tableInfo, builderAssistant);

+ 1 - 1
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/handler/DataPermissionHandler.java

@@ -30,7 +30,7 @@ public interface DataPermissionHandler {
      *
      * @param where             待执行 SQL Where 条件表达式
      * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
-     * @return JSqlParser 条件表达式
+     * @return JSqlParser 条件表达式,返回的条件表达式会覆盖原有的条件表达式
      */
     Expression getSqlSegment(Expression where, String mappedStatementId);
 }

+ 53 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/handler/MultiDataPermissionHandler.java

@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.plugins.handler;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+
+/**
+ * 支持多表的数据权限处理器
+ *
+ * @author houkunlin
+ * @since 3.5.2 +
+ */
+public interface MultiDataPermissionHandler extends DataPermissionHandler {
+    /**
+     * 为兼容旧版数据权限处理器,继承了 {@link DataPermissionHandler} 但是新的多表数据权限处理又不会调用此方法,因此标记过时
+     *
+     * @param where             待执行 SQL Where 条件表达式
+     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
+     * @return JSqlParser 条件表达式
+     * @deprecated 新的多表数据权限处理不会调用此方法,因此标记过时
+     */
+    @Deprecated
+    @Override
+    default Expression getSqlSegment(Expression where, String mappedStatementId) {
+        return where;
+    }
+
+    /**
+     * 获取数据权限 SQL 片段。
+     * <p>旧的 {@link MultiDataPermissionHandler#getSqlSegment(Expression, String)} 方法第一个参数包含所有的 where 条件信息,如果 return 了 null 会覆盖原有的 where 数据,</p>
+     * <p>新版的 {@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆盖原有的 where 数据,如果 return 了 null 则表示不追加任何 where 条件</p>
+     *
+     * @param table             所执行的数据库表信息,可以通过此参数获取表名和表别名
+     * @param where             原有的 where 条件信息
+     * @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
+     * @return JSqlParser 条件表达式,返回的条件表达式会拼接在原有的表达式后面(不会覆盖原有的表达式)
+     */
+    Expression getSqlSegment(final Table table, final Expression where, final String mappedStatementId);
+}

+ 424 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java

@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.plugins.inner;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import net.sf.jsqlparser.expression.*;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
+import net.sf.jsqlparser.expression.operators.relational.ExistsExpression;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.InExpression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.select.*;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 多表条件处理基对象,从原有的 {@link TenantLineInnerInterceptor} 拦截器中提取出来
+ *
+ * @author houkunlin
+ * @since 3.5.2
+ */
+@Data
+@NoArgsConstructor
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+@SuppressWarnings({"rawtypes"})
+public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
+
+    protected void processSelectBody(SelectBody selectBody, final String whereSegment) {
+        if (selectBody == null) {
+            return;
+        }
+        if (selectBody instanceof PlainSelect) {
+            processPlainSelect((PlainSelect) selectBody, whereSegment);
+        } else if (selectBody instanceof WithItem) {
+            WithItem withItem = (WithItem) selectBody;
+            processSelectBody(withItem.getSubSelect().getSelectBody(), whereSegment);
+        } else {
+            SetOperationList operationList = (SetOperationList) selectBody;
+            List<SelectBody> selectBodyList = operationList.getSelects();
+            if (CollectionUtils.isNotEmpty(selectBodyList)) {
+                selectBodyList.forEach(body -> processSelectBody(body, whereSegment));
+            }
+        }
+    }
+
+    /**
+     * delete update 语句 where 处理
+     */
+    protected Expression andExpression(Table table, Expression where, final String whereSegment) {
+        //获得where条件表达式
+        final Expression expression = buildTableExpression(table, where, whereSegment);
+        if (expression == null) {
+            return where;
+        }
+        if (where != null) {
+            if (where instanceof OrExpression) {
+                return new AndExpression(new Parenthesis(where), expression);
+            } else {
+                return new AndExpression(where, expression);
+            }
+        }
+        return expression;
+    }
+
+    /**
+     * 处理 PlainSelect
+     */
+    protected void processPlainSelect(final PlainSelect plainSelect, final String whereSegment) {
+        //#3087 github
+        List<SelectItem> selectItems = plainSelect.getSelectItems();
+        if (CollectionUtils.isNotEmpty(selectItems)) {
+            selectItems.forEach(selectItem -> processSelectItem(selectItem, whereSegment));
+        }
+
+        // 处理 where 中的子查询
+        Expression where = plainSelect.getWhere();
+        processWhereSubSelect(where, whereSegment);
+
+        // 处理 fromItem
+        FromItem fromItem = plainSelect.getFromItem();
+        List<Table> list = processFromItem(fromItem, whereSegment);
+        List<Table> mainTables = new ArrayList<>(list);
+
+        // 处理 join
+        List<Join> joins = plainSelect.getJoins();
+        if (CollectionUtils.isNotEmpty(joins)) {
+            mainTables = processJoins(mainTables, joins, whereSegment);
+        }
+
+        // 当有 mainTable 时,进行 where 条件追加
+        if (CollectionUtils.isNotEmpty(mainTables)) {
+            plainSelect.setWhere(builderExpression(where, mainTables, whereSegment));
+        }
+    }
+
+    private List<Table> processFromItem(FromItem fromItem, final String whereSegment) {
+        // 处理括号括起来的表达式
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        List<Table> mainTables = new ArrayList<>();
+        // 无 join 时的处理逻辑
+        if (fromItem instanceof Table) {
+            Table fromTable = (Table) fromItem;
+            mainTables.add(fromTable);
+        } else if (fromItem instanceof SubJoin) {
+            // SubJoin 类型则还需要添加上 where 条件
+            List<Table> tables = processSubJoin((SubJoin) fromItem, whereSegment);
+            mainTables.addAll(tables);
+        } else {
+            // 处理下 fromItem
+            processOtherFromItem(fromItem, whereSegment);
+        }
+        return mainTables;
+    }
+
+    /**
+     * 处理where条件内的子查询
+     * <p>
+     * 支持如下:
+     * <ol>
+     *     <li>in</li>
+     *     <li>=</li>
+     *     <li>&gt;</li>
+     *     <li>&lt;</li>
+     *     <li>&gt;=</li>
+     *     <li>&lt;=</li>
+     *     <li>&lt;&gt;</li>
+     *     <li>EXISTS</li>
+     *     <li>NOT EXISTS</li>
+     * </ol>
+     * <p>
+     * 前提条件:
+     * 1. 子查询必须放在小括号中
+     * 2. 子查询一般放在比较操作符的右边
+     *
+     * @param where where 条件
+     */
+    protected void processWhereSubSelect(Expression where, final String whereSegment) {
+        if (where == null) {
+            return;
+        }
+        if (where instanceof FromItem) {
+            processOtherFromItem((FromItem) where, whereSegment);
+            return;
+        }
+        if (where.toString().indexOf("SELECT") > 0) {
+            // 有子查询
+            if (where instanceof BinaryExpression) {
+                // 比较符号 , and , or , 等等
+                BinaryExpression expression = (BinaryExpression) where;
+                processWhereSubSelect(expression.getLeftExpression(), whereSegment);
+                processWhereSubSelect(expression.getRightExpression(), whereSegment);
+            } else if (where instanceof InExpression) {
+                // in
+                InExpression expression = (InExpression) where;
+                Expression inExpression = expression.getRightExpression();
+                if (inExpression instanceof SubSelect) {
+                    processSelectBody(((SubSelect) inExpression).getSelectBody(), whereSegment);
+                }
+            } else if (where instanceof ExistsExpression) {
+                // exists
+                ExistsExpression expression = (ExistsExpression) where;
+                processWhereSubSelect(expression.getRightExpression(), whereSegment);
+            } else if (where instanceof NotExpression) {
+                // not exists
+                NotExpression expression = (NotExpression) where;
+                processWhereSubSelect(expression.getExpression(), whereSegment);
+            } else if (where instanceof Parenthesis) {
+                Parenthesis expression = (Parenthesis) where;
+                processWhereSubSelect(expression.getExpression(), whereSegment);
+            }
+        }
+    }
+
+    protected void processSelectItem(SelectItem selectItem, final String whereSegment) {
+        if (selectItem instanceof SelectExpressionItem) {
+            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
+            final Expression expression = selectExpressionItem.getExpression();
+            if (expression instanceof SubSelect) {
+                processSelectBody(((SubSelect) expression).getSelectBody(), whereSegment);
+            } else if (expression instanceof Function) {
+                processFunction((Function) expression, whereSegment);
+            }
+        }
+    }
+
+    /**
+     * 处理函数
+     * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
+     * <p> fixed gitee pulls/141</p>
+     *
+     * @param function
+     */
+    protected void processFunction(Function function, final String whereSegment) {
+        ExpressionList parameters = function.getParameters();
+        if (parameters != null) {
+            parameters.getExpressions().forEach(expression -> {
+                if (expression instanceof SubSelect) {
+                    processSelectBody(((SubSelect) expression).getSelectBody(), whereSegment);
+                } else if (expression instanceof Function) {
+                    processFunction((Function) expression, whereSegment);
+                }
+            });
+        }
+    }
+
+    /**
+     * 处理子查询等
+     */
+    protected void processOtherFromItem(FromItem fromItem, final String whereSegment) {
+        // 去除括号
+        while (fromItem instanceof ParenthesisFromItem) {
+            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
+        }
+
+        if (fromItem instanceof SubSelect) {
+            SubSelect subSelect = (SubSelect) fromItem;
+            if (subSelect.getSelectBody() != null) {
+                processSelectBody(subSelect.getSelectBody(), whereSegment);
+            }
+        } else if (fromItem instanceof ValuesList) {
+            logger.debug("Perform a subQuery, if you do not give us feedback");
+        } else if (fromItem instanceof LateralSubSelect) {
+            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
+            if (lateralSubSelect.getSubSelect() != null) {
+                SubSelect subSelect = lateralSubSelect.getSubSelect();
+                if (subSelect.getSelectBody() != null) {
+                    processSelectBody(subSelect.getSelectBody(), whereSegment);
+                }
+            }
+        }
+    }
+
+    /**
+     * 处理 sub join
+     *
+     * @param subJoin subJoin
+     * @return Table subJoin 中的主表
+     */
+    private List<Table> processSubJoin(SubJoin subJoin, final String whereSegment) {
+        List<Table> mainTables = new ArrayList<>();
+        if (subJoin.getJoinList() != null) {
+            List<Table> list = processFromItem(subJoin.getLeft(), whereSegment);
+            mainTables.addAll(list);
+            mainTables = processJoins(mainTables, subJoin.getJoinList(), whereSegment);
+        }
+        return mainTables;
+    }
+
+    /**
+     * 处理 joins
+     *
+     * @param mainTables 可以为 null
+     * @param joins      join 集合
+     * @return List<Table> 右连接查询的 Table 列表
+     */
+    private List<Table> processJoins(List<Table> mainTables, List<Join> joins, final String whereSegment) {
+        // join 表达式中最终的主表
+        Table mainTable = null;
+        // 当前 join 的左表
+        Table leftTable = null;
+
+        if (mainTables == null) {
+            mainTables = new ArrayList<>();
+        } else if (mainTables.size() == 1) {
+            mainTable = mainTables.get(0);
+            leftTable = mainTable;
+        }
+
+        //对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
+        Deque<List<Table>> onTableDeque = new LinkedList<>();
+        for (Join join : joins) {
+            // 处理 on 表达式
+            FromItem joinItem = join.getRightItem();
+
+            // 获取当前 join 的表,subJoint 可以看作是一张表
+            List<Table> joinTables = null;
+            if (joinItem instanceof Table) {
+                joinTables = new ArrayList<>();
+                joinTables.add((Table) joinItem);
+            } else if (joinItem instanceof SubJoin) {
+                joinTables = processSubJoin((SubJoin) joinItem, whereSegment);
+            }
+
+            if (joinTables != null) {
+
+                // 如果是隐式内连接
+                if (join.isSimple()) {
+                    mainTables.addAll(joinTables);
+                    continue;
+                }
+
+                // 当前表是否忽略
+                Table joinTable = joinTables.get(0);
+
+                List<Table> onTables = null;
+                // 如果不要忽略,且是右连接,则记录下当前表
+                if (join.isRight()) {
+                    mainTable = joinTable;
+                    if (leftTable != null) {
+                        onTables = Collections.singletonList(leftTable);
+                    }
+                } else if (join.isInner()) {
+                    if (mainTable == null) {
+                        onTables = Collections.singletonList(joinTable);
+                    } else {
+                        onTables = Arrays.asList(mainTable, joinTable);
+                    }
+                    mainTable = null;
+                } else {
+                    onTables = Collections.singletonList(joinTable);
+                }
+
+                mainTables = new ArrayList<>();
+                if (mainTable != null) {
+                    mainTables.add(mainTable);
+                }
+
+                // 获取 join 尾缀的 on 表达式列表
+                Collection<Expression> originOnExpressions = join.getOnExpressions();
+                // 正常 join on 表达式只有一个,立刻处理
+                if (originOnExpressions.size() == 1 && onTables != null) {
+                    List<Expression> onExpressions = new LinkedList<>();
+                    onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables, whereSegment));
+                    join.setOnExpressions(onExpressions);
+                    leftTable = joinTable;
+                    continue;
+                }
+                // 表名压栈,忽略的表压入 null,以便后续不处理
+                onTableDeque.push(onTables);
+                // 尾缀多个 on 表达式的时候统一处理
+                if (originOnExpressions.size() > 1) {
+                    Collection<Expression> onExpressions = new LinkedList<>();
+                    for (Expression originOnExpression : originOnExpressions) {
+                        List<Table> currentTableList = onTableDeque.poll();
+                        if (CollectionUtils.isEmpty(currentTableList)) {
+                            onExpressions.add(originOnExpression);
+                        } else {
+                            onExpressions.add(builderExpression(originOnExpression, currentTableList, whereSegment));
+                        }
+                    }
+                    join.setOnExpressions(onExpressions);
+                }
+                leftTable = joinTable;
+            } else {
+                processOtherFromItem(joinItem, whereSegment);
+                leftTable = null;
+            }
+        }
+
+        return mainTables;
+    }
+
+    /**
+     * 处理条件
+     */
+    protected Expression builderExpression(Expression currentExpression, List<Table> tables, final String whereSegment) {
+        // 没有表需要处理直接返回
+        if (CollectionUtils.isEmpty(tables)) {
+            return currentExpression;
+        }
+        // 构造每张表的条件
+        List<Expression> expressions = tables.stream()
+            .map(item -> buildTableExpression(item, currentExpression, whereSegment))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
+
+        // 没有表需要处理直接返回
+        if (CollectionUtils.isEmpty(expressions)) {
+            return currentExpression;
+        }
+
+        // 注入的表达式
+        Expression injectExpression = expressions.get(0);
+        // 如果有多表,则用 and 连接
+        if (expressions.size() > 1) {
+            for (int i = 1; i < expressions.size(); i++) {
+                injectExpression = new AndExpression(injectExpression, expressions.get(i));
+            }
+        }
+
+        if (currentExpression == null) {
+            return injectExpression;
+        }
+        if (currentExpression instanceof OrExpression) {
+            return new AndExpression(new Parenthesis(currentExpression), injectExpression);
+        } else {
+            return new AndExpression(currentExpression, injectExpression);
+        }
+    }
+
+    /**
+     * 构建数据库表的查询条件
+     *
+     * @param table        表对象
+     * @param where        当前where条件
+     * @param whereSegment 所属Mapper对象全路径
+     * @return 需要拼接的新条件(不会覆盖原有的where条件,只会在原有条件上再加条件),为 null 则不加入新的条件
+     */
+    public abstract Expression buildTableExpression(final Table table, final Expression where, final String whereSegment);
+}

+ 194 - 207
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DataChangeRecorderInterceptor.java → mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DataChangeRecorderInnerInterceptor.java

@@ -1,45 +1,23 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.baomidou.mybatisplus.extension.plugins.inner;
 
-import static com.baomidou.mybatisplus.extension.plugins.inner.DataChangeRecorderInterceptor.convertDoubleQuotes;
-
-import java.io.Reader;
-import java.math.BigDecimal;
-import java.sql.Clob;
-import java.sql.Connection;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Properties;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.apache.ibatis.executor.statement.StatementHandler;
-import org.apache.ibatis.mapping.BoundSql;
-import org.apache.ibatis.mapping.MappedStatement;
-import org.apache.ibatis.mapping.ParameterMapping;
-import org.apache.ibatis.mapping.SqlCommandType;
-import org.apache.ibatis.plugin.Intercepts;
-import org.apache.ibatis.plugin.Signature;
-import org.apache.ibatis.reflection.MetaObject;
-import org.apache.ibatis.reflection.SystemMetaObject;
-import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.baomidou.mybatisplus.core.metadata.TableInfo;
 import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
 import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
-
 import lombok.Data;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.JdbcParameter;
@@ -49,13 +27,26 @@ import net.sf.jsqlparser.schema.Table;
 import net.sf.jsqlparser.statement.Statement;
 import net.sf.jsqlparser.statement.delete.Delete;
 import net.sf.jsqlparser.statement.insert.Insert;
-import net.sf.jsqlparser.statement.select.AllColumns;
-import net.sf.jsqlparser.statement.select.PlainSelect;
-import net.sf.jsqlparser.statement.select.Select;
-import net.sf.jsqlparser.statement.select.SelectExpressionItem;
-import net.sf.jsqlparser.statement.select.SelectItem;
+import net.sf.jsqlparser.statement.select.*;
 import net.sf.jsqlparser.statement.update.Update;
 import net.sf.jsqlparser.statement.update.UpdateSet;
+import org.apache.ibatis.executor.statement.StatementHandler;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.ParameterMapping;
+import org.apache.ibatis.mapping.SqlCommandType;
+import org.apache.ibatis.reflection.MetaObject;
+import org.apache.ibatis.reflection.SystemMetaObject;
+import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.sql.*;
+import java.util.Date;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * <p>
@@ -63,51 +54,46 @@ import net.sf.jsqlparser.statement.update.UpdateSet;
  * 默认会生成一条log,格式:
  * ----------------------INSERT LOG------------------------------
  * {
- *   "tableName": "h2user",
- *   "operation": "insert",
- *   "recordStatus": "true",
- *   "changedData": [
- *     {
- *       "LAST_UPDATED_DT": "null->2022-08-22 18:49:16.512",
- *       "TEST_ID": "null->1561666810058739714",
- *       "AGE": "null->THREE"
- *     }
- *   ],
- *   "cost(ms)": 0
+ * "tableName": "h2user",
+ * "operation": "insert",
+ * "recordStatus": "true",
+ * "changedData": [
+ * {
+ * "LAST_UPDATED_DT": "null->2022-08-22 18:49:16.512",
+ * "TEST_ID": "null->1561666810058739714",
+ * "AGE": "null->THREE"
+ * }
+ * ],
+ * "cost(ms)": 0
  * }
  * </p>
  * <p>
- *  * ----------------------UPDATE LOG------------------------------
- *
+ * * ----------------------UPDATE LOG------------------------------
+ * <p>
  * {
- *   "tableName": "h2user",
- *   "operation": "update",
- *   "recordStatus": "true",
- *   "changedData": [
- *     {
- *       "TEST_ID": "102",
- *       "AGE": "2->THREE",
- *       "FIRSTNAME": "DOU.HAO->{\"json\":\"abc\"}",
- *       "LAST_UPDATED_DT": "null->2022-08-22 18:49:16.512"
- *     }
- *   ],
- *   "cost(ms)": 0
+ * "tableName": "h2user",
+ * "operation": "update",
+ * "recordStatus": "true",
+ * "changedData": [
+ * {
+ * "TEST_ID": "102",
+ * "AGE": "2->THREE",
+ * "FIRSTNAME": "DOU.HAO->{\"json\":\"abc\"}",
+ * "LAST_UPDATED_DT": "null->2022-08-22 18:49:16.512"
+ * }
+ * ],
+ * "cost(ms)": 0
  * }
  * </p>
  *
  * @author yuxiaobin
  * @date 2022-8-21
  */
-@Intercepts({
-    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
-})
-public class DataChangeRecorderInterceptor implements InnerInterceptor {
-
+public class DataChangeRecorderInnerInterceptor implements InnerInterceptor {
+    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
     @SuppressWarnings("unused")
     public static final String IGNORED_TABLE_COLUMN_PROPERTIES = "ignoredTableColumns";
 
-    private final Logger logger = LoggerFactory.getLogger("DataChangeRecorder");
-
     private final Map<String, Set<String>> ignoredTableColumns = new ConcurrentHashMap<>();
     private final Set<String> ignoreAllColumns = new HashSet<>();//全部表的这些字段名,INSERT/UPDATE都忽略,delete暂时保留
 
@@ -515,13 +501,6 @@ public class DataChangeRecorderInterceptor implements InnerInterceptor {
         }
     }
 
-    public static String convertDoubleQuotes(Object obj) {
-        if (obj == null) {
-            return null;
-        }
-        return obj.toString().replace("\"", "\\\"");
-    }
-
     @Data
     public static class OperationResult {
 
@@ -585,160 +564,168 @@ public class DataChangeRecorderInterceptor implements InnerInterceptor {
             sb.append("}");
             return sb.toString();
         }
-
     }
 
-}
-
-
-@Data
-class Columns2SelectItemsResult {
+    @Data
+    public static class Columns2SelectItemsResult {
 
-    private Column pk;
-    /**
-     * all column with additional columns: ID, etc.
-     */
-    private List<SelectItem> selectItems;
-    /**
-     * newly added column count from meta data.
-     */
-    private int additionalItemCount;
+        private Column pk;
+        /**
+         * all column with additional columns: ID, etc.
+         */
+        private List<SelectItem> selectItems;
+        /**
+         * newly added column count from meta data.
+         */
+        private int additionalItemCount;
 
-    public static Columns2SelectItemsResult build(List<SelectItem> selectItems, int additionalItemCount) {
-        Columns2SelectItemsResult result = new Columns2SelectItemsResult();
-        result.setSelectItems(selectItems);
-        result.setAdditionalItemCount(additionalItemCount);
-        return result;
+        public static Columns2SelectItemsResult build(List<SelectItem> selectItems, int additionalItemCount) {
+            Columns2SelectItemsResult result = new Columns2SelectItemsResult();
+            result.setSelectItems(selectItems);
+            result.setAdditionalItemCount(additionalItemCount);
+            return result;
+        }
     }
 
-}
+    @Data
+    public static class OriginalDataObj {
 
-@Data
-class OriginalDataObj {
+        private List<DataChangedRecord> originalDataObj;
 
-    private List<DataChangedRecord> originalDataObj;
+        public boolean isEmpty() {
+            return originalDataObj == null || originalDataObj.isEmpty();
+        }
 
-    public boolean isEmpty() {
-        return originalDataObj == null || originalDataObj.isEmpty();
     }
 
-}
-
-@Data
-class DataColumnChangeResult {
-
-    private String columnName;
-    private Object originalValue;
-    private Object updateValue;
-
-    @SuppressWarnings("rawtypes")
-    public boolean isDataChanged(Object updateValue) {
-        if (!Objects.equals(originalValue, updateValue)) {
-            if (updateValue instanceof Number && originalValue instanceof Number) {
-                BigDecimal update = new BigDecimal(updateValue.toString());
-                BigDecimal original = new BigDecimal(originalValue.toString());
-                return update.compareTo(original) != 0;
-            }
-            if (updateValue instanceof Date && originalValue instanceof Date) {
-                Date update = (Date) updateValue;
-                Date original = (Date) originalValue;
-                return update.compareTo(original) != 0;
+    @Data
+    public static class DataColumnChangeResult {
+
+        private String columnName;
+        private Object originalValue;
+        private Object updateValue;
+
+        @SuppressWarnings("rawtypes")
+        public boolean isDataChanged(Object updateValue) {
+            if (!Objects.equals(originalValue, updateValue)) {
+                if (updateValue instanceof Number && originalValue instanceof Number) {
+                    BigDecimal update = new BigDecimal(updateValue.toString());
+                    BigDecimal original = new BigDecimal(originalValue.toString());
+                    return update.compareTo(original) != 0;
+                }
+                if (updateValue instanceof Date && originalValue instanceof Date) {
+                    Date update = (Date) updateValue;
+                    Date original = (Date) originalValue;
+                    return update.compareTo(original) != 0;
+                }
+                if (originalValue instanceof Clob) {
+                    String originalStr = convertClob((Clob) originalValue);
+                    setOriginalValue(originalStr);
+                    return !originalStr.equals(updateValue);
+                }
+                return true;
             }
-            if (originalValue instanceof Clob) {
-                String originalStr = convertClob((Clob) originalValue);
-                setOriginalValue(originalStr);
-                return !originalStr.equals(updateValue);
+            if (originalValue instanceof Comparable) {
+                Comparable original = (Comparable) originalValue;
+                Comparable update = (Comparable) updateValue;
+                return original.compareTo(update) != 0;
             }
-            return true;
-        }
-        if (originalValue instanceof Comparable) {
-            Comparable original = (Comparable) originalValue;
-            Comparable update = (Comparable) updateValue;
-            return original.compareTo(update) != 0;
+            return false;
         }
-        return false;
-    }
 
-    public static String convertClob(Clob clobObj) {
-        try {
-            return clobObj.getSubString(0, (int) clobObj.length());
-        } catch (Exception e) {
-            try (Reader is = clobObj.getCharacterStream()) {
-                char[] chars = new char[64];
-                int readChars;
-                StringBuilder sb = new StringBuilder();
-                while ((readChars = is.read(chars)) != -1) {
-                    sb.append(chars, 0, readChars);
+        public static String convertClob(Clob clobObj) {
+            try {
+                return clobObj.getSubString(0, (int) clobObj.length());
+            } catch (Exception e) {
+                try (Reader is = clobObj.getCharacterStream()) {
+                    char[] chars = new char[64];
+                    int readChars;
+                    StringBuilder sb = new StringBuilder();
+                    while ((readChars = is.read(chars)) != -1) {
+                        sb.append(chars, 0, readChars);
+                    }
+                    return sb.toString();
+                } catch (Exception e2) {
+                    //ignored
+                    return "unknown clobObj";
                 }
-                return sb.toString();
-            } catch (Exception e2) {
-                //ignored
-                return "unknown clobObj";
             }
         }
-    }
 
-    public static DataColumnChangeResult constrcutByUpdateVal(String columnName, Object updateValue) {
-        DataColumnChangeResult res = new DataColumnChangeResult();
-        res.setColumnName(columnName);
-        res.setUpdateValue(updateValue);
-        return res;
-    }
+        public static DataColumnChangeResult constrcutByUpdateVal(String columnName, Object updateValue) {
+            DataColumnChangeResult res = new DataColumnChangeResult();
+            res.setColumnName(columnName);
+            res.setUpdateValue(updateValue);
+            return res;
+        }
 
-    public static DataColumnChangeResult constrcutByOriginalVal(String columnName, Object originalValue) {
-        DataColumnChangeResult res = new DataColumnChangeResult();
-        res.setColumnName(columnName);
-        res.setOriginalValue(originalValue);
-        return res;
-    }
+        public static DataColumnChangeResult constrcutByOriginalVal(String columnName, Object originalValue) {
+            DataColumnChangeResult res = new DataColumnChangeResult();
+            res.setColumnName(columnName);
+            res.setOriginalValue(originalValue);
+            return res;
+        }
 
-    public String generateDataStr() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("\"").append(columnName).append("\"").append(":").append("\"").append(convertDoubleQuotes(originalValue)).append("->").append(convertDoubleQuotes(updateValue)).append("\"").append(",");
-        return sb.toString();
+        public String generateDataStr() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("\"").append(columnName).append("\"").append(":").append("\"").append(convertDoubleQuotes(originalValue)).append("->").append(convertDoubleQuotes(updateValue)).append("\"").append(",");
+            return sb.toString();
+        }
+
+        public String convertDoubleQuotes(Object obj) {
+            if (obj == null) {
+                return null;
+            }
+            return obj.toString().replace("\"", "\\\"");
+        }
     }
-}
 
-@Data
-class DataChangedRecord {
-    private String pkColumnName;
-    private Object pkColumnVal;
-    private List<DataColumnChangeResult> originalColumnDatas;
-    private List<DataColumnChangeResult> updatedColumns;
-
-    public boolean hasUpdate(Map<String, Object> columnNameValMap, Set<String> ignoredColumns, Set<String> ignoreAllColumns) {
-        if (originalColumnDatas == null) {
-            return true;
-        }
-        boolean hasUpdate = false;
-        updatedColumns = new ArrayList<>(originalColumnDatas.size());
-        for (DataColumnChangeResult originalColumn : originalColumnDatas) {
-            final String columnName = originalColumn.getColumnName().toUpperCase();
-            if (ignoredColumns != null && ignoredColumns.contains(columnName) || ignoreAllColumns.contains(columnName)) {
-                continue;
+    @Data
+    public static class DataChangedRecord {
+        private String pkColumnName;
+        private Object pkColumnVal;
+        private List<DataColumnChangeResult> originalColumnDatas;
+        private List<DataColumnChangeResult> updatedColumns;
+
+        public boolean hasUpdate(Map<String, Object> columnNameValMap, Set<String> ignoredColumns, Set<String> ignoreAllColumns) {
+            if (originalColumnDatas == null) {
+                return true;
             }
-            Object updatedValue = columnNameValMap.get(columnName);
-            if (originalColumn.isDataChanged(updatedValue)) {
-                hasUpdate = true;
-                originalColumn.setUpdateValue(updatedValue);
-                updatedColumns.add(originalColumn);
+            boolean hasUpdate = false;
+            updatedColumns = new ArrayList<>(originalColumnDatas.size());
+            for (DataColumnChangeResult originalColumn : originalColumnDatas) {
+                final String columnName = originalColumn.getColumnName().toUpperCase();
+                if (ignoredColumns != null && ignoredColumns.contains(columnName) || ignoreAllColumns.contains(columnName)) {
+                    continue;
+                }
+                Object updatedValue = columnNameValMap.get(columnName);
+                if (originalColumn.isDataChanged(updatedValue)) {
+                    hasUpdate = true;
+                    originalColumn.setUpdateValue(updatedValue);
+                    updatedColumns.add(originalColumn);
+                }
             }
+            return hasUpdate;
         }
-        return hasUpdate;
-    }
 
-    public String generateUpdatedDataStr() {
-        StringBuilder sb = new StringBuilder();
-        sb.append("{");
-        if (pkColumnName != null) {
-            sb.append("\"").append(pkColumnName).append("\"").append(":").append("\"").append(convertDoubleQuotes(pkColumnVal)).append("\"").append(",");
+        public String generateUpdatedDataStr() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            if (pkColumnName != null) {
+                sb.append("\"").append(pkColumnName).append("\"").append(":").append("\"").append(convertDoubleQuotes(pkColumnVal)).append("\"").append(",");
+            }
+            for (DataColumnChangeResult update : updatedColumns) {
+                sb.append(update.generateDataStr());
+            }
+            sb.replace(sb.length() - 1, sb.length(), "}");
+            return sb.toString();
         }
-        for (DataColumnChangeResult update : updatedColumns) {
-            sb.append(update.generateDataStr());
+
+        public String convertDoubleQuotes(Object obj) {
+            if (obj == null) {
+                return null;
+            }
+            return obj.toString().replace("\"", "\\\"");
         }
-        sb.replace(sb.length() - 1, sb.length(), "}");
-        return sb.toString();
     }
-
-
 }

+ 70 - 5
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DataPermissionInterceptor.java

@@ -17,20 +17,26 @@ package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
-import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
 import com.baomidou.mybatisplus.extension.plugins.handler.DataPermissionHandler;
+import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
 import lombok.*;
 import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.statement.delete.Delete;
 import net.sf.jsqlparser.statement.select.PlainSelect;
 import net.sf.jsqlparser.statement.select.Select;
 import net.sf.jsqlparser.statement.select.SelectBody;
 import net.sf.jsqlparser.statement.select.SetOperationList;
+import net.sf.jsqlparser.statement.update.Update;
 import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.executor.statement.StatementHandler;
 import org.apache.ibatis.mapping.BoundSql;
 import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlCommandType;
 import org.apache.ibatis.session.ResultHandler;
 import org.apache.ibatis.session.RowBounds;
 
+import java.sql.Connection;
 import java.sql.SQLException;
 import java.util.List;
 
@@ -38,7 +44,7 @@ import java.util.List;
  * 数据权限处理器
  *
  * @author hubin
- * @since 3.4.1 +
+ * @since 3.5.2
  */
 @Data
 @NoArgsConstructor
@@ -46,16 +52,32 @@ import java.util.List;
 @ToString(callSuper = true)
 @EqualsAndHashCode(callSuper = true)
 @SuppressWarnings({"rawtypes"})
-public class DataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
+public class DataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
     private DataPermissionHandler dataPermissionHandler;
 
     @Override
     public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
-        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) return;
+        if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+            return;
+        }
         PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
         mpBs.sql(parserSingle(mpBs.sql(), ms.getId()));
     }
 
+    @Override
+    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
+        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
+        MappedStatement ms = mpSh.mappedStatement();
+        SqlCommandType sct = ms.getSqlCommandType();
+        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
+            if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) {
+                return;
+            }
+            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
+            mpBs.sql(parserMulti(mpBs.sql(), ms.getId()));
+        }
+    }
+
     @Override
     protected void processSelect(Select select, int index, String sql, Object obj) {
         SelectBody selectBody = select.getSelectBody();
@@ -75,9 +97,52 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne
      * @param whereSegment 查询条件片段
      */
     protected void setWhere(PlainSelect plainSelect, String whereSegment) {
-        Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), whereSegment);
+        if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
+            processPlainSelect(plainSelect, whereSegment);
+            return;
+        }
+        // 兼容旧版的数据权限处理
+        final Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), whereSegment);
         if (null != sqlSegment) {
             plainSelect.setWhere(sqlSegment);
         }
     }
+
+    /**
+     * update 语句处理
+     */
+    @Override
+    protected void processUpdate(Update update, int index, String sql, Object obj) {
+        final Expression sqlSegment = getUpdateOrDeleteExpression(update.getTable(), update.getWhere(), (String) obj);
+        if (null != sqlSegment) {
+            update.setWhere(sqlSegment);
+        }
+    }
+
+    /**
+     * delete 语句处理
+     */
+    @Override
+    protected void processDelete(Delete delete, int index, String sql, Object obj) {
+        final Expression sqlSegment = getUpdateOrDeleteExpression(delete.getTable(), delete.getWhere(), (String) obj);
+        if (null != sqlSegment) {
+            delete.setWhere(sqlSegment);
+        }
+    }
+
+    protected Expression getUpdateOrDeleteExpression(final Table table, final Expression where, final String whereSegment) {
+        if (dataPermissionHandler instanceof MultiDataPermissionHandler) {
+            return andExpression(table, where, whereSegment);
+        } else {
+            // 兼容旧版的数据权限处理
+            return dataPermissionHandler.getSqlSegment(where, whereSegment);
+        }
+    }
+
+    @Override
+    public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
+        // 只有新版数据权限处理器才会执行到这里
+        final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler;
+        return handler.getSqlSegment(table, where, whereSegment);
+    }
 }

+ 19 - 9
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/IllegalSQLInnerInterceptor.java

@@ -35,6 +35,7 @@ import net.sf.jsqlparser.statement.delete.Delete;
 import net.sf.jsqlparser.statement.select.Join;
 import net.sf.jsqlparser.statement.select.PlainSelect;
 import net.sf.jsqlparser.statement.select.Select;
+import net.sf.jsqlparser.statement.select.SelectBody;
 import net.sf.jsqlparser.statement.select.SubSelect;
 import net.sf.jsqlparser.statement.update.Update;
 import org.apache.ibatis.executor.statement.StatementHandler;
@@ -46,7 +47,13 @@ import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.ResultSet;
 import java.sql.SQLException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -108,13 +115,16 @@ public class IllegalSQLInnerInterceptor extends JsqlParserSupport implements Inn
 
     @Override
     protected void processSelect(Select select, int index, String sql, Object obj) {
-        PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
-        Expression where = plainSelect.getWhere();
-        Assert.notNull(where, "非法SQL,必须要有where条件");
-        Table table = (Table) plainSelect.getFromItem();
-        List<Join> joins = plainSelect.getJoins();
-        validWhere(where, table, (Connection) obj);
-        validJoins(joins, table, (Connection) obj);
+        SelectBody selectBody = select.getSelectBody();
+        if (selectBody instanceof PlainSelect) {
+            PlainSelect plainSelect = (PlainSelect) selectBody;
+            Expression where = plainSelect.getWhere();
+            Assert.notNull(where, "非法SQL,必须要有where条件");
+            Table table = (Table) plainSelect.getFromItem();
+            List<Join> joins = plainSelect.getJoins();
+            validWhere(where, table, (Connection) obj);
+            validJoins(joins, table, (Connection) obj);
+        }
     }
 
     @Override
@@ -329,7 +339,7 @@ public class IllegalSQLInnerInterceptor extends JsqlParserSupport implements Inn
                     indexInfoMap.put(key, indexInfos);
                 }
             } catch (SQLException e) {
-                e.printStackTrace();
+                logger.error(String.format("getIndexInfo fault, with key:%s, dbName:%s, tableName:%s", key, dbName, tableName), e);
             }
         }
         return indexInfos;

+ 3 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/OptimisticLockerInnerInterceptor.java

@@ -38,6 +38,7 @@ import org.apache.ibatis.mapping.SqlCommandType;
 import java.lang.reflect.Field;
 import java.sql.SQLException;
 import java.sql.Timestamp;
+import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.Date;
 import java.util.Map;
@@ -293,6 +294,8 @@ public class OptimisticLockerInnerInterceptor implements InnerInterceptor {
             return new Timestamp(System.currentTimeMillis());
         } else if (LocalDateTime.class.equals(clazz)) {
             return LocalDateTime.now();
+        } else if (Instant.class.equals(clazz)) {
+            return LocalDateTime.now();
         }
         //not supported type, return original val.
         return originalVersionVal;

+ 39 - 388
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/TenantLineInnerInterceptor.java

@@ -17,14 +17,15 @@ package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
 import com.baomidou.mybatisplus.core.toolkit.*;
-import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import com.baomidou.mybatisplus.extension.toolkit.PropertyMapper;
 import lombok.*;
-import net.sf.jsqlparser.expression.*;
-import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
-import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
-import net.sf.jsqlparser.expression.operators.relational.*;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.StringValue;
+import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
+import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
+import net.sf.jsqlparser.expression.operators.relational.ItemsList;
+import net.sf.jsqlparser.expression.operators.relational.MultiExpressionList;
 import net.sf.jsqlparser.schema.Column;
 import net.sf.jsqlparser.schema.Table;
 import net.sf.jsqlparser.statement.delete.Delete;
@@ -41,8 +42,8 @@ import org.apache.ibatis.session.RowBounds;
 
 import java.sql.Connection;
 import java.sql.SQLException;
-import java.util.*;
-import java.util.stream.Collectors;
+import java.util.List;
+import java.util.Properties;
 
 /**
  * @author hubin
@@ -54,13 +55,15 @@ import java.util.stream.Collectors;
 @ToString(callSuper = true)
 @EqualsAndHashCode(callSuper = true)
 @SuppressWarnings({"rawtypes"})
-public class TenantLineInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
+public class TenantLineInnerInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor {
 
     private TenantLineHandler tenantLineHandler;
 
     @Override
     public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
-        if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) return;
+        if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
+            return;
+        }
         PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
         mpBs.sql(parserSingle(mpBs.sql(), null));
     }
@@ -81,28 +84,11 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
 
     @Override
     protected void processSelect(Select select, int index, String sql, Object obj) {
-        processSelectBody(select.getSelectBody());
+        final String whereSegment = (String) obj;
+        processSelectBody(select.getSelectBody(), whereSegment);
         List<WithItem> withItemsList = select.getWithItemsList();
         if (!CollectionUtils.isEmpty(withItemsList)) {
-            withItemsList.forEach(this::processSelectBody);
-        }
-    }
-
-    protected void processSelectBody(SelectBody selectBody) {
-        if (selectBody == null) {
-            return;
-        }
-        if (selectBody instanceof PlainSelect) {
-            processPlainSelect((PlainSelect) selectBody);
-        } else if (selectBody instanceof WithItem) {
-            WithItem withItem = (WithItem) selectBody;
-            processSelectBody(withItem.getSubSelect().getSelectBody());
-        } else {
-            SetOperationList operationList = (SetOperationList) selectBody;
-            List<SelectBody> selectBodyList = operationList.getSelects();
-            if (CollectionUtils.isNotEmpty(selectBodyList)) {
-                selectBodyList.forEach(this::processSelectBody);
-            }
+            withItemsList.forEach(withItem -> processSelectBody(withItem, whereSegment));
         }
     }
 
@@ -135,7 +121,7 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
 
         Select select = insert.getSelect();
         if (select != null) {
-            processInsertSelect(select.getSelectBody());
+            this.processInsertSelect(select.getSelectBody(), (String) obj);
         } else if (insert.getItemsList() != null) {
             // fixed github pull/295
             ItemsList itemsList = insert.getItemsList();
@@ -160,7 +146,7 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
             // 过滤退出执行
             return;
         }
-        update.setWhere(this.andExpression(table, update.getWhere()));
+        update.setWhere(this.andExpression(table, update.getWhere(), (String) obj));
     }
 
     /**
@@ -172,28 +158,9 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
             // 过滤退出执行
             return;
         }
-        delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere()));
-    }
-
-    /**
-     * delete update 语句 where 处理
-     */
-    protected BinaryExpression andExpression(Table table, Expression where) {
-        //获得where条件表达式
-        EqualsTo equalsTo = new EqualsTo();
-        equalsTo.setLeftExpression(this.getAliasColumn(table));
-        equalsTo.setRightExpression(tenantLineHandler.getTenantId());
-        if (null != where) {
-            if (where instanceof OrExpression) {
-                return new AndExpression(equalsTo, new Parenthesis(where));
-            } else {
-                return new AndExpression(equalsTo, where);
-            }
-        }
-        return equalsTo;
+        delete.setWhere(this.andExpression(delete.getTable(), delete.getWhere(), (String) obj));
     }
 
-
     /**
      * 处理 insert into select
      * <p>
@@ -201,17 +168,17 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
      *
      * @param selectBody SelectBody
      */
-    protected void processInsertSelect(SelectBody selectBody) {
+    protected void processInsertSelect(SelectBody selectBody, final String whereSegment) {
         PlainSelect plainSelect = (PlainSelect) selectBody;
         FromItem fromItem = plainSelect.getFromItem();
         if (fromItem instanceof Table) {
             // fixed gitee pulls/141 duplicate update
-            processPlainSelect(plainSelect);
+            processPlainSelect(plainSelect, whereSegment);
             appendSelectItem(plainSelect.getSelectItems());
         } else if (fromItem instanceof SubSelect) {
             SubSelect subSelect = (SubSelect) fromItem;
             appendSelectItem(plainSelect.getSelectItems());
-            processInsertSelect(subSelect.getSelectBody());
+            processInsertSelect(subSelect.getSelectBody(), whereSegment);
         }
     }
 
@@ -233,336 +200,6 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
         selectItems.add(new SelectExpressionItem(new Column(tenantLineHandler.getTenantIdColumn())));
     }
 
-    /**
-     * 处理 PlainSelect
-     */
-    protected void processPlainSelect(PlainSelect plainSelect) {
-        //#3087 github
-        List<SelectItem> selectItems = plainSelect.getSelectItems();
-        if (CollectionUtils.isNotEmpty(selectItems)) {
-            selectItems.forEach(this::processSelectItem);
-        }
-
-        // 处理 where 中的子查询
-        Expression where = plainSelect.getWhere();
-        processWhereSubSelect(where);
-
-        // 处理 fromItem
-        FromItem fromItem = plainSelect.getFromItem();
-        List<Table> list = processFromItem(fromItem);
-        List<Table> mainTables = new ArrayList<>(list);
-
-        // 处理 join
-        List<Join> joins = plainSelect.getJoins();
-        if (CollectionUtils.isNotEmpty(joins)) {
-            mainTables = processJoins(mainTables, joins);
-        }
-
-        // 当有 mainTable 时,进行 where 条件追加
-        if (CollectionUtils.isNotEmpty(mainTables)) {
-            plainSelect.setWhere(builderExpression(where, mainTables));
-        }
-    }
-
-    private List<Table> processFromItem(FromItem fromItem) {
-        // 处理括号括起来的表达式
-        while (fromItem instanceof ParenthesisFromItem) {
-            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
-        }
-
-        List<Table> mainTables = new ArrayList<>();
-        // 无 join 时的处理逻辑
-        if (fromItem instanceof Table) {
-            Table fromTable = (Table) fromItem;
-            mainTables.add(fromTable);
-        } else if (fromItem instanceof SubJoin) {
-            // SubJoin 类型则还需要添加上 where 条件
-            List<Table> tables = processSubJoin((SubJoin) fromItem);
-            mainTables.addAll(tables);
-        } else {
-            // 处理下 fromItem
-            processOtherFromItem(fromItem);
-        }
-        return mainTables;
-    }
-
-    /**
-     * 处理where条件内的子查询
-     * <p>
-     * 支持如下:
-     * 1. in
-     * 2. =
-     * 3. >
-     * 4. <
-     * 5. >=
-     * 6. <=
-     * 7. <>
-     * 8. EXISTS
-     * 9. NOT EXISTS
-     * <p>
-     * 前提条件:
-     * 1. 子查询必须放在小括号中
-     * 2. 子查询一般放在比较操作符的右边
-     *
-     * @param where where 条件
-     */
-    protected void processWhereSubSelect(Expression where) {
-        if (where == null) {
-            return;
-        }
-        if (where instanceof FromItem) {
-            processOtherFromItem((FromItem) where);
-            return;
-        }
-        if (where.toString().indexOf("SELECT") > 0) {
-            // 有子查询
-            if (where instanceof BinaryExpression) {
-                // 比较符号 , and , or , 等等
-                BinaryExpression expression = (BinaryExpression) where;
-                processWhereSubSelect(expression.getLeftExpression());
-                processWhereSubSelect(expression.getRightExpression());
-            } else if (where instanceof InExpression) {
-                // in
-                InExpression expression = (InExpression) where;
-                Expression inExpression = expression.getRightExpression();
-                if (inExpression instanceof SubSelect) {
-                    processSelectBody(((SubSelect) inExpression).getSelectBody());
-                }
-            } else if (where instanceof ExistsExpression) {
-                // exists
-                ExistsExpression expression = (ExistsExpression) where;
-                processWhereSubSelect(expression.getRightExpression());
-            } else if (where instanceof NotExpression) {
-                // not exists
-                NotExpression expression = (NotExpression) where;
-                processWhereSubSelect(expression.getExpression());
-            } else if (where instanceof Parenthesis) {
-                Parenthesis expression = (Parenthesis) where;
-                processWhereSubSelect(expression.getExpression());
-            }
-        }
-    }
-
-    protected void processSelectItem(SelectItem selectItem) {
-        if (selectItem instanceof SelectExpressionItem) {
-            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
-            if (selectExpressionItem.getExpression() instanceof SubSelect) {
-                processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());
-            } else if (selectExpressionItem.getExpression() instanceof Function) {
-                processFunction((Function) selectExpressionItem.getExpression());
-            }
-        }
-    }
-
-    /**
-     * 处理函数
-     * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>
-     * <p> fixed gitee pulls/141</p>
-     *
-     * @param function
-     */
-    protected void processFunction(Function function) {
-        ExpressionList parameters = function.getParameters();
-        if (parameters != null) {
-            parameters.getExpressions().forEach(expression -> {
-                if (expression instanceof SubSelect) {
-                    processSelectBody(((SubSelect) expression).getSelectBody());
-                } else if (expression instanceof Function) {
-                    processFunction((Function) expression);
-                }
-            });
-        }
-    }
-
-    /**
-     * 处理子查询等
-     */
-    protected void processOtherFromItem(FromItem fromItem) {
-        // 去除括号
-        while (fromItem instanceof ParenthesisFromItem) {
-            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();
-        }
-
-        if (fromItem instanceof SubSelect) {
-            SubSelect subSelect = (SubSelect) fromItem;
-            if (subSelect.getSelectBody() != null) {
-                processSelectBody(subSelect.getSelectBody());
-            }
-        } else if (fromItem instanceof ValuesList) {
-            logger.debug("Perform a subQuery, if you do not give us feedback");
-        } else if (fromItem instanceof LateralSubSelect) {
-            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;
-            if (lateralSubSelect.getSubSelect() != null) {
-                SubSelect subSelect = lateralSubSelect.getSubSelect();
-                if (subSelect.getSelectBody() != null) {
-                    processSelectBody(subSelect.getSelectBody());
-                }
-            }
-        }
-    }
-
-    /**
-     * 处理 sub join
-     *
-     * @param subJoin subJoin
-     * @return Table subJoin 中的主表
-     */
-    private List<Table> processSubJoin(SubJoin subJoin) {
-        List<Table> mainTables = new ArrayList<>();
-        if (subJoin.getJoinList() != null) {
-            List<Table> list = processFromItem(subJoin.getLeft());
-            mainTables.addAll(list);
-            mainTables = processJoins(mainTables, subJoin.getJoinList());
-        }
-        return mainTables;
-    }
-
-    /**
-     * 处理 joins
-     *
-     * @param mainTables 可以为 null
-     * @param joins      join 集合
-     * @return List<Table> 右连接查询的 Table 列表
-     */
-    private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {
-        // join 表达式中最终的主表
-        Table mainTable = null;
-        // 当前 join 的左表
-        Table leftTable = null;
-
-        if (mainTables == null) {
-            mainTables = new ArrayList<>();
-        } else if (mainTables.size() == 1) {
-            mainTable = mainTables.get(0);
-            leftTable = mainTable;
-        }
-
-        //对于 on 表达式写在最后的 join,需要记录下前面多个 on 的表名
-        Deque<List<Table>> onTableDeque = new LinkedList<>();
-        for (Join join : joins) {
-            // 处理 on 表达式
-            FromItem joinItem = join.getRightItem();
-
-            // 获取当前 join 的表,subJoint 可以看作是一张表
-            List<Table> joinTables = null;
-            if (joinItem instanceof Table) {
-                joinTables = new ArrayList<>();
-                joinTables.add((Table) joinItem);
-            } else if (joinItem instanceof SubJoin) {
-                joinTables = processSubJoin((SubJoin) joinItem);
-            }
-
-            if (joinTables != null) {
-
-                // 如果是隐式内连接
-                if (join.isSimple()) {
-                    mainTables.addAll(joinTables);
-                    continue;
-                }
-
-                // 当前表是否忽略
-                Table joinTable = joinTables.get(0);
-
-                List<Table> onTables = null;
-                // 如果不要忽略,且是右连接,则记录下当前表
-                if (join.isRight()) {
-                    mainTable = joinTable;
-                    if (leftTable != null) {
-                        onTables = Collections.singletonList(leftTable);
-                    }
-                } else if (join.isLeft()) {
-                    onTables = Collections.singletonList(joinTable);
-                } else if (join.isInner()) {
-                    if (mainTable == null) {
-                        onTables = Collections.singletonList(joinTable);
-                    } else {
-                        onTables = Arrays.asList(mainTable, joinTable);
-                    }
-                    mainTable = null;
-                }
-
-                mainTables = new ArrayList<>();
-                if (mainTable != null) {
-                    mainTables.add(mainTable);
-                }
-
-                // 获取 join 尾缀的 on 表达式列表
-                Collection<Expression> originOnExpressions = join.getOnExpressions();
-                // 正常 join on 表达式只有一个,立刻处理
-                if (originOnExpressions.size() == 1 && onTables != null) {
-                    List<Expression> onExpressions = new LinkedList<>();
-                    onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));
-                    join.setOnExpressions(onExpressions);
-                    leftTable = joinTable;
-                    continue;
-                }
-                // 表名压栈,忽略的表压入 null,以便后续不处理
-                onTableDeque.push(onTables);
-                // 尾缀多个 on 表达式的时候统一处理
-                if (originOnExpressions.size() > 1) {
-                    Collection<Expression> onExpressions = new LinkedList<>();
-                    for (Expression originOnExpression : originOnExpressions) {
-                        List<Table> currentTableList = onTableDeque.poll();
-                        if (CollectionUtils.isEmpty(currentTableList)) {
-                            onExpressions.add(originOnExpression);
-                        } else {
-                            onExpressions.add(builderExpression(originOnExpression, currentTableList));
-                        }
-                    }
-                    join.setOnExpressions(onExpressions);
-                }
-                leftTable = joinTable;
-            } else {
-                processOtherFromItem(joinItem);
-                leftTable = null;
-            }
-        }
-
-        return mainTables;
-    }
-
-    /**
-     * 处理条件
-     */
-    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {
-        // 没有表需要处理直接返回
-        if (CollectionUtils.isEmpty(tables)) {
-            return currentExpression;
-        }
-        // 构造每张表的条件
-        List<Table> tempTables = tables.stream()
-            .filter(x -> !tenantLineHandler.ignoreTable(x.getName()))
-            .collect(Collectors.toList());
-
-        // 没有表需要处理直接返回
-        if (CollectionUtils.isEmpty(tempTables)) {
-            return currentExpression;
-        }
-
-        Expression tenantId = tenantLineHandler.getTenantId();
-        List<EqualsTo> equalsTos = tempTables.stream()
-            .map(item -> new EqualsTo(getAliasColumn(item), tenantId))
-            .collect(Collectors.toList());
-
-        // 注入的表达式
-        Expression injectExpression = equalsTos.get(0);
-        // 如果有多表,则用 and 连接
-        if (equalsTos.size() > 1) {
-            for (int i = 1; i < equalsTos.size(); i++) {
-                injectExpression = new AndExpression(injectExpression, equalsTos.get(i));
-            }
-        }
-
-        if (currentExpression == null) {
-            return injectExpression;
-        }
-        if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new Parenthesis(currentExpression), injectExpression);
-        } else {
-            return new AndExpression(currentExpression, injectExpression);
-        }
-    }
-
     /**
      * 租户字段别名设置
      * <p>tenantId 或 tableAlias.tenantId</p>
@@ -572,8 +209,7 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
      */
     protected Column getAliasColumn(Table table) {
         StringBuilder column = new StringBuilder();
-        // 禁止 `为了兼容隐式内连接,没有别名时条件就需要加上表名`
-        // 该起别名就要起别名
+        // todo 该起别名就要起别名,禁止修改此处逻辑
         if (table.getAlias() != null) {
             column.append(table.getAlias().getName()).append(StringPool.DOT);
         }
@@ -586,6 +222,21 @@ public class TenantLineInnerInterceptor extends JsqlParserSupport implements Inn
         PropertyMapper.newInstance(properties).whenNotBlank("tenantLineHandler",
             ClassUtils::newInstance, this::setTenantLineHandler);
     }
-}
-
 
+    /**
+     * 构建租户条件表达式
+     *
+     * @param table        表对象
+     * @param where        当前where条件
+     * @param whereSegment 所属Mapper对象全路径(在原租户拦截器功能中,这个参数并不需要参与相关判断)
+     * @return 租户条件表达式
+     * @see BaseMultiTableInnerInterceptor#buildTableExpression(Table, Expression, String)
+     */
+    @Override
+    public Expression buildTableExpression(final Table table, final Expression where, final String whereSegment) {
+        if (tenantLineHandler.ignoreTable(table.getName())) {
+            return null;
+        }
+        return new EqualsTo(getAliasColumn(table), tenantLineHandler.getTenantId());
+    }
+}

+ 2 - 1
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/DialectFactory.java

@@ -69,7 +69,8 @@ public class DialectFactory {
                 || dbType == DbType.VERTICA
                 || dbType == DbType.REDSHIFT
                 || dbType == DbType.OPENGAUSS
-                || dbType == DbType.TDENGINE) {
+                || dbType == DbType.TDENGINE
+                || dbType == DbType.UXDB) {
                 dialect = new PostgreDialect();
             }
             // other types

+ 31 - 16
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/InformixDialect.java

@@ -1,16 +1,31 @@
-package com.baomidou.mybatisplus.extension.plugins.pagination.dialects;
-import com.baomidou.mybatisplus.core.toolkit.StringPool;
-import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
-public class InformixDialect implements IDialect{
-    @Override
-    public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
-        /*StringBuilder ret = new StringBuilder();
-        ret.append(String.format("select skip %s first %s ", FIRST_MARK+"",SECOND_MARK+""));
-        ret.append(originalSql.replaceFirst("(?i)select", ""));
-        return new DialectModel(ret.toString(), offset, limit).setConsumerChain();*/
-        StringBuilder ret = new StringBuilder();
-        ret.append(String.format("select skip %s first %s ", offset+"",limit+""));
-        ret.append(originalSql.replaceFirst("(?i)select", ""));
-        return new DialectModel(ret.toString());
-    }
-}
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.plugins.pagination.dialects;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
+public class InformixDialect implements IDialect{
+    @Override
+    public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
+        /*StringBuilder ret = new StringBuilder();
+        ret.append(String.format("select skip %s first %s ", FIRST_MARK+"",SECOND_MARK+""));
+        ret.append(originalSql.replaceFirst("(?i)select", ""));
+        return new DialectModel(ret.toString(), offset, limit).setConsumerChain();*/
+        StringBuilder ret = new StringBuilder();
+        ret.append(String.format("select skip %s first %s ", offset+"",limit+""));
+        ret.append(originalSql.replaceFirst("(?i)select", ""));
+        return new DialectModel(ret.toString());
+    }
+}

+ 1 - 1
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/SQLServer2005Dialect.java

@@ -29,7 +29,7 @@ public class SQLServer2005Dialect implements IDialect {
 
     private static String getOrderByPart(String sql) {
         String loweredString = sql.toLowerCase();
-        int orderByIndex = loweredString.indexOf("order by");
+        int orderByIndex = loweredString.lastIndexOf("order by");
         if (orderByIndex != -1) {
             return sql.substring(orderByIndex);
         } else {

+ 62 - 2
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/pagination/dialects/SybaseDialect.java

@@ -17,6 +17,12 @@ package com.baomidou.mybatisplus.extension.plugins.pagination.dialects;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.DialectModel;
 
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 /**
  * sybase 数据库分页方言
  *
@@ -37,8 +43,10 @@ public class SybaseDialect implements IDialect {
 
     @Override
     public DialectModel buildPaginationSql(String originalSql, long offset, long limit) {
-        String tempSql = originalSql.toUpperCase();
-        int index = tempSql.indexOf(" FROM ");
+        int index = findMainFROM(originalSql);
+        if(index == -1){
+            index = originalSql.toUpperCase().indexOf(" FROM ");
+        }
         String sql = "select";
         if (hasTop) {
             sql += " top " + (offset + limit);
@@ -48,4 +56,56 @@ public class SybaseDialect implements IDialect {
         sql += "drop table #t ";
         return new DialectModel(sql, offset, offset + limit).setConsumerChain();
     }
+
+    /**
+     * 查找主查询的FROM位置
+     * @param sql   需要查找的SQL
+     * @return  FROM位置的起始下标
+     * @author lroyia
+     * @since  2022年6月15日 17:57:28
+     */
+    private int findMainFROM(String sql){
+        String tempSql = sql.toUpperCase();
+        tempSql = tempSql.replace("\n", " ").replace("\t", " ").replace("\r", " ");
+        Matcher select_ = Pattern.compile("SELECT ").matcher(tempSql);
+        Matcher from_ = Pattern.compile(" FROM ").matcher(tempSql);
+        List<Integer> selectIndex = new ArrayList<>(10);
+        List<Integer> fromIndex = new ArrayList<>(10);
+        while (select_.find()){
+            int start = select_.start();
+            if(start == 0 || tempSql.charAt(start - 1) == ' '|| tempSql.charAt(start - 1) == '('){
+                selectIndex.add(start);
+            }
+        }
+        while (from_.find()){
+            fromIndex.add(from_.start());
+        }
+
+        // 形成select与from的混合顺序下标列表
+        List<Integer> indexList = new ArrayList<>(20);
+        indexList.addAll(selectIndex);
+        indexList.addAll(fromIndex);
+        indexList.sort(Comparator.naturalOrder());
+        // 无法匹配有效下标
+        if(indexList.size() < 2){
+            return -1;
+        }
+        // 利用栈逻辑匹配select与from
+        int selectCount = 1;
+        for(int i = 1; i < indexList.size(); i++){
+            int each = indexList.get(i);
+            if(fromIndex.contains(each)){
+                // pointer弹栈
+                selectCount--;
+            }else{
+                // pointer压栈
+                selectCount++;
+            }
+            // from将全部select弹出,代表当前这个from为主要from
+            if(selectCount == 0){
+                return each;
+            }
+        }
+        return -1;
+    }
 }

+ 12 - 1
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/service/IService.java

@@ -519,7 +519,18 @@ public interface IService<T> {
      * @return LambdaQueryWrapper 的包装类
      */
     default LambdaQueryChainWrapper<T> lambdaQuery() {
-        return ChainWrappers.lambdaQueryChain(getBaseMapper());
+        return ChainWrappers.lambdaQueryChain(getBaseMapper(), getEntityClass());
+    }
+
+    /**
+     * 链式查询 lambda 式
+     * <p>注意:不支持 Kotlin </p>
+     *
+     * @param entity 实体对象
+     * @return LambdaQueryWrapper 的包装类
+     */
+    default LambdaQueryChainWrapper<T> lambdaQuery(T entity) {
+        return ChainWrappers.lambdaQueryChain(getBaseMapper(), entity);
     }
 
     /**

+ 36 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/ChainWrappers.java

@@ -45,6 +45,10 @@ public final class ChainWrappers {
         return new QueryChainWrapper<>(mapper);
     }
 
+    public static <T> QueryChainWrapper<T> queryChain(Class<T> entityClass) {
+        return new QueryChainWrapper<>(entityClass);
+    }
+
     /**
      * 链式查询 lambda 式
      * <p>注意:不支持 Kotlin </p>
@@ -55,6 +59,30 @@ public final class ChainWrappers {
         return new LambdaQueryChainWrapper<>(mapper);
     }
 
+    public static <T> LambdaQueryChainWrapper<T> lambdaQueryChain(Class<T> entityClass) {
+        return new LambdaQueryChainWrapper<>(entityClass);
+    }
+
+    /**
+     * 链式查询 lambda 式
+     * <p>注意:不支持 Kotlin </p>
+     *
+     * @return LambdaQueryWrapper 的包装类
+     */
+    public static <T> LambdaQueryChainWrapper<T> lambdaQueryChain(BaseMapper<T> mapper, T entity) {
+        return new LambdaQueryChainWrapper<>(mapper, entity);
+    }
+
+    /**
+     * 链式查询 lambda 式
+     * <p>注意:不支持 Kotlin </p>
+     *
+     * @return LambdaQueryWrapper 的包装类
+     */
+    public static <T> LambdaQueryChainWrapper<T> lambdaQueryChain(BaseMapper<T> mapper, Class<T> entityClass) {
+        return new LambdaQueryChainWrapper<>(mapper, entityClass);
+    }
+
     /**
      * 链式查询 lambda 式
      * 仅支持 Kotlin
@@ -84,6 +112,10 @@ public final class ChainWrappers {
         return new UpdateChainWrapper<>(mapper);
     }
 
+    public static <T> UpdateChainWrapper<T> updateChain(Class<T> entityClass) {
+        return new UpdateChainWrapper<>(entityClass);
+    }
+
     /**
      * 链式更改 lambda 式
      * <p>注意:不支持 Kotlin </p>
@@ -94,6 +126,10 @@ public final class ChainWrappers {
         return new LambdaUpdateChainWrapper<>(mapper);
     }
 
+    public static <T> LambdaUpdateChainWrapper<T> lambdaUpdateChain(Class<T> entityClass) {
+        return new LambdaUpdateChainWrapper<>(entityClass);
+    }
+
     /**
      * 链式更改 lambda 式
      * 仅支持 Kotlin

+ 640 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/Db.java

@@ -0,0 +1,640 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.extension.toolkit;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.ibatis.binding.MapperMethod;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
+import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
+import com.baomidou.mybatisplus.core.enums.SqlMethod;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
+import com.baomidou.mybatisplus.core.toolkit.Assert;
+import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
+import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
+import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
+import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+ * 以静态方式调用Service中的函数
+ *
+ * @author VampireAchao
+ * @since 2022-05-03
+ */
+public class Db {
+
+    private static final Log log = LogFactory.getLog(Db.class);
+
+    private Db() {
+        /* Do not new me! */
+    }
+
+    /**
+     * 插入一条记录(选择字段,策略插入)
+     *
+     * @param entity 实体对象
+     */
+    public static <T> boolean save(T entity) {
+        if (Objects.isNull(entity)) {
+            return false;
+        }
+        Integer result = SqlHelper.execute(getEntityClass(entity), baseMapper -> baseMapper.insert(entity));
+        return SqlHelper.retBool(result);
+    }
+
+    /**
+     * 插入(批量)
+     *
+     * @param entityList 实体对象集合
+     */
+    public static <T> boolean saveBatch(Collection<T> entityList) {
+        return saveBatch(entityList, IService.DEFAULT_BATCH_SIZE);
+    }
+
+    /**
+     * 插入(批量)
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  插入批次数量
+     */
+    public static <T> boolean saveBatch(Collection<T> entityList, int batchSize) {
+        if (CollectionUtils.isEmpty(entityList)) {
+            return false;
+        }
+        Class<T> entityClass = getEntityClass(entityList);
+        Class<?> mapperClass = ClassUtils.toClassConfident(getTableInfo(entityClass).getCurrentNamespace());
+        String sqlStatement = SqlHelper.getSqlStatement(mapperClass, SqlMethod.INSERT_ONE);
+        return SqlHelper.executeBatch(entityClass, LogFactory.getLog(Db.class), entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
+    }
+
+    /**
+     * 批量修改插入
+     *
+     * @param entityList 实体对象集合
+     */
+    public static <T> boolean saveOrUpdateBatch(Collection<T> entityList) {
+        return saveOrUpdateBatch(entityList, IService.DEFAULT_BATCH_SIZE);
+    }
+
+    /**
+     * 批量修改插入
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  每次的数量
+     */
+    public static <T> boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
+        if (CollectionUtils.isEmpty(entityList)) {
+            return false;
+        }
+        Class<T> entityClass = getEntityClass(entityList);
+        TableInfo tableInfo = getTableInfo(entityClass);
+        Class<?> mapperClass = ClassUtils.toClassConfident(tableInfo.getCurrentNamespace());
+        String keyProperty = tableInfo.getKeyProperty();
+        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for primary key from entity!");
+        return SqlHelper.saveOrUpdateBatch(entityClass, mapperClass, LogFactory.getLog(Db.class), entityList, batchSize, (sqlSession, entity) -> {
+            Object idVal = tableInfo.getPropertyValue(entity, keyProperty);
+            return StringUtils.checkValNull(idVal)
+                || CollectionUtils.isEmpty(sqlSession.selectList(SqlHelper.getSqlStatement(mapperClass, SqlMethod.SELECT_BY_ID), entity));
+        }, (sqlSession, entity) -> {
+            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
+            param.put(Constants.ENTITY, entity);
+            sqlSession.update(SqlHelper.getSqlStatement(mapperClass, SqlMethod.UPDATE_BY_ID), param);
+        });
+    }
+
+    /**
+     * 根据 ID 删除
+     *
+     * @param id          主键ID
+     * @param entityClass 实体类
+     */
+    public static <T> boolean removeById(Serializable id, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> SqlHelper.retBool(baseMapper.deleteById(id)));
+    }
+
+    /**
+     * 根据实体(ID)删除
+     *
+     * @param entity 实体
+     */
+    public static <T> boolean removeById(T entity) {
+        if (Objects.isNull(entity)) {
+            return false;
+        }
+        return SqlHelper.execute(getEntityClass(entity), baseMapper -> SqlHelper.retBool(baseMapper.deleteById(entity)));
+    }
+
+    /**
+     * 根据 entity 条件,删除记录
+     *
+     * @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> boolean remove(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> SqlHelper.retBool(baseMapper.delete(queryWrapper)));
+    }
+
+    /**
+     * 根据 ID 选择修改
+     *
+     * @param entity 实体对象
+     */
+    public static <T> boolean updateById(T entity) {
+        if (Objects.isNull(entity)) {
+            return false;
+        }
+        return SqlHelper.execute(getEntityClass(entity), baseMapper -> SqlHelper.retBool(baseMapper.updateById(entity)));
+    }
+
+    /**
+     * 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
+     *
+     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+     */
+    public static <T> boolean update(AbstractWrapper<T, ?, ?> updateWrapper) {
+        return update(null, updateWrapper);
+    }
+
+    /**
+     * 根据 whereEntity 条件,更新记录
+     *
+     * @param entity        实体对象
+     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
+     */
+    public static <T> boolean update(T entity, AbstractWrapper<T, ?, ?> updateWrapper) {
+        return SqlHelper.execute(getEntityClass(updateWrapper), baseMapper -> SqlHelper.retBool(baseMapper.update(entity, updateWrapper)));
+    }
+
+    /**
+     * 根据ID 批量更新
+     *
+     * @param entityList 实体对象集合
+     */
+    public static <T> boolean updateBatchById(Collection<T> entityList) {
+        return updateBatchById(entityList, IService.DEFAULT_BATCH_SIZE);
+    }
+
+    /**
+     * 根据ID 批量更新
+     *
+     * @param entityList 实体对象集合
+     * @param batchSize  更新批次数量
+     */
+    public static <T> boolean updateBatchById(Collection<T> entityList, int batchSize) {
+        Class<T> entityClass = getEntityClass(entityList);
+        TableInfo tableInfo = getTableInfo(entityClass);
+        String sqlStatement = SqlHelper.getSqlStatement(ClassUtils.toClassConfident(tableInfo.getCurrentNamespace()), SqlMethod.UPDATE_BY_ID);
+        return SqlHelper.executeBatch(entityClass, LogFactory.getLog(Db.class), entityList, batchSize, (sqlSession, entity) -> {
+            MapperMethod.ParamMap<T> param = new MapperMethod.ParamMap<>();
+            param.put(Constants.ENTITY, entity);
+            sqlSession.update(sqlStatement, param);
+        });
+    }
+
+    /**
+     * 删除(根据ID 批量删除)
+     *
+     * @param list        主键ID或实体列表
+     * @param entityClass 实体类
+     */
+    public static <T> boolean removeByIds(Collection<? extends Serializable> list, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> SqlHelper.retBool(baseMapper.deleteBatchIds(list)));
+    }
+
+    /**
+     * 根据 columnMap 条件,删除记录
+     *
+     * @param columnMap   表字段 map 对象
+     * @param entityClass 实体类
+     */
+    public static <T> boolean removeByMap(Map<String, Object> columnMap, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> SqlHelper.retBool(baseMapper.deleteByMap(columnMap)));
+    }
+
+    /**
+     * TableId 注解存在更新记录,否插入一条记录
+     *
+     * @param entity 实体对象
+     */
+    public static <T> boolean saveOrUpdate(T entity) {
+        if (Objects.isNull(entity)) {
+            return false;
+        }
+        Class<T> entityClass = getEntityClass(entity);
+        TableInfo tableInfo = TableInfoHelper.getTableInfo(entityClass);
+        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
+        String keyProperty = tableInfo.getKeyProperty();
+        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
+        Object idVal = tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
+        return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal, entityClass)) ? save(entity) : updateById(entity);
+    }
+
+    /**
+     * 根据 ID 查询
+     *
+     * @param id          主键ID
+     * @param entityClass 实体类
+     */
+    public static <T> T getById(Serializable id, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectById(id));
+    }
+
+    /**
+     * 根据 Wrapper,查询一条记录 <br/>
+     * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> T getOne(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return getOne(queryWrapper, true);
+    }
+
+    /**
+     * 根据 entity里不为空的字段,查询一条记录 <br/>
+     * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
+     *
+     * @param entity 实体对象
+     */
+    public static <T> T getOne(T entity) {
+        return getOne(Wrappers.lambdaQuery(entity), true);
+    }
+
+    /**
+     * 根据 entity里不为空的字段,查询一条记录
+     *
+     * @param entity  实体对象
+     * @param throwEx 有多个 result 是否抛出异常
+     */
+    public static <T> T getOne(T entity, boolean throwEx) {
+        return getOne(Wrappers.lambdaQuery(entity), throwEx);
+    }
+
+    /**
+     * 根据 Wrapper,查询一条记录
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     * @param throwEx      有多个 result 是否抛出异常
+     */
+    public static <T> T getOne(AbstractWrapper<T, ?, ?> queryWrapper, boolean throwEx) {
+        Class<T> entityClass = getEntityClass(queryWrapper);
+        if (throwEx) {
+            return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectOne(queryWrapper));
+        }
+        return SqlHelper.execute(entityClass, baseMapper -> SqlHelper.getObject(log, baseMapper.selectList(queryWrapper)));
+    }
+
+    /**
+     * 查询(根据 columnMap 条件)
+     *
+     * @param columnMap   表字段 map 对象
+     * @param entityClass 实体类
+     */
+    public static <T> List<T> listByMap(Map<String, Object> columnMap, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectByMap(columnMap));
+    }
+
+    /**
+     * 查询(根据ID 批量查询)
+     *
+     * @param idList      主键ID列表
+     * @param entityClass 实体类
+     */
+    public static <T> List<T> listByIds(Collection<? extends Serializable> idList, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectBatchIds(idList));
+    }
+
+    /**
+     * 根据 Wrapper,查询一条记录
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> Map<String, Object> getMap(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> SqlHelper.getObject(log, baseMapper.selectMaps(queryWrapper)));
+    }
+
+    /**
+     * 根据 entity不为空条件,查询一条记录
+     *
+     * @param entity 实体对象
+     */
+    public static <T> Map<String, Object> getMap(T entity) {
+        return getMap(Wrappers.lambdaQuery(entity));
+    }
+
+    /**
+     * 查询总记录数
+     *
+     * @param entityClass 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T> long count(Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectCount(null));
+    }
+
+    /**
+     * 根据entity中不为空的数据查询记录数
+     *
+     * @param entity 实体类
+     */
+    public static <T> long count(T entity) {
+        return count(Wrappers.lambdaQuery(entity));
+    }
+
+    /**
+     * 根据 Wrapper 条件,查询总记录数
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> long count(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectCount(queryWrapper));
+    }
+
+    /**
+     * 查询列表
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> List<T> list(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectList(queryWrapper));
+    }
+
+    /**
+     * 查询所有
+     *
+     * @param entityClass 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T> List<T> list(Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectList(null));
+    }
+
+    /**
+     * 根据entity中不为空的字段进行查询
+     *
+     * @param entity 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T> List<T> list(T entity) {
+        return list(Wrappers.lambdaQuery(entity));
+    }
+
+    /**
+     * 查询列表
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> List<Map<String, Object>> listMaps(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectMaps(queryWrapper));
+    }
+
+    /**
+     * 查询所有列表
+     *
+     * @param entityClass 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T> List<Map<String, Object>> listMaps(Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectMaps(null));
+    }
+
+    /**
+     * 根据entity不为空的条件查询列表
+     *
+     * @param entity 实体类
+     */
+    public static <T> List<Map<String, Object>> listMaps(T entity) {
+        return listMaps(Wrappers.lambdaQuery(entity));
+    }
+
+    /**
+     * 查询全部记录
+     *
+     * @param entityClass 实体类
+     */
+    public static <T> List<T> listObjs(Class<T> entityClass) {
+        return listObjs(entityClass, i -> i);
+    }
+
+    /**
+     * 根据 Wrapper 条件,查询全部记录
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> List<Object> listObjs(AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectObjs(queryWrapper));
+    }
+
+    /**
+     * 根据 Wrapper 条件,查询全部记录
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     * @param mapper       转换函数
+     */
+    public static <T, V> List<V> listObjs(AbstractWrapper<T, ?, ?> queryWrapper, SFunction<? super T, V> mapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectList(queryWrapper).stream().map(mapper).collect(Collectors.toList()));
+    }
+
+    /**
+     * 查询全部记录
+     *
+     * @param entityClass 实体类
+     * @param mapper      转换函数
+     */
+    public static <T, V> List<V> listObjs(Class<T> entityClass, SFunction<? super T, V> mapper) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectList(null).stream().map(mapper).collect(Collectors.toList()));
+    }
+
+    /**
+     * 无条件翻页查询
+     *
+     * @param page        翻页对象
+     * @param entityClass 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T, E extends IPage<Map<String, Object>>> E pageMaps(E page, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectMapsPage(page, null));
+    }
+
+    /**
+     * 翻页查询
+     *
+     * @param page         翻页对象
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T, E extends IPage<Map<String, Object>>> E pageMaps(E page, AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectMapsPage(page, queryWrapper));
+    }
+
+    /**
+     * 无条件翻页查询
+     *
+     * @param page        翻页对象
+     * @param entityClass 实体类
+     * @see Wrappers#emptyWrapper()
+     */
+    public static <T> IPage<T> page(IPage<T> page, Class<T> entityClass) {
+        return SqlHelper.execute(entityClass, baseMapper -> baseMapper.selectPage(page, null));
+    }
+
+    /**
+     * 翻页查询
+     *
+     * @param page         翻页对象
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     */
+    public static <T> IPage<T> page(IPage<T> page, AbstractWrapper<T, ?, ?> queryWrapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> baseMapper.selectPage(page, queryWrapper));
+    }
+
+    /**
+     * 链式查询 普通
+     *
+     * @return QueryWrapper 的包装类
+     */
+    public static <T> QueryChainWrapper<T> query(Class<T> entityClass) {
+        return ChainWrappers.queryChain(entityClass);
+    }
+
+    /**
+     * 链式查询 lambda 式
+     * <p>注意:不支持 Kotlin </p>
+     *
+     * @return LambdaQueryWrapper 的包装类
+     */
+    public static <T> LambdaQueryChainWrapper<T> lambdaQuery(Class<T> entityClass) {
+        return ChainWrappers.lambdaQueryChain(entityClass);
+    }
+
+    /**
+     * 链式更改 普通
+     *
+     * @return UpdateWrapper 的包装类
+     */
+    public static <T> UpdateChainWrapper<T> update(Class<T> entityClass) {
+        return ChainWrappers.updateChain(entityClass);
+    }
+
+    /**
+     * 链式更改 lambda 式
+     * <p>注意:不支持 Kotlin </p>
+     *
+     * @return LambdaUpdateWrapper 的包装类
+     */
+    public static <T> LambdaUpdateChainWrapper<T> lambdaUpdate(Class<T> entityClass) {
+        return ChainWrappers.lambdaUpdateChain(entityClass);
+    }
+
+    /**
+     * <p>
+     * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
+     * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作)
+     * </p>
+     *
+     * @param entity 实体对象
+     */
+    public static <T> boolean saveOrUpdate(T entity, AbstractWrapper<T, ?, ?> updateWrapper) {
+        return update(entity, updateWrapper) || saveOrUpdate(entity);
+    }
+
+    /**
+     * 根据 Wrapper,查询一条记录
+     *
+     * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
+     * @param mapper       转换函数
+     */
+    public static <T, V> V getObj(AbstractWrapper<T, ?, ?> queryWrapper, SFunction<? super T, V> mapper) {
+        return SqlHelper.execute(getEntityClass(queryWrapper), baseMapper -> mapper.apply(baseMapper.selectOne(queryWrapper)));
+    }
+
+    /**
+     * 从集合中获取实体类型
+     *
+     * @param entityList 实体集合
+     * @param <T>        实体类型
+     * @return 实体类型
+     */
+    protected static <T> Class<T> getEntityClass(Collection<T> entityList) {
+        Class<T> entityClass = null;
+        for (T entity : entityList) {
+            if (entity != null && entity.getClass() != null) {
+                entityClass = getEntityClass(entity);
+                break;
+            }
+        }
+        Assert.notNull(entityClass, "error: can not get entityClass from entityList");
+        return entityClass;
+    }
+
+    /**
+     * 从wrapper中尝试获取实体类型
+     *
+     * @param queryWrapper 条件构造器
+     * @param <T>          实体类型
+     * @return 实体类型
+     */
+    protected static <T> Class<T> getEntityClass(AbstractWrapper<T, ?, ?> queryWrapper) {
+        Class<T> entityClass = queryWrapper.getEntityClass();
+        if (entityClass == null) {
+            T entity = queryWrapper.getEntity();
+            if (entity != null) {
+                entityClass = getEntityClass(entity);
+            }
+        }
+        Assert.notNull(entityClass, "error: can not get entityClass from wrapper");
+        return entityClass;
+    }
+
+    /**
+     * 从entity中尝试获取实体类型
+     *
+     * @param entity 实体
+     * @param <T>    实体类型
+     * @return 实体类型
+     */
+    @SuppressWarnings("unchecked")
+    protected static <T> Class<T> getEntityClass(T entity) {
+        return (Class<T>) entity.getClass();
+    }
+
+
+    /**
+     * 获取表信息,获取不到报错提示
+     *
+     * @param entityClass 实体类
+     * @param <T>         实体类型
+     * @return 对应表信息
+     */
+    protected static <T> TableInfo getTableInfo(Class<T> entityClass) {
+        return Optional.ofNullable(TableInfoHelper.getTableInfo(entityClass)).orElseThrow(() -> ExceptionUtils.mpe("error: can not find TableInfo from Class: \"%s\".", entityClass.getName()));
+    }
+}

+ 5 - 3
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/JdbcUtils.java

@@ -131,9 +131,11 @@ public class JdbcUtils {
             return DbType.OPENGAUSS;
         } else if (url.contains(":taos:") || url.contains(":taos-rs:")) {
             return DbType.TDENGINE;
-        }else if (url.contains(":informix")) {
-           return DbType.INFORMIX;
-        }  else {
+        } else if (url.contains(":informix")) {
+            return DbType.INFORMIX;
+        } else if (url.contains(":uxdb:")) {
+            return DbType.UXDB;
+        } else {
             logger.warn("The jdbcUrl is " + jdbcUrl + ", Mybatis Plus Cannot Read Database type or The Database's Not Supported!");
             return DbType.OTHER;
         }

+ 27 - 29
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/SimpleQuery.java

@@ -15,18 +15,28 @@
  */
 package com.baomidou.mybatisplus.extension.toolkit;
 
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
-import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
-import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
-
-import java.util.*;
-import java.util.function.*;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BinaryOperator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collector;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.LambdaUtils;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+
 /**
  * simple-query 让简单的查询更简单
  *
@@ -55,7 +65,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A> Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
-        return list2Map(selectList(getType(sFunction), wrapper), sFunction, Function.identity(), peeks);
+        return list2Map(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, Function.identity(), peeks);
     }
 
     /**
@@ -71,7 +81,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A> Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks) {
-        return list2Map(selectList(getType(sFunction), wrapper), sFunction, Function.identity(), isParallel, peeks);
+        return list2Map(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, Function.identity(), isParallel, peeks);
     }
 
     /**
@@ -79,7 +89,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A, P> Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, Consumer<E>... peeks) {
-        return list2Map(selectList(getType(keyFunc), wrapper), keyFunc, valueFunc, peeks);
+        return list2Map(Db.list(wrapper.setEntityClass(getType(keyFunc))), keyFunc, valueFunc, peeks);
     }
 
     /**
@@ -97,7 +107,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A, P> Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, boolean isParallel, Consumer<E>... peeks) {
-        return list2Map(selectList(getType(keyFunc), wrapper), keyFunc, valueFunc, isParallel, peeks);
+        return list2Map(Db.list(wrapper.setEntityClass(getType(keyFunc))), keyFunc, valueFunc, isParallel, peeks);
     }
 
     /**
@@ -105,7 +115,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A> Map<A, List<E>> group(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
-        return listGroupBy(selectList(getType(sFunction), wrapper), sFunction, peeks);
+        return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, peeks);
     }
 
     /**
@@ -113,7 +123,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <T, K> Map<K, List<T>> group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, boolean isParallel, Consumer<T>... peeks) {
-        return listGroupBy(selectList(getType(sFunction), wrapper), sFunction, isParallel, peeks);
+        return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, isParallel, peeks);
     }
 
     /**
@@ -121,7 +131,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <T, K, D, A> Map<K, D> group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<T, A, D> downstream, Consumer<T>... peeks) {
-        return listGroupBy(selectList(getType(sFunction), wrapper), sFunction, downstream, false, peeks);
+        return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, downstream, false, peeks);
     }
 
     /**
@@ -140,7 +150,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <T, K, D, A> Map<K, D> group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<T, A, D> downstream, boolean isParallel, Consumer<T>... peeks) {
-        return listGroupBy(selectList(getType(sFunction), wrapper), sFunction, downstream, isParallel, peeks);
+        return listGroupBy(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, downstream, isParallel, peeks);
     }
 
     /**
@@ -148,7 +158,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A> List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks) {
-        return list2List(selectList(getType(sFunction), wrapper), sFunction, peeks);
+        return list2List(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, peeks);
     }
 
     /**
@@ -163,7 +173,7 @@ public class SimpleQuery {
      */
     @SafeVarargs
     public static <E, A> List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks) {
-        return list2List(selectList(getType(sFunction), wrapper), sFunction, isParallel, peeks);
+        return list2List(Db.list(wrapper.setEntityClass(getType(sFunction))), sFunction, isParallel, peeks);
     }
 
     /**
@@ -317,16 +327,4 @@ public class SimpleQuery {
         return Stream.of(peeks).reduce(StreamSupport.stream(list.spliterator(), isParallel), Stream::peek, Stream::concat);
     }
 
-    /**
-     * 通过entityClass查询列表,并关闭sqlSession
-     *
-     * @param entityClass 表对应实体
-     * @param wrapper     条件构造器
-     * @param <E>         实体类型
-     * @return 查询列表结果
-     */
-    public static <E> List<E> selectList(Class<E> entityClass, LambdaQueryWrapper<E> wrapper) {
-        return SqlHelper.execute(entityClass, m -> m.selectList(wrapper));
-    }
-
 }

+ 6 - 5
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/toolkit/SqlHelper.java

@@ -277,14 +277,15 @@ public final class SqlHelper {
      *
      * @param entityClass 实体
      * @param <T>         实体类型
+     * @param <M>         Mapper类型
      * @return Mapper
      */
     @SuppressWarnings("unchecked")
-    public static <T> BaseMapper<T> getMapper(Class<T> entityClass, SqlSession sqlSession) {
+    public static <T,M extends BaseMapper<T>> M getMapper(Class<T> entityClass, SqlSession sqlSession) {
         Assert.notNull(entityClass, "entityClass can't be null!");
         TableInfo tableInfo = Optional.ofNullable(TableInfoHelper.getTableInfo(entityClass)).orElseThrow(() -> ExceptionUtils.mpe("Can not find TableInfo from Class: \"%s\".", entityClass.getName()));
         Class<?> mapperClass = ClassUtils.toClassConfident(tableInfo.getCurrentNamespace());
-        return (BaseMapper<T>) tableInfo.getConfiguration().getMapper(mapperClass, sqlSession);
+        return (M) tableInfo.getConfiguration().getMapper(mapperClass, sqlSession);
     }
 
     /**
@@ -294,13 +295,13 @@ public final class SqlHelper {
      * @param sFunction   lambda操作,例如 {@code m->m.selectList(wrapper)}
      * @param <T>         实体类的类型
      * @param <R>         返回值类型
+     * @param <M>         Mapper类型
      * @return 返回lambda执行结果
      */
-    public static <T, R> R execute(Class<T> entityClass, SFunction<BaseMapper<T>, R> sFunction) {
+    public static <T, R,M extends BaseMapper<T>> R execute(Class<T> entityClass, SFunction<M, R> sFunction) {
         SqlSession sqlSession = SqlHelper.sqlSession(entityClass);
         try {
-            BaseMapper<T> baseMapper = SqlHelper.getMapper(entityClass, sqlSession);
-            return sFunction.apply(baseMapper);
+            return sFunction.apply(SqlHelper.getMapper(entityClass, sqlSession));
         } finally {
             SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(entityClass));
         }

+ 4 - 0
mybatis-plus-extension/src/main/kotlin/com/baomidou/mybatisplus/extension/kotlin/KtQueryChainWrapper.kt

@@ -55,4 +55,8 @@ open class KtQueryChainWrapper<T : Any>(
         return baseMapper
     }
 
+    override fun getEntityClass(): Class<T> {
+        return super.wrapperChildren.entityClass
+    }
+
 }

+ 4 - 0
mybatis-plus-extension/src/main/kotlin/com/baomidou/mybatisplus/extension/kotlin/KtUpdateChainWrapper.kt

@@ -53,4 +53,8 @@ open class KtUpdateChainWrapper<T : Any>(
         return baseMapper
     }
 
+    override fun getEntityClass(): Class<T> {
+        return super.wrapperChildren.entityClass
+    }
+
 }

+ 1 - 1
mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/DynamicTableNameInnerInterceptorTest.java

@@ -45,6 +45,6 @@ class DynamicTableNameInnerInterceptorTest {
 
         // 别名被声明要替换
         origin = "SELECT t.* FROM t_user_real t left join entity e on e.id = t.id";
-        assertEquals("SELECT t.* FROM t_user_real_r t left join entity e on e.id = t.id", interceptor.changeTable(origin));
+        assertEquals("SELECT t.* FROM t_user_real_r t left join entity_r e on e.id = t.id", interceptor.changeTable(origin));
     }
 }

+ 120 - 0
mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/MultiDataPermissionInterceptorTest.java

@@ -0,0 +1,120 @@
+package com.baomidou.mybatisplus.extension.plugins.inner;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler;
+import com.google.common.collect.HashBasedTable;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import net.sf.jsqlparser.schema.Table;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * SQL多表场景的数据权限拦截器测试
+ *
+ * @author houkunlin
+ * @since 3.5.2 +
+ */
+public class MultiDataPermissionInterceptorTest {
+    private static final Logger logger = LoggerFactory.getLogger(MultiDataPermissionInterceptorTest.class);
+    /**
+     * 这里可以理解为数据库配置的数据权限规则 SQL
+     */
+    private static final com.google.common.collect.Table<String, String, String> sqlSegmentMap;
+    private static final DataPermissionInterceptor interceptor;
+    private static String TEST_1 = "com.baomidou.userMapper.selectByUsername";
+    private static String TEST_2 = "com.baomidou.userMapper.selectById";
+    private static String TEST_3 = "com.baomidou.roleMapper.selectByCompanyId";
+    private static String TEST_4 = "com.baomidou.roleMapper.selectById";
+    private static String TEST_5 = "com.baomidou.roleMapper.selectByRoleId";
+    private static String TEST_6 = "com.baomidou.roleMapper.selectUserInfo";
+    private static String TEST_7 = "com.baomidou.roleMapper.summarySum";
+
+    static {
+        sqlSegmentMap = HashBasedTable.create();
+        sqlSegmentMap.put(TEST_1, "sys_user", "username='123' or userId IN (1,2,3)");
+        sqlSegmentMap.put(TEST_2, "sys_user", "u.state=1 and u.amount > 1000");
+        sqlSegmentMap.put(TEST_3, "sys_role", "companyId in (1,2,3)");
+        sqlSegmentMap.put(TEST_4, "sys_role", "username like 'abc%'");
+        sqlSegmentMap.put(TEST_5, "sys_role", "id=1 and role_id in (select id from sys_role)");
+        sqlSegmentMap.put(TEST_6, "sys_user", "u.state=1 and u.amount > 1000");
+        sqlSegmentMap.put(TEST_6, "sys_user_role", "r.role_id=3 AND r.role_id IN (7,9,11)");
+        sqlSegmentMap.put(TEST_7, "`fund`", "a.id = 1 AND a.year = 2022 AND a.create_user_id = 1111");
+        sqlSegmentMap.put(TEST_7, "`fund_month`", "b.fund_id = 2 AND b.month <= '2022-05'");
+        interceptor = new DataPermissionInterceptor(new MultiDataPermissionHandler() {
+
+            @Override
+            public Expression getSqlSegment(final Table table, final Expression where, final String mappedStatementId) {
+                try {
+                    String sqlSegment = sqlSegmentMap.get(mappedStatementId, table.getName());
+                    if (sqlSegment == null) {
+                        logger.info("{} {} AS {} : NOT FOUND", mappedStatementId, table.getName(), table.getAlias());
+                        return null;
+                    }
+                    Expression sqlSegmentExpression = CCJSqlParserUtil.parseCondExpression(sqlSegment);
+                    logger.info("{} {} AS {} : {}", mappedStatementId, table.getName(), table.getAlias(), sqlSegmentExpression.toString());
+                    return sqlSegmentExpression;
+                } catch (JSQLParserException e) {
+                    e.printStackTrace();
+                }
+                return null;
+            }
+        });
+    }
+
+    @Test
+    void test1() {
+        assertSql(TEST_1, "select * from sys_user",
+            "SELECT * FROM sys_user WHERE username = '123' OR userId IN (1, 2, 3)");
+    }
+
+    @Test
+    void test2() {
+        assertSql(TEST_2, "select u.username from sys_user u join sys_user_role r on u.id=r.user_id where r.role_id=3",
+            "SELECT u.username FROM sys_user u JOIN sys_user_role r ON u.id = r.user_id WHERE r.role_id = 3 AND u.state = 1 AND u.amount > 1000");
+    }
+
+    @Test
+    void test3() {
+        assertSql(TEST_3, "select * from sys_role where company_id=6",
+            "SELECT * FROM sys_role WHERE company_id = 6 AND companyId IN (1, 2, 3)");
+    }
+
+    @Test
+    void test3unionAll() {
+        assertSql(TEST_3, "select * from sys_role where company_id=6 union all select * from sys_role where company_id=7",
+            "SELECT * FROM sys_role WHERE company_id = 6 AND companyId IN (1, 2, 3) UNION ALL SELECT * FROM sys_role WHERE company_id = 7 AND companyId IN (1, 2, 3)");
+    }
+
+    @Test
+    void test4() {
+        assertSql(TEST_4, "select * from sys_role where id=3",
+            "SELECT * FROM sys_role WHERE id = 3 AND username LIKE 'abc%'");
+    }
+
+    @Test
+    void test5() {
+        assertSql(TEST_5, "select * from sys_role where id=3",
+            "SELECT * FROM sys_role WHERE id = 3 AND id = 1 AND role_id IN (SELECT id FROM sys_role)");
+    }
+
+    @Test
+    void test6() {
+        // 显式指定 JOIN 类型时 JOIN 右侧表才能进行拼接条件
+        assertSql(TEST_6, "select u.username from sys_user u LEFT join sys_user_role r on u.id=r.user_id",
+            "SELECT u.username FROM sys_user u LEFT JOIN sys_user_role r ON u.id = r.user_id AND r.role_id = 3 AND r.role_id IN (7, 9, 11) WHERE u.state = 1 AND u.amount > 1000");
+    }
+
+    @Test
+    void test7() {
+        assertSql(TEST_7, "SELECT c.doc AS title, sum(c.total_paid_amount) AS total_paid_amount, sum(c.balance_amount) AS balance_amount FROM (SELECT `a`.`id`, `a`.`doc`, `b`.`month`, `b`.`total_paid_amount`, `b`.`balance_amount`, row_number() OVER (PARTITION BY `a`.`id` ORDER BY `b`.`month` DESC) AS `row_index` FROM `fund` `a` LEFT JOIN `fund_month` `b` ON `a`.`id` = `b`.`fund_id` AND `b`.`submit` = TRUE) c WHERE c.row_index = 1 GROUP BY title LIMIT 20",
+            "SELECT c.doc AS title, sum(c.total_paid_amount) AS total_paid_amount, sum(c.balance_amount) AS balance_amount FROM (SELECT `a`.`id`, `a`.`doc`, `b`.`month`, `b`.`total_paid_amount`, `b`.`balance_amount`, row_number() OVER (PARTITION BY `a`.`id` ORDER BY `b`.`month` DESC) AS `row_index` FROM `fund` `a` LEFT JOIN `fund_month` `b` ON `a`.`id` = `b`.`fund_id` AND `b`.`submit` = TRUE AND b.fund_id = 2 AND b.month <= '2022-05' WHERE a.id = 1 AND a.year = 2022 AND a.create_user_id = 1111) c WHERE c.row_index = 1 GROUP BY title LIMIT 20");
+    }
+
+    void assertSql(String mappedStatementId, String sql, String targetSql) {
+        assertThat(interceptor.parserSingle(sql, mappedStatementId)).isEqualTo(targetSql);
+    }
+}

+ 30 - 2
mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/extension/plugins/inner/TenantLineInnerInterceptorTest.java

@@ -38,6 +38,8 @@ class TenantLineInnerInterceptorTest {
     @Test
     void insert() {
         // plain
+        assertSql("insert into entity (id) values (?)",
+            "INSERT INTO entity (id, tenant_id) VALUES (?, 1)");
         assertSql("insert into entity (id,name) values (?,?)",
             "INSERT INTO entity (id, name, tenant_id) VALUES (?, ?, 1)");
         // batch
@@ -69,13 +71,13 @@ class TenantLineInnerInterceptorTest {
     @Test
     void delete() {
         assertSql("delete from entity where id = ?",
-            "DELETE FROM entity WHERE tenant_id = 1 AND id = ?");
+            "DELETE FROM entity WHERE id = ? AND tenant_id = 1");
     }
 
     @Test
     void update() {
         assertSql("update entity set name = ? where id = ?",
-            "UPDATE entity SET name = ? WHERE tenant_id = 1 AND id = ?");
+            "UPDATE entity SET name = ? WHERE id = ? AND tenant_id = 1");
     }
 
     @Test
@@ -93,6 +95,9 @@ class TenantLineInnerInterceptorTest {
         /* not */
         assertSql("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
             "SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND tenant_id = 1");
+
+        assertSql("SELECT * FROM entity u WHERE not (u.id = ? OR u.name = ?)",
+            "SELECT * FROM entity u WHERE NOT (u.id = ? OR u.name = ?) AND u.tenant_id = 1");
     }
 
     @Test
@@ -414,6 +419,15 @@ class TenantLineInnerInterceptorTest {
 
     }
 
+    @Test
+    void selectJoin() {
+        // join
+        assertSql("SELECT * FROM entity e join entity1 e1 on e1.id = e.id WHERE e.id = ? OR e.name = ?",
+            "SELECT * FROM entity e JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+
+        assertSql("SELECT * FROM entity e join entity1 e1 on e1.id = e.id WHERE (e.id = ? OR e.name = ?)",
+            "SELECT * FROM entity e JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1");
+    }
 
     @Test
     void selectWithAs() {
@@ -428,6 +442,20 @@ class TenantLineInnerInterceptorTest {
             "SELECT dict.dict_code, item.item_text AS \"text\", item.item_value AS \"value\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)");
     }
 
+    @Test
+    void test6() {
+        // 不显式指定 JOIN 类型时 JOIN 右侧表无法识进行拼接条件(在未改动之前就已经有这个问题)
+        assertSql("select u.username from sys_user u join sys_user_role r on u.id=r.user_id",
+            "SELECT u.username FROM sys_user u JOIN sys_user_role r ON u.id = r.user_id AND r.tenant_id = 1 WHERE u.tenant_id = 1");
+    }
+
+    @Test
+    void test7() {
+        // 显式指定 JOIN 类型时 JOIN 右侧表才能进行拼接条件
+        assertSql("select u.username from sys_user u LEFT join sys_user_role r on u.id=r.user_id",
+            "SELECT u.username FROM sys_user u LEFT JOIN sys_user_role r ON u.id = r.user_id AND r.tenant_id = 1 WHERE u.tenant_id = 1");
+    }
+
     void assertSql(String sql, String targetSql) {
         assertThat(interceptor.parserSingle(sql, null)).isEqualTo(targetSql);
     }

+ 19 - 0
mybatis-plus-generator/build.gradle

@@ -0,0 +1,19 @@
+dependencies {
+    implementation project(":mybatis-plus-extension")
+    implementation "${lib.velocity}"
+    implementation "${lib.freemarker}"
+    implementation "${lib.beetl}"
+    implementation "${lib.enjoy}"
+
+    compileOnly "org.jetbrains:annotations:22.0.0"
+    testImplementation "${lib.sqlserver}"
+    testImplementation "${lib.postgresql}"
+    testImplementation lib.oracle as ConfigurableFileTree
+    testImplementation lib.dm as ConfigurableFileTree
+    testImplementation "${lib.h2}"
+    testImplementation "${lib.mysql}"
+    testImplementation "${lib.sqlite}"
+    testImplementation "${lib.firebird}"
+    testImplementation "${lib.'swagger-annotations'}"
+    testCompileOnly "org.jetbrains:annotations:22.0.0"
+}

+ 221 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/AutoGenerator.java

@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator;
+
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
+import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * 生成文件
+ *
+ * @author YangHu, tangguo, hubin
+ * @since 2016-08-30
+ */
+public class AutoGenerator {
+
+    private static final Logger logger = LoggerFactory.getLogger(AutoGenerator.class);
+
+    /**
+     * 配置信息
+     */
+    protected ConfigBuilder config;
+    /**
+     * 注入配置
+     */
+    protected InjectionConfig injection;
+    /**
+     * 数据源配置
+     */
+    private DataSourceConfig dataSource;
+    /**
+     * 数据库表配置
+     */
+    private StrategyConfig strategy;
+    /**
+     * 包 相关配置
+     */
+    private PackageConfig packageInfo;
+    /**
+     * 模板 相关配置
+     */
+    private TemplateConfig template;
+    /**
+     * 全局 相关配置
+     */
+    private GlobalConfig globalConfig;
+
+    private AutoGenerator() {
+        // 不推荐使用
+    }
+
+    /**
+     * 构造方法
+     *
+     * @param dataSourceConfig 数据库配置
+     * @since 3.5.0
+     */
+    public AutoGenerator(@NotNull DataSourceConfig dataSourceConfig) {
+        //这个是必须参数,其他都是可选的,后续去除默认构造更改成final
+        this.dataSource = dataSourceConfig;
+    }
+
+    /**
+     * 注入配置
+     *
+     * @param injectionConfig 注入配置
+     * @return this
+     * @since 3.5.0
+     */
+    public AutoGenerator injection(@NotNull InjectionConfig injectionConfig) {
+        this.injection = injectionConfig;
+        return this;
+    }
+
+    /**
+     * 生成策略
+     *
+     * @param strategyConfig 策略配置
+     * @return this
+     * @since 3.5.0
+     */
+    public AutoGenerator strategy(@NotNull StrategyConfig strategyConfig) {
+        this.strategy = strategyConfig;
+        return this;
+    }
+
+    /**
+     * 指定包配置信息
+     *
+     * @param packageConfig 包配置
+     * @return this
+     * @since 3.5.0
+     */
+    public AutoGenerator packageInfo(@NotNull PackageConfig packageConfig) {
+        this.packageInfo = packageConfig;
+        return this;
+    }
+
+    /**
+     * 指定模板配置
+     *
+     * @param templateConfig 模板配置
+     * @return this
+     * @since 3.5.0
+     */
+    public AutoGenerator template(@NotNull TemplateConfig templateConfig) {
+        this.template = templateConfig;
+        return this;
+    }
+
+    /**
+     * 指定全局配置
+     *
+     * @param globalConfig 全局配置
+     * @return this
+     * @see 3.5.0
+     */
+    public AutoGenerator global(@NotNull GlobalConfig globalConfig) {
+        this.globalConfig = globalConfig;
+        return this;
+    }
+
+    /**
+     * 设置配置汇总
+     *
+     * @param configBuilder 配置汇总
+     * @return this
+     * @since 3.5.0
+     */
+    public AutoGenerator config(@NotNull ConfigBuilder configBuilder) {
+        this.config = configBuilder;
+        return this;
+    }
+
+    /**
+     * 生成代码
+     */
+    public void execute() {
+        this.execute(null);
+    }
+
+    /**
+     * 生成代码
+     *
+     * @param templateEngine 模板引擎
+     */
+    public void execute(AbstractTemplateEngine templateEngine) {
+        logger.debug("==========================准备生成文件...==========================");
+        // 初始化配置
+        if (null == config) {
+            config = new ConfigBuilder(packageInfo, dataSource, strategy, template, globalConfig, injection);
+        }
+        if (null == templateEngine) {
+            // 为了兼容之前逻辑,采用 Velocity 引擎 【 默认 】
+            templateEngine = new VelocityTemplateEngine();
+        }
+        templateEngine.setConfigBuilder(config);
+        // 模板引擎初始化执行文件输出
+        templateEngine.init(config).batchOutput().open();
+        logger.debug("==========================文件生成完成!!!==========================");
+    }
+
+    /**
+     * 开放表信息、预留子类重写
+     *
+     * @param config 配置信息
+     * @return ignore
+     */
+    @NotNull
+    protected List<TableInfo> getAllTableInfoList(@NotNull ConfigBuilder config) {
+        return config.getTableInfoList();
+    }
+
+    public ConfigBuilder getConfig() {
+        return config;
+    }
+
+    public InjectionConfig getInjectionConfig() {
+        return injection;
+    }
+
+    public DataSourceConfig getDataSource() {
+        return dataSource;
+    }
+
+    public StrategyConfig getStrategy() {
+        return strategy;
+    }
+
+    public PackageConfig getPackageInfo() {
+        return packageInfo;
+    }
+
+    public TemplateConfig getTemplate() {
+        return template;
+    }
+
+    public GlobalConfig getGlobalConfig() {
+        return globalConfig;
+    }
+}

+ 231 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/FastAutoGenerator.java

@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.generator.config.*;
+import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Scanner;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * mybatis plus FastAutoGenerator
+ *
+ * @author L.cm, lanjerry
+ * @since 2021-07-22
+ */
+public final class FastAutoGenerator {
+
+    /**
+     * 数据源配置 Builder
+     */
+    private final DataSourceConfig.Builder dataSourceConfigBuilder;
+
+    /**
+     * 全局配置 Builder
+     */
+    private final GlobalConfig.Builder globalConfigBuilder;
+
+    /**
+     * 包配置 Builder
+     */
+    private final PackageConfig.Builder packageConfigBuilder;
+
+    /**
+     * 策略配置 Builder
+     */
+    private final StrategyConfig.Builder strategyConfigBuilder;
+
+    /**
+     * 注入配置 Builder
+     */
+    private final InjectionConfig.Builder injectionConfigBuilder;
+
+    /**
+     * 模板配置 Builder
+     */
+    private final TemplateConfig.Builder templateConfigBuilder;
+
+    /**
+     * 模板引擎
+     */
+    private AbstractTemplateEngine templateEngine;
+
+    private FastAutoGenerator(DataSourceConfig.Builder dataSourceConfigBuilder) {
+        this.dataSourceConfigBuilder = dataSourceConfigBuilder;
+        this.globalConfigBuilder = new GlobalConfig.Builder();
+        this.packageConfigBuilder = new PackageConfig.Builder();
+        this.strategyConfigBuilder = new StrategyConfig.Builder();
+        this.injectionConfigBuilder = new InjectionConfig.Builder();
+        this.templateConfigBuilder = new TemplateConfig.Builder();
+    }
+
+    public static FastAutoGenerator create(@NotNull String url, String username, String password) {
+        return new FastAutoGenerator(new DataSourceConfig.Builder(url, username, password));
+    }
+
+    public static FastAutoGenerator create(@NotNull DataSourceConfig.Builder dataSourceConfigBuilder) {
+        return new FastAutoGenerator(dataSourceConfigBuilder);
+    }
+
+    /**
+     * 读取控制台输入内容
+     */
+    private final Scanner scanner = new Scanner(System.in);
+
+    /**
+     * 控制台输入内容读取并打印提示信息
+     *
+     * @param message 提示信息
+     * @return String
+     */
+    public String scannerNext(String message) {
+        System.out.println(message);
+        String nextLine = scanner.nextLine();
+        if (StringUtils.isBlank(nextLine)) {
+            // 如果输入空行继续等待
+            return scanner.next();
+        }
+        return nextLine;
+    }
+
+    /**
+     * 全局配置
+     *
+     * @param consumer 自定义全局配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator dataSourceConfig(Consumer<DataSourceConfig.Builder> consumer) {
+        consumer.accept(this.dataSourceConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator dataSourceConfig(BiConsumer<Function<String, String>, DataSourceConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.dataSourceConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 全局配置
+     *
+     * @param consumer 自定义全局配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator globalConfig(Consumer<GlobalConfig.Builder> consumer) {
+        consumer.accept(this.globalConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator globalConfig(BiConsumer<Function<String, String>, GlobalConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.globalConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 包配置
+     *
+     * @param consumer 自定义包配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator packageConfig(Consumer<PackageConfig.Builder> consumer) {
+        consumer.accept(this.packageConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator packageConfig(BiConsumer<Function<String, String>, PackageConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.packageConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 策略配置
+     *
+     * @param consumer 自定义策略配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator strategyConfig(Consumer<StrategyConfig.Builder> consumer) {
+        consumer.accept(this.strategyConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator strategyConfig(BiConsumer<Function<String, String>, StrategyConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.strategyConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 注入配置
+     *
+     * @param consumer 自定义注入配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator injectionConfig(Consumer<InjectionConfig.Builder> consumer) {
+        consumer.accept(this.injectionConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator injectionConfig(BiConsumer<Function<String, String>, InjectionConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.injectionConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 模板配置
+     *
+     * @param consumer 自定义模板配置
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator templateConfig(Consumer<TemplateConfig.Builder> consumer) {
+        consumer.accept(this.templateConfigBuilder);
+        return this;
+    }
+
+    public FastAutoGenerator templateConfig(BiConsumer<Function<String, String>, TemplateConfig.Builder> biConsumer) {
+        biConsumer.accept(this::scannerNext, this.templateConfigBuilder);
+        return this;
+    }
+
+    /**
+     * 模板引擎配置
+     *
+     * @param templateEngine 模板引擎
+     * @return FastAutoGenerator
+     */
+    public FastAutoGenerator templateEngine(AbstractTemplateEngine templateEngine) {
+        this.templateEngine = templateEngine;
+        return this;
+    }
+
+    public void execute() {
+        new AutoGenerator(this.dataSourceConfigBuilder.build())
+            // 全局配置
+            .global(this.globalConfigBuilder.build())
+            // 包配置
+            .packageInfo(this.packageConfigBuilder.build())
+            // 策略配置
+            .strategy(this.strategyConfigBuilder.build())
+            // 注入配置
+            .injection(this.injectionConfigBuilder.build())
+            // 模板配置
+            .template(this.templateConfigBuilder.build())
+            // 执行
+            .execute(this.templateEngine);
+    }
+}

+ 35 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/IFill.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * 填充接口
+ *
+ * @author nieqiurong
+ * @since 3.5.0 2020/11/30.
+ */
+public interface IFill {
+
+    @NotNull
+    String getName();
+
+    @NotNull
+    FieldFill getFieldFill();
+
+}

+ 34 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/ITemplate.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator;
+
+import com.baomidou.mybatisplus.generator.config.po.TableInfo;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+/**
+ * 渲染模板接口
+ *
+ * @author nieqiurong 2020/11/9.
+ * @since 3.5.0
+ */
+public interface ITemplate {
+
+    @NotNull
+    Map<String, Object> renderData(@NotNull TableInfo tableInfo);
+
+}

+ 90 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/ConstVal.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator.config;
+
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 定义常量
+ *
+ * @author YangHu, tangguo, hubin
+ * @since 2016-08-31
+ */
+public interface ConstVal {
+
+    String MODULE_NAME = "ModuleName";
+
+    String ENTITY = "Entity";
+    String SERVICE = "Service";
+    String SERVICE_IMPL = "ServiceImpl";
+    String MAPPER = "Mapper";
+    String XML = "Xml";
+    String CONTROLLER = "Controller";
+    String PARENT = "Parent";
+
+    String JAVA_TMPDIR = "java.io.tmpdir";
+    String UTF8 = StandardCharsets.UTF_8.name();
+    String UNDERLINE = "_";
+
+    String JAVA_SUFFIX = StringPool.DOT_JAVA;
+    String KT_SUFFIX = ".kt";
+    String XML_SUFFIX = ".xml";
+
+    /**
+     * 实体模板路径
+     */
+    String TEMPLATE_ENTITY_JAVA = "/templates/entity.java";
+
+    /**
+     * 实体模板路径(kotlin模板)
+     */
+    String TEMPLATE_ENTITY_KT = "/templates/entity.kt";
+
+    /**
+     * 控制器模板路径
+     */
+    String TEMPLATE_CONTROLLER = "/templates/controller.java";
+
+    /**
+     * Mapper模板路径
+     */
+    String TEMPLATE_MAPPER = "/templates/mapper.java";
+
+    /**
+     * MapperXml模板路径
+     */
+    String TEMPLATE_XML = "/templates/mapper.xml";
+
+    /**
+     * Service模板路径
+     */
+    String TEMPLATE_SERVICE = "/templates/service.java";
+
+    /**
+     * ServiceImpl模板路径
+     */
+    String TEMPLATE_SERVICE_IMPL = "/templates/serviceImpl.java";
+
+    String VM_LOAD_PATH_KEY = "file.resource.loader.class";
+    String VM_LOAD_PATH_VALUE = "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader";
+
+    String SUPER_MAPPER_CLASS = "com.baomidou.mybatisplus.core.mapper.BaseMapper";
+    String SUPER_SERVICE_CLASS = "com.baomidou.mybatisplus.extension.service.IService";
+    String SUPER_SERVICE_IMPL_CLASS = "com.baomidou.mybatisplus.extension.service.impl.ServiceImpl";
+
+}

+ 470 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/DataSourceConfig.java

@@ -0,0 +1,470 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator.config;
+
+import com.baomidou.mybatisplus.annotation.DbType;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
+import com.baomidou.mybatisplus.generator.config.converts.TypeConverts;
+import com.baomidou.mybatisplus.generator.config.querys.DbQueryDecorator;
+import com.baomidou.mybatisplus.generator.config.querys.DbQueryRegistry;
+import com.baomidou.mybatisplus.generator.query.AbstractDatabaseQuery;
+import com.baomidou.mybatisplus.generator.query.DefaultQuery;
+import com.baomidou.mybatisplus.generator.query.IDatabaseQuery;
+import com.baomidou.mybatisplus.generator.query.SQLQuery;
+import com.baomidou.mybatisplus.generator.type.ITypeConvertHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+
+/**
+ * 数据库配置
+ *
+ * @author YangHu, hcl, hubin
+ * @since 2016/8/30
+ */
+public class DataSourceConfig {
+    protected final Logger logger = LoggerFactory.getLogger(DataSourceConfig.class);
+
+    private DataSourceConfig() {
+    }
+
+    /**
+     * 数据库信息查询
+     */
+    private IDbQuery dbQuery;
+
+    /**
+     * schemaName
+     */
+    private String schemaName;
+
+    /**
+     * 类型转换
+     */
+    private ITypeConvert typeConvert;
+
+    /**
+     * 关键字处理器
+     *
+     * @since 3.3.2
+     */
+    private IKeyWordsHandler keyWordsHandler;
+
+    /**
+     * 驱动连接的URL
+     */
+    private String url;
+
+    /**
+     * 数据库连接用户名
+     */
+    private String username;
+
+    /**
+     * 数据库连接密码
+     */
+    private String password;
+
+    /**
+     * 数据源实例
+     *
+     * @since 3.5.0
+     */
+    private DataSource dataSource;
+
+    /**
+     * 数据库连接
+     *
+     * @since 3.5.0
+     */
+    private Connection connection;
+
+    /**
+     * 数据库连接属性
+     *
+     * @since 3.5.3
+     */
+    private final Map<String, String> connectionProperties = new HashMap<>();
+
+    /**
+     * 查询方式
+     *
+     * @see DefaultQuery 默认查询方式,配合{@link #getTypeConvertHandler()} 使用
+     * @see SQLQuery SQL语句查询方式,配合{@link #typeConvert} 使用
+     * @since 3.5.3
+     */
+    private Class<? extends AbstractDatabaseQuery> databaseQueryClass = DefaultQuery.class;
+
+    /**
+     * 类型转换处理
+     *
+     * @since 3.5.3
+     */
+    private ITypeConvertHandler typeConvertHandler;
+
+    /**
+     * 获取数据库查询
+     */
+    @NotNull
+    public IDbQuery getDbQuery() {
+        if (null == dbQuery) {
+            DbType dbType = getDbType();
+            DbQueryRegistry dbQueryRegistry = new DbQueryRegistry();
+            // 默认 MYSQL
+            dbQuery = Optional.ofNullable(dbQueryRegistry.getDbQuery(dbType))
+                .orElseGet(() -> dbQueryRegistry.getDbQuery(DbType.MYSQL));
+        }
+        return dbQuery;
+    }
+
+    /**
+     * 判断数据库类型
+     *
+     * @return 类型枚举值
+     */
+    @NotNull
+    public DbType getDbType() {
+        return this.getDbType(this.url.toLowerCase());
+    }
+
+    /**
+     * 判断数据库类型
+     *
+     * @param str url
+     * @return 类型枚举值,如果没找到,则返回 null
+     */
+    @NotNull
+    private DbType getDbType(@NotNull String str) {
+        if (str.contains(":mysql:") || str.contains(":cobar:")) {
+            return DbType.MYSQL;
+        } else if (str.contains(":oracle:")) {
+            return DbType.ORACLE;
+        } else if (str.contains(":postgresql:")) {
+            return DbType.POSTGRE_SQL;
+        } else if (str.contains(":sqlserver:")) {
+            return DbType.SQL_SERVER;
+        } else if (str.contains(":db2:")) {
+            return DbType.DB2;
+        } else if (str.contains(":mariadb:")) {
+            return DbType.MARIADB;
+        } else if (str.contains(":sqlite:")) {
+            return DbType.SQLITE;
+        } else if (str.contains(":h2:")) {
+            return DbType.H2;
+        } else if (str.contains(":kingbase:") || str.contains(":kingbase8:")) {
+            return DbType.KINGBASE_ES;
+        } else if (str.contains(":dm:")) {
+            return DbType.DM;
+        } else if (str.contains(":zenith:")) {
+            return DbType.GAUSS;
+        } else if (str.contains(":oscar:")) {
+            return DbType.OSCAR;
+        } else if (str.contains(":firebird:")) {
+            return DbType.FIREBIRD;
+        } else if (str.contains(":xugu:")) {
+            return DbType.XU_GU;
+        } else if (str.contains(":clickhouse:")) {
+            return DbType.CLICK_HOUSE;
+        } else if (str.contains(":sybase:")) {
+            return DbType.SYBASE;
+        } else {
+            return DbType.OTHER;
+        }
+    }
+
+    /**
+     * 获取数据库字段类型转换
+     */
+    @NotNull
+    public ITypeConvert getTypeConvert() {
+        if (null == typeConvert) {
+            DbType dbType = getDbType();
+            // 默认 MYSQL
+            typeConvert = TypeConverts.getTypeConvert(dbType);
+            if (null == typeConvert) {
+                typeConvert = MySqlTypeConvert.INSTANCE;
+            }
+        }
+        return typeConvert;
+    }
+
+    /**
+     * 创建数据库连接对象
+     * 这方法建议只调用一次,毕竟只是代码生成,用一个连接就行。
+     *
+     * @return Connection
+     * @see DbQueryDecorator#getConnection()
+     */
+    @NotNull
+    public Connection getConn() {
+        try {
+            if (connection != null && !connection.isClosed()) {
+                return connection;
+            } else {
+                synchronized (this) {
+                    if (dataSource != null) {
+                        connection = dataSource.getConnection();
+                    } else {
+                        Properties properties = new Properties();
+                        connectionProperties.forEach(properties::setProperty);
+                        properties.put("user", username);
+                        properties.put("password", password);
+                        // 使用元数据查询方式时,有些数据库需要增加属性才能读取注释
+                        this.processProperties(properties);
+                        this.connection = DriverManager.getConnection(url, properties);
+                    }
+                }
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+        return connection;
+    }
+
+    private void processProperties(Properties properties) {
+        if (this.databaseQueryClass.getName().equals(DefaultQuery.class.getName())) {
+            switch (this.getDbType()) {
+                case MYSQL:
+                    properties.put("remarks", "true");
+                    properties.put("useInformationSchema", "true");
+                    break;
+                case ORACLE:
+                    properties.put("remarks", "true");
+                    properties.put("remarksReporting", "true");
+                    break;
+            }
+        }
+    }
+
+    /**
+     * 获取数据库默认schema
+     *
+     * @return 默认schema
+     * @since 3.5.0
+     */
+    @Nullable
+    protected String getDefaultSchema() {
+        DbType dbType = getDbType();
+        String schema = null;
+        if (DbType.POSTGRE_SQL == dbType) {
+            //pg 默认 schema=public
+            schema = "public";
+        } else if (DbType.KINGBASE_ES == dbType) {
+            //kingbase 默认 schema=PUBLIC
+            schema = "PUBLIC";
+        } else if (DbType.DB2 == dbType) {
+            //db2 默认 schema=current schema
+            schema = "current schema";
+        } else if (DbType.ORACLE == dbType) {
+            //oracle 默认 schema=username
+            schema = this.username.toUpperCase();
+        }
+        return schema;
+    }
+
+    @Nullable
+    public String getSchemaName() {
+        return schemaName;
+    }
+
+    @Nullable
+    public IKeyWordsHandler getKeyWordsHandler() {
+        return keyWordsHandler;
+    }
+
+    @NotNull
+    public String getUrl() {
+        return url;
+    }
+
+    @Nullable
+    public String getUsername() {
+        return username;
+    }
+
+    @Nullable
+    public String getPassword() {
+        return password;
+    }
+
+    @NotNull
+    public Class<? extends IDatabaseQuery> getDatabaseQueryClass() {
+        return databaseQueryClass;
+    }
+
+    @Nullable
+    public ITypeConvertHandler getTypeConvertHandler() {
+        return typeConvertHandler;
+    }
+
+    /**
+     * 数据库配置构建者
+     *
+     * @author nieqiurong 2020/10/10.
+     * @since 3.5.0
+     */
+    public static class Builder implements IConfigBuilder<DataSourceConfig> {
+
+        private final DataSourceConfig dataSourceConfig;
+
+        private Builder() {
+            this.dataSourceConfig = new DataSourceConfig();
+        }
+
+        /**
+         * 构造初始化方法
+         *
+         * @param url      数据库连接地址
+         * @param username 数据库账号
+         * @param password 数据库密码
+         */
+        public Builder(@NotNull String url, String username, String password) {
+            this();
+            if (StringUtils.isBlank(url)) {
+                throw new RuntimeException("无法创建文件,请正确输入 url 配置信息!");
+            }
+            this.dataSourceConfig.url = url;
+            this.dataSourceConfig.username = username;
+            this.dataSourceConfig.password = password;
+        }
+
+        /**
+         * 构造初始化方法
+         *
+         * @param dataSource 外部数据源实例
+         */
+        public Builder(@NotNull DataSource dataSource) {
+            this();
+            this.dataSourceConfig.dataSource = dataSource;
+            try {
+                Connection conn = dataSource.getConnection();
+                this.dataSourceConfig.url = conn.getMetaData().getURL();
+                try {
+                    this.dataSourceConfig.schemaName = conn.getSchema();
+                } catch (Throwable exception) {
+                    //ignore  如果使用低版本的驱动,这里由于是1.7新增的方法,所以会报错,如果驱动太低,需要自行指定了。
+                }
+                this.dataSourceConfig.connection = conn;
+                this.dataSourceConfig.username = conn.getMetaData().getUserName();
+            } catch (SQLException ex) {
+                throw new RuntimeException("构建数据库配置对象失败!", ex);
+            }
+        }
+
+        /**
+         * 设置数据库查询实现
+         *
+         * @param dbQuery 数据库查询实现
+         * @return this
+         */
+        public Builder dbQuery(@NotNull IDbQuery dbQuery) {
+            this.dataSourceConfig.dbQuery = dbQuery;
+            return this;
+        }
+
+        /**
+         * 设置数据库schema
+         *
+         * @param schemaName 数据库schema
+         * @return this
+         */
+        public Builder schema(@NotNull String schemaName) {
+            this.dataSourceConfig.schemaName = schemaName;
+            return this;
+        }
+
+        /**
+         * 设置类型转换器
+         *
+         * @param typeConvert 类型转换器
+         * @return this
+         */
+        public Builder typeConvert(@NotNull ITypeConvert typeConvert) {
+            this.dataSourceConfig.typeConvert = typeConvert;
+            return this;
+        }
+
+        /**
+         * 设置数据库关键字处理器
+         *
+         * @param keyWordsHandler 关键字处理器
+         * @return this
+         */
+        public Builder keyWordsHandler(@NotNull IKeyWordsHandler keyWordsHandler) {
+            this.dataSourceConfig.keyWordsHandler = keyWordsHandler;
+            return this;
+        }
+
+        /**
+         * 指定数据库查询方式
+         *
+         * @param databaseQueryClass 查询类
+         * @return this
+         * @since 3.5.3
+         */
+        public Builder databaseQueryClass(@NotNull Class<? extends AbstractDatabaseQuery> databaseQueryClass) {
+            this.dataSourceConfig.databaseQueryClass = databaseQueryClass;
+            return this;
+        }
+
+        /**
+         * 指定类型转换器
+         *
+         * @param typeConvertHandler 类型转换器
+         * @return this
+         * @since 3.5.3
+         */
+        public Builder typeConvertHandler(@NotNull ITypeConvertHandler typeConvertHandler) {
+            this.dataSourceConfig.typeConvertHandler = typeConvertHandler;
+            return this;
+        }
+
+        /**
+         * 增加数据库连接属性
+         *
+         * @param key   属性名
+         * @param value 属性值
+         * @return this
+         * @since 3.5.3
+         */
+        public Builder addConnectionProperty(@NotNull String key, @NotNull String value) {
+            this.dataSourceConfig.connectionProperties.put(key, value);
+            return this;
+        }
+
+
+        /**
+         * 构建数据库配置
+         *
+         * @return 数据库配置
+         */
+        @Override
+        public DataSourceConfig build() {
+            return this.dataSourceConfig;
+        }
+    }
+}

+ 264 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/GlobalConfig.java

@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator.config;
+
+import com.baomidou.mybatisplus.generator.config.rules.DateType;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.function.Supplier;
+
+
+/**
+ * 全局配置
+ *
+ * @author hubin
+ * @since 2016-12-02
+ */
+public class GlobalConfig {
+
+    private GlobalConfig() {
+    }
+
+    protected static final Logger LOGGER = LoggerFactory.getLogger(GlobalConfig.class);
+
+    /**
+     * 生成文件的输出目录【 windows:D://  linux or mac:/tmp 】
+     */
+    private String outputDir = System.getProperty("os.name").toLowerCase().contains("windows") ? "D://" : "/tmp";
+
+    /**
+     * 是否覆盖已有文件(默认 false)(已迁移到策略配置中,3.5.4版本会删除此方法)
+     */
+    @Deprecated
+    private boolean fileOverride;
+
+    /**
+     * 是否打开输出目录
+     */
+    private boolean open = true;
+
+    /**
+     * 作者
+     */
+    private String author = "baomidou";
+
+    /**
+     * 开启 Kotlin 模式(默认 false)
+     */
+    private boolean kotlin;
+
+    /**
+     * 开启 swagger 模式(默认 false 与 springdoc 不可同时使用)
+     */
+    private boolean swagger;
+    /**
+     * 开启 springdoc 模式(默认 false 与 swagger 不可同时使用)
+     */
+    private boolean springdoc;
+
+    /**
+     * 时间类型对应策略
+     */
+    private DateType dateType = DateType.TIME_PACK;
+
+    /**
+     * 获取注释日期
+     *
+     * @since 3.5.0
+     */
+    private Supplier<String> commentDate = () -> new SimpleDateFormat("yyyy-MM-dd").format(new Date());
+
+    /**
+     * 是否生成service 接口(默认 true)
+     * 增加此开关的原因:在某些项目实践中,只需要生成service实现类,不需要抽象sevice接口
+     * 针对某些项目,生成service接口,开发时反而麻烦,这种情况,可以将该属性设置为false
+     */
+    private boolean serviceInterface = true;
+
+    public String getOutputDir() {
+        return outputDir;
+    }
+
+    /**
+     * 是否覆盖已有文件(已迁移到策略配置中,3.5.4版本会删除此方法)
+     */
+    @Deprecated
+    public boolean isFileOverride() {
+        return fileOverride;
+    }
+
+    public boolean isOpen() {
+        return open;
+    }
+
+    public String getAuthor() {
+        return author;
+    }
+
+    public boolean isKotlin() {
+        return kotlin;
+    }
+
+    public boolean isSwagger() {
+        // springdoc 设置优先于 swagger
+        return springdoc ? false : swagger;
+    }
+
+    public boolean isSpringdoc() {
+        return springdoc;
+    }
+
+    @NotNull
+    public DateType getDateType() {
+        return dateType;
+    }
+
+    @NotNull
+    public String getCommentDate() {
+        return commentDate.get();
+    }
+
+    public boolean isServiceInterface() {
+        return serviceInterface;
+    }
+
+    public void setServiceInterface(boolean serviceInterface) {
+        this.serviceInterface = serviceInterface;
+    }
+
+    /**
+     * 全局配置构建
+     *
+     * @author nieqiurong 2020/10/11.
+     * @since 3.5.0
+     */
+    public static class Builder implements IConfigBuilder<GlobalConfig> {
+
+        private final GlobalConfig globalConfig;
+
+        public Builder() {
+            this.globalConfig = new GlobalConfig();
+        }
+
+        /**
+         * 覆盖已有文件(已迁移到策略配置中,3.5.4版本会删除此方法)
+         */
+        @Deprecated
+        public Builder fileOverride() {
+            LOGGER.warn("全局覆盖已有文件的配置已失效,已迁移到策略配置中");
+            this.globalConfig.fileOverride = true;
+            return this;
+        }
+
+        /**
+         * 禁止打开输出目录
+         */
+        public Builder disableOpenDir() {
+            this.globalConfig.open = false;
+            return this;
+        }
+
+        /**
+         * 输出目录
+         */
+        public Builder outputDir(@NotNull String outputDir) {
+            this.globalConfig.outputDir = outputDir;
+            return this;
+        }
+
+        /**
+         * 作者
+         */
+        public Builder author(@NotNull String author) {
+            this.globalConfig.author = author;
+            return this;
+        }
+
+        /**
+         * 开启 kotlin 模式
+         */
+        public Builder enableKotlin() {
+            this.globalConfig.kotlin = true;
+            return this;
+        }
+
+        /**
+         * 开启 swagger 模式
+         */
+        public Builder enableSwagger() {
+            this.globalConfig.swagger = true;
+            return this;
+        }
+
+        /**
+         * 开启 springdoc 模式
+         */
+        public Builder enableSpringdoc() {
+            this.globalConfig.springdoc = true;
+            return this;
+        }
+
+        /**
+         * 不生成service接口
+         * @return
+         */
+        public Builder disableServiceInterface() {
+            this.globalConfig.serviceInterface = false;
+            return this;
+        }
+
+        /**
+         * 时间类型对应策略
+         */
+        public Builder dateType(@NotNull DateType dateType) {
+            this.globalConfig.dateType = dateType;
+            return this;
+        }
+
+        /**
+         * 注释日期获取处理
+         * example: () -> LocalDateTime.now().format(DateTimeFormatter.ISO_DATE)
+         *
+         * @param commentDate 获取注释日期
+         * @return this
+         * @since 3.5.0
+         */
+        public Builder commentDate(@NotNull Supplier<String> commentDate) {
+            this.globalConfig.commentDate = commentDate;
+            return this;
+        }
+
+        /**
+         * 指定注释日期格式化
+         *
+         * @param pattern 格式
+         * @return this
+         * @since 3.5.0
+         */
+        public Builder commentDate(@NotNull String pattern) {
+            return commentDate(() -> new SimpleDateFormat(pattern).format(new Date()));
+        }
+
+        @Override
+        public GlobalConfig build() {
+            return this.globalConfig;
+        }
+    }
+}

+ 26 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/IConfigBuilder.java

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator.config;
+
+/**
+ * 配置构建接口
+ *
+ * @param <T>
+ */
+public interface IConfigBuilder<T> {
+
+    T build();
+}

+ 81 - 0
mybatis-plus-generator/src/main/java/com/baomidou/mybatisplus/generator/config/IDbQuery.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.baomidou.mybatisplus.generator.config;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+/**
+ * 表数据查询接口
+ *
+ * @author hubin
+ * @since 2018-01-16
+ */
+public interface IDbQuery {
+    /**
+     * 表信息查询 SQL
+     */
+    String tablesSql();
+
+    /**
+     * 表字段信息查询 SQL
+     */
+    String tableFieldsSql();
+
+    /**
+     * 表名称
+     */
+    String tableName();
+
+    /**
+     * 表注释
+     */
+    String tableComment();
+
+    /**
+     * 字段名称
+     */
+    String fieldName();
+
+    /**
+     * 字段类型
+     */
+    String fieldType();
+
+    /**
+     * 字段注释
+     */
+    String fieldComment();
+
+    /**
+     * 主键字段
+     */
+    String fieldKey();
+
+    /**
+     * 判断主键是否为identity
+     *
+     * @param results ResultSet
+     * @return 主键是否为identity
+     * @throws SQLException ignore
+     */
+    boolean isKeyIdentity(ResultSet results) throws SQLException;
+
+    /**
+     * 自定义字段名称
+     */
+    String[] fieldCustom();
+}

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików