15 Коммиты ad123d4704 ... e3fa397bf6

Автор SHA1 Сообщение Дата
  nieqiurong e3fa397bf6 新增动态表解析器(基于JsqlParser). 1 месяц назад
  nieqiurong a09bddd5c2 调整动态表解析(支持重写). 1 месяц назад
  nieqiurong 003501bb85 更新快照. 1 месяц назад
  nieqiurong c0de7a4a0d 调整#6719代码. 1 месяц назад
  yuxiaobin 778de65bb3 feat: #6687 多租户插件的查询使用索引的优化:多租户插件:增加配置项expressionAppendMode(默认true表示追加),设置false可以插入到最前方 2 месяцев назад
  nieqiurong cfc53c05d3 新增DdlApplicationRunner多处理器异常控制. 1 месяц назад
  nieqiurong 2b8a08a92a 支持DdlApplicationRunner参数配置. 1 месяц назад
  nieqiurong 36ea865c97 支持自定义脚本运行器参数. 1 месяц назад
  nieqiurong bd41e19763 更新快照. 1 месяц назад
  nieqiurong 4dd12f6250 调整DdlScript. 1 месяц назад
  nieqiurong 4df5724bd0 Update README.md 1 месяц назад
  Johnny Lim c2aabc612e Polish "What is MyBatis-Plus?" in README.md 1 месяц назад
  nieqiurong c9e5cb25f2 移除资源字符编码指定. 1 месяц назад
  nieqiurong 5c0bfd51d5 支持DDL执行自定义异常处理. 1 месяц назад
  nieqiurong ff02afd10f 解耦Spring. 1 месяц назад
16 измененных файлов с 1255 добавлено и 83 удалено
  1. 2 2
      README.md
  2. 7 0
      changelog-temp.md
  3. 128 16
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlHelper.java
  4. 181 40
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScript.java
  5. 73 0
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScriptErrorHandler.java
  6. 35 9
      mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DynamicTableNameInnerInterceptor.java
  7. 30 0
      mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/test/DdlScriptTest.java
  8. 27 4
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-4.9/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java
  9. 30 6
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-5.0/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java
  10. 81 0
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-5.0/src/test/java/com/baomidou/mybatisplus/test/extension/plugins/inner/TenantLineInnerInterceptorTest.java
  11. 36 0
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-common/src/main/java/com/baomidou/mybatisplus/jsqlparser/enums/ExpressionAppendMode.java
  12. 65 0
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/DynamicTableNameHandler.java
  13. 27 3
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java
  14. 101 0
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DynamicTableNameJsqlParserInnerInterceptor.java
  15. 371 0
      mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/test/java/com/baomidou/mybatisplus/test/extension/plugins/inner/DynamicTableNameJsqlParserInnerInterceptorTest.java
  16. 61 3
      spring-boot-starter/mybatis-plus-spring-boot-autoconfigure/src/main/java/com/baomidou/mybatisplus/autoconfigure/DdlApplicationRunner.java

+ 2 - 2
README.md

@@ -41,8 +41,8 @@
 
 ## What is MyBatis-Plus?
 
-MyBatis-Plus is an powerful enhanced toolkit of MyBatis for simplify development. This toolkit provides some efficient,
-useful, out-of-the-box features for MyBatis, use it can effectively save your development time.
+MyBatis-Plus is a powerful and enhanced toolkit of MyBatis for simplifying development.
+It provides efficient, and out-of-the-box features (such as code generation, conditional query builders, pagination plugins...), effectively saving development time
 
 ## Links
 

+ 7 - 0
changelog-temp.md

@@ -4,16 +4,23 @@
 - fix: 修复`AbstractCaffeineJsqlParseCache`异步产生的错误
 - fix: 修复动态SQL解析包含SQL注释(--或#)导致的合并错误
 - fix: 修复`DataChangeRecorderInnerInterceptor`数据比较出现强转异常
+- feat: 支持`DDL`自定义脚本运行器参数
+- feat: 支持`DdlApplicationRunner`参数配置(脚本错误处理,自定义ScriptRunner,多处理器执行异常是否中断)
+- feat: 支持`BaseMultiTableInnerInterceptor`指定追加条件模式 (默认条件追加至末尾,仅作用于select,delete,update)
 - feat: 支持生成器`Entity`指定`serialVersionUID`添加`@Serial`注解
 - feat: 支持生成器`Entity`注解(字段,类注解)自定义处理
 - feat: 支持生成器`Entity`导包自定义处理
 - feat: 支持崖山数据库
 - feat: 支持`Hive2`分页
 - feat: 升级`Gradle`至8.10
+- feat: 支持`DdlHelper`执行自定义异常处理
+- opt: 调整`DdlScript`类方法实现(分离DDL版本记录,优化执行方法)
 - opt: 调整`DbType#GAUSS`数据库名为gauss
 - opt: 调整`JsqlParser`解析线程池指定
 - opt: 移除过时的`FieldStrategy.IGNORED`
 - opt: 移除过时的`GlobalConfig.DbConfig#selectStrategy`
 - opt: 移除过时的`MybatisSqlSessionFactoryBean#typeEnumsPackage`
+- opt: 优化`DdlHelper`资源加载(不再依赖spring或者其他实现)
+- opt: 去除`DdlHelper`中getScriptRunner方法指定的字符集编码
 - doc: 修正`DdlHelper`中注释错误
 -

+ 128 - 16
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlHelper.java

@@ -17,17 +17,17 @@ package com.baomidou.mybatisplus.extension.ddl;
 
 import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.core.toolkit.StringPool;
-import com.baomidou.mybatisplus.extension.spi.CompatibleHelper;
 import com.baomidou.mybatisplus.extension.ddl.history.*;
 import com.baomidou.mybatisplus.extension.toolkit.JdbcUtils;
-import lombok.extern.slf4j.Slf4j;
 import org.apache.ibatis.io.Resources;
+import org.apache.ibatis.jdbc.RuntimeSqlException;
 import org.apache.ibatis.jdbc.ScriptRunner;
 import org.apache.ibatis.jdbc.SqlRunner;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
 
 import javax.sql.DataSource;
 import java.io.*;
-import java.nio.charset.StandardCharsets;
 import java.sql.Connection;
 import java.sql.SQLException;
 import java.time.LocalDateTime;
@@ -35,6 +35,7 @@ import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * DDL 辅助类
@@ -42,37 +43,87 @@ import java.util.Objects;
  * @author hubin
  * @since 2021-06-22
  */
-@Slf4j
 public class DdlHelper {
 
+    private static final Log LOG = LogFactory.getLog(DdlHelper.class);
+
     /**
      * 运行 SQL 脚本文件
      *
      * @param ddlGenerator DDL 生成器
-     * @param connection   数据库连接
+     * @param connection   数据库连接 (自行控制回收)
      * @param sqlFiles     SQL 文件列表
      * @param autoCommit   是否自动提交事务
      * @throws SQLException SQLException
      */
     public static void runScript(IDdlGenerator ddlGenerator, Connection connection, List<String> sqlFiles, boolean autoCommit) throws SQLException {
-        // 执行自定义 DDL 信息
+        runScript(ddlGenerator, connection, sqlFiles, autoCommit, DdlScriptErrorHandler.PrintlnLogErrorHandler.INSTANCE);
+    }
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param connection            数据库连接 (自行控制回收)
+     * @param sqlFiles              SQL 文件列表
+     * @param autoCommit            是否自动提交事务
+     * @param ddlScriptErrorHandler 错误处理器
+     * @throws SQLException SQLException
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, Connection connection, List<String> sqlFiles,
+                                 boolean autoCommit, DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
+        runScript(ddlGenerator, connection, sqlFiles, null, autoCommit, ddlScriptErrorHandler);
+    }
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param connection            数据库连接 (自行控制回收)
+     * @param sqlFiles              SQL 文件列表
+     * @param scriptRunnerConsumer  自定义 ScriptRunner 函数
+     * @param ddlScriptErrorHandler 错误处理器
+     * @throws SQLException SQLException
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, Connection connection, List<String> sqlFiles,
+                                 Consumer<ScriptRunner> scriptRunnerConsumer, DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
+        runScript(ddlGenerator, connection, sqlFiles, scriptRunnerConsumer, false, ddlScriptErrorHandler);
+    }
+
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param connection            数据库连接 (自行控制回收)
+     * @param sqlFiles              SQL 文件列表
+     * @param scriptRunnerConsumer  自定义 ScriptRunner 函数
+     * @param autoCommit            是否自动提交事务
+     * @param ddlScriptErrorHandler 错误处理器
+     * @throws SQLException SQLException
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, Connection connection, List<String> sqlFiles, Consumer<ScriptRunner> scriptRunnerConsumer,
+                                 boolean autoCommit, DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
         final String jdbcUrl = connection.getMetaData().getURL();
         final String schema = DdlHelper.getDatabase(jdbcUrl);
         SqlRunner sqlRunner = new SqlRunner(connection);
         ScriptRunner scriptRunner = getScriptRunner(connection, autoCommit);
+        if (scriptRunnerConsumer != null) {
+            scriptRunnerConsumer.accept(scriptRunner);
+        }
         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;
-                }
+                return null != resultMap && !StringPool.ZERO.equals(String.valueOf(resultMap.get(StringPool.NUM)));
             } catch (SQLException e) {
-                log.error("run script sql:{} , error: {}", sql, e.getMessage());
+                throw new RuntimeSqlException("Check exist table error:", e);
             }
-            return false;
         })) {
             scriptRunner.runScript(new StringReader(ddlGenerator.createDdlHistory()));
         }
@@ -81,7 +132,9 @@ public class DdlHelper {
             try {
                 List<Map<String, Object>> objectMap = sqlRunner.selectAll(ddlGenerator.selectDdlHistory(sqlFile, StringPool.SQL));
                 if (null == objectMap || objectMap.isEmpty()) {
-                    log.debug("run script file: {}", sqlFile);
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug("Run script file: " + sqlFile);
+                    }
                     String[] sqlFileArr = sqlFile.split(StringPool.HASH);
                     if (Objects.equals(2, sqlFileArr.length)) {
                         // 命令间的分隔符
@@ -100,7 +153,9 @@ public class DdlHelper {
                     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());
+                if (ddlScriptErrorHandler != null) {
+                    ddlScriptErrorHandler.handle(sqlFile, e);
+                }
             }
         }
     }
@@ -112,17 +167,75 @@ public class DdlHelper {
      * @param dataSource   数据源
      * @param sqlFiles     SQL 文件列表
      * @param autoCommit   是否自动提交事务
+     * @see #runScript(IDdlGenerator, Connection, List, boolean)
+     * @see #runScript(IDdlGenerator, DataSource, List, boolean, DdlScriptErrorHandler)
+     * @deprecated 3.5.11 方法会吞掉所有异常,建议自行处理.
      */
+    @Deprecated
     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());
+            LOG.error("Run script error: ", e);
+        }
+    }
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param dataSource            数据源
+     * @param sqlFiles              SQL 文件列表
+     * @param autoCommit            是否自动提交事务
+     * @param ddlScriptErrorHandler 错误处理器
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator,
+                                 DataSource dataSource, List<String> sqlFiles, boolean autoCommit,
+                                 DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
+        try (Connection connection = dataSource.getConnection()) {
+            runScript(ddlGenerator, connection, sqlFiles, autoCommit, ddlScriptErrorHandler);
+        }
+    }
+
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param dataSource            数据源
+     * @param sqlFiles              SQL 文件列表
+     * @param scriptRunnerConsumer  自定义 ScriptRunner 处理函数
+     * @param ddlScriptErrorHandler 错误处理器
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator,
+                                 DataSource dataSource, List<String> sqlFiles, Consumer<ScriptRunner> scriptRunnerConsumer,
+                                 DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
+        try (Connection connection = dataSource.getConnection()) {
+            runScript(ddlGenerator, connection, sqlFiles, scriptRunnerConsumer, false, ddlScriptErrorHandler);
+        }
+    }
+
+    /**
+     * 运行 SQL 脚本文件
+     *
+     * @param ddlGenerator          DDL 生成器
+     * @param dataSource            数据源
+     * @param sqlFiles              SQL 文件列表
+     * @param scriptRunnerConsumer  自定义 ScriptRunner 处理函数
+     * @param ddlScriptErrorHandler 错误处理器
+     * @since 3.5.11
+     */
+    public static void runScript(IDdlGenerator ddlGenerator, DataSource dataSource, List<String> sqlFiles,
+                                 Consumer<ScriptRunner> scriptRunnerConsumer, boolean autoCommit, DdlScriptErrorHandler ddlScriptErrorHandler) throws SQLException {
+        try (Connection connection = dataSource.getConnection()) {
+            runScript(ddlGenerator, connection, sqlFiles, scriptRunnerConsumer, autoCommit, ddlScriptErrorHandler);
         }
     }
 
     public static InputStream getInputStream(String path) throws Exception {
-        return CompatibleHelper.getCompatibleSet().getInputStream(path);
+        return Resources.getResourceAsStream(path);
     }
 
     protected static String getNowTime() {
@@ -131,7 +244,6 @@ public class DdlHelper {
 
     public static ScriptRunner getScriptRunner(Connection connection, boolean autoCommit) {
         ScriptRunner scriptRunner = new ScriptRunner(connection);
-        Resources.setCharset(StandardCharsets.UTF_8);
         scriptRunner.setAutoCommit(autoCommit);
         scriptRunner.setEscapeProcessing(false);
         scriptRunner.setRemoveCRs(true);

+ 181 - 40
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScript.java

@@ -15,16 +15,20 @@
  */
 package com.baomidou.mybatisplus.extension.ddl;
 
+import com.baomidou.mybatisplus.core.toolkit.ClassUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.ddl.history.IDdlGenerator;
+import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
 import org.apache.ibatis.jdbc.ScriptRunner;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
 
 import javax.sql.DataSource;
 import java.io.Reader;
 import java.io.StringReader;
 import java.sql.Connection;
 import java.sql.DriverManager;
-import java.sql.SQLException;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -36,26 +40,97 @@ import java.util.function.Consumer;
  */
 public class DdlScript {
 
+    private static final Log LOG = LogFactory.getLog(DdlScript.class);
+
+    /**
+     * 数据源
+     */
     private final DataSource dataSource;
 
-    private final IDdlGenerator ddlGenerator;
 
-    private final boolean autoCommit;
+    /**
+     * DDL生成器
+     *
+     * @deprecated 3.5.11 区分职责,如果需要DDL版本控制请直接使用{@link DdlHelper}
+     */
+    @Deprecated
+    private IDdlGenerator ddlGenerator;
 
+    /**
+     * 是否自动提交
+     */
+    private boolean autoCommit;
+
+    /**
+     * 自定义脚本运行器
+     *
+     * @since 3.5.11
+     */
+    private Consumer<ScriptRunner> scriptRunnerConsumer;
+
+    /**
+     * 非池化执行 (非自动提交)
+     *
+     * @since 3.5.11
+     */
+    public DdlScript(String driverClassName, String url, String user, String password) {
+        this(driverClassName, url, user, password, false);
+    }
+
+    /**
+     * 非池化执行
+     *
+     * @since 3.5.11
+     */
+    public DdlScript(String driverClassName, String url, String user, String password, boolean autoCommit) {
+        this.dataSource = new UnpooledDataSource(driverClassName, url, user, password);
+        this.autoCommit = autoCommit;
+    }
+
+    /**
+     * 创建脚本执行器
+     *
+     * @param dataSource 数据源
+     */
     public DdlScript(DataSource dataSource) {
-        this(dataSource, null);
+        this.dataSource = dataSource;
     }
 
+    /**
+     * 创建脚本执行器
+     *
+     * @param dataSource   数据源
+     * @param ddlGenerator DDL生成器
+     * @deprecated 3.5.11
+     */
+    @Deprecated
     public DdlScript(DataSource dataSource, IDdlGenerator ddlGenerator) {
         this(dataSource, ddlGenerator, false);
     }
 
+    /**
+     * 创建脚本执行器
+     *
+     * @param dataSource   数据源
+     * @param ddlGenerator DDL生成器
+     * @param autoCommit   是否自动提交
+     * @deprecated 3.5.11
+     */
+    @Deprecated
     public DdlScript(DataSource dataSource, IDdlGenerator ddlGenerator, boolean autoCommit) {
         this.dataSource = dataSource;
         this.ddlGenerator = ddlGenerator;
         this.autoCommit = autoCommit;
     }
 
+    /**
+     * 执行 SQL 脚本文件
+     *
+     * @param sqlFiles SQL 脚本文件列表
+     * @see DdlHelper#runScript(IDdlGenerator, DataSource, List, boolean)
+     * @deprecated 3.5.11
+     */
+    @Deprecated
     public void run(List<String> sqlFiles) {
         this.run(sqlFiles, this.autoCommit);
     }
@@ -65,13 +140,39 @@ public class DdlScript {
      *
      * @param sqlFiles   SQL 脚本文件列表
      * @param autoCommit 自动提交事务
+     * @see DdlHelper#runScript(IDdlGenerator, DataSource, List, boolean)
+     * @deprecated 3.5.11
      */
+    @Deprecated
     public void run(List<String> sqlFiles, boolean autoCommit) {
-        DdlHelper.runScript(this.ddlGenerator, this.dataSource, sqlFiles, autoCommit);
+        try (Connection connection = this.dataSource.getConnection()) {
+            DdlHelper.runScript(this.ddlGenerator, connection, sqlFiles, this.scriptRunnerConsumer, autoCommit, DdlScriptErrorHandler.PrintlnLogErrorHandler.INSTANCE);
+        } catch (Exception e) {
+            // TODO 保持兼容,吞掉所有异常
+            LOG.error("Run script error: ", e);
+        }
     }
 
+    /**
+     * 自定义 ScriptRunner
+     *
+     * @param scriptRunnerConsumer 处理函数
+     * @return this
+     * @since 3.5.11
+     */
+    public DdlScript scriptRunner(Consumer<ScriptRunner> scriptRunnerConsumer) {
+        this.scriptRunnerConsumer = scriptRunnerConsumer;
+        return this;
+    }
+
+    /**
+     * 运行 SQL 脚本
+     *
+     * @param sqlScript 脚本内容
+     * @throws Exception {@link org.apache.ibatis.jdbc.RuntimeSqlException}
+     */
     public void run(String sqlScript) throws Exception {
-        this.run(sqlScript, null);
+        this.run(sqlScript, StringPool.SEMICOLON);
     }
 
     /**
@@ -79,86 +180,126 @@ public class DdlScript {
      *
      * @param sqlScript SQL 脚本内容
      * @param delimiter 执行 SQL 分隔符,默认 ; 符号结束执行
-     * @throws Exception
+     * @throws Exception {@link org.apache.ibatis.jdbc.RuntimeSqlException}
      */
     public void run(String sqlScript, String delimiter) throws Exception {
         this.run(new StringReader(sqlScript), this.autoCommit, delimiter);
     }
 
     public void run(Reader reader) throws Exception {
-        this.run(reader, this.autoCommit, null);
+        this.run(reader, this.autoCommit, StringPool.SEMICOLON);
     }
 
     public void run(Reader reader, boolean autoCommit) throws Exception {
-        this.run(reader, autoCommit, null);
+        this.run(reader, autoCommit, StringPool.SEMICOLON);
     }
 
     public void run(Reader reader, boolean autoCommit, String delimiter) throws Exception {
-        this.run(this.dataSource.getConnection(), reader, autoCommit, delimiter);
+        try (Connection connection = this.dataSource.getConnection()) {
+            this.run(connection, reader, autoCommit, delimiter);
+        }
     }
 
     /**
      * 执行 SQL 脚本
      *
-     * @param connection {@link Connection}
+     * @param connection {@link Connection} 调用方需要自行控制关闭
      * @param reader     SQL 脚本内容
      * @param autoCommit 自动提交事务
      * @param delimiter  执行 SQL 分隔符,默认 ; 符号结束执行
      */
     public void run(Connection connection, Reader reader, boolean autoCommit, String delimiter) {
         ScriptRunner scriptRunner = DdlHelper.getScriptRunner(connection, autoCommit);
-        // 设置自定义 SQL 分隔符,默认 ; 符号分割
+        if (scriptRunnerConsumer != null) {
+            scriptRunnerConsumer.accept(scriptRunner);
+        }
         if (StringUtils.isNotBlank(delimiter)) {
             scriptRunner.setDelimiter(delimiter);
         }
-        // 执行 SQL 脚本
         scriptRunner.runScript(reader);
     }
 
+    /**
+     * 以默认分隔符(;) 执行 SQL 脚本
+     *
+     * @param driverClassName   连接驱动名
+     * @param url               连接地址
+     * @param user              数据库用户名
+     * @param password          数据库密码
+     * @param sqlScript         执行 SQL 脚本
+     * @param exceptionConsumer 异常处理
+     * @see DdlScript(String, String, String, String)
+     * @see #run(String, Consumer)
+     * @deprecated 3.5.11
+     */
+    @Deprecated
     public boolean execute(final String driverClassName, final String url, final String user, final String password,
-                           final String sql, Consumer<String> exceptionConsumer) {
-        return this.execute(driverClassName, url, user, password, sql, null, exceptionConsumer);
+                           final String sqlScript, Consumer<String> exceptionConsumer) {
+        return this.execute(driverClassName, url, user, password, sqlScript, StringPool.SEMICOLON, exceptionConsumer);
+    }
+
+    /**
+     * 以默认分隔符(;) 执行 SQL 脚本
+     *
+     * @param sqlScript         执行 SQL脚本
+     * @param exceptionConsumer 异常处理
+     * @since 3.5.11
+     */
+    public boolean run(String sqlScript, Consumer<String> exceptionConsumer) {
+        return this.run(sqlScript, StringPool.SEMICOLON, exceptionConsumer);
     }
 
     /**
-     * jdbc 连接指定 sql 执行
+     * 以指定分隔符 执行 SQL 脚本
      *
      * @param driverClassName   连接驱动名
      * @param url               连接地址
      * @param user              数据库用户名
      * @param password          数据库密码
-     * @param sql               执行 SQL
+     * @param sqlScript         执行 SQL 脚本
      * @param delimiter         执行 SQL 分隔符,默认 ; 符号结束执行
      * @param exceptionConsumer 异常处理
-     * @return
+     * @return 操作结果
+     * @deprecated 3.5.11  {@link #run(String, String, Consumer)}
      */
+    @Deprecated
     public boolean execute(final String driverClassName, final String url, final String user, final String password,
-                           final String sql, String delimiter, Consumer<String> exceptionConsumer) {
-        Connection connection = null;
-        try {
-            Class.forName(driverClassName);
-            connection = DriverManager.getConnection(url, user, password);
-            // 执行 SQL 脚本
-            this.run(connection, new StringReader(sql), this.autoCommit, delimiter);
+                           final String sqlScript, String delimiter, Consumer<String> exceptionConsumer) {
+        //一般不需要显示加载,只有很旧很旧的驱动才需要
+        if (StringUtils.isNotBlank(driverClassName)) {
+            Class<?> driverClass = ClassUtils.toClassConfident(driverClassName);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Load driver class: " + driverClass.getName());
+            }
+        }
+        try (Connection connection = DriverManager.getConnection(url, user, password)) {
+            this.run(connection, new StringReader(sqlScript), this.autoCommit, delimiter);
             return true;
         } catch (Exception e) {
-            if (null != connection) {
-                try {
-                    connection.rollback();
-                } catch (SQLException ex) {
-                    ex.printStackTrace();
-                }
-            }
+            LOG.error("Execute sqlScript: " + sqlScript + " , fail:", e);
+            exceptionConsumer.accept(e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * 以指定分隔符 执行 SQL 脚本
+     *
+     * @param sqlScript         执行 SQL 脚本
+     * @param exceptionConsumer 异常处理
+     * @param delimiter         执行 SQL 分隔符,默认 ; 符号结束执行
+     * @return 操作结果
+     * @since 3.5.11
+     */
+    public boolean run(String sqlScript, String delimiter, Consumer<String> exceptionConsumer) {
+        try (Connection connection = dataSource.getConnection()) {
+            this.run(connection, new StringReader(sqlScript), this.autoCommit, delimiter);
+            return true;
+        } catch (Exception e) {
+            LOG.error("Execute sqlScript: " + sqlScript + " , fail:", e);
             exceptionConsumer.accept(e.getMessage());
-        } finally {
-            if (null != connection) {
-                try {
-                    connection.close();
-                } catch (SQLException ex) {
-                    ex.printStackTrace();
-                }
-            }
         }
         return false;
     }
+
 }

+ 73 - 0
mybatis-plus-extension/src/main/java/com/baomidou/mybatisplus/extension/ddl/DdlScriptErrorHandler.java

@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2011-2025, 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.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
+import java.sql.SQLException;
+
+
+/**
+ * 错误处理器
+ *
+ * @author nieqiurong
+ * @since 3.5.11
+ */
+@FunctionalInterface
+public interface DdlScriptErrorHandler {
+
+    /**
+     * 错误处理
+     *
+     * @param sqlFile   执行的sql文件
+     * @param exception 异常信息 {@link org.apache.ibatis.jdbc.RuntimeSqlException}
+     * @throws SQLException SQLException
+     */
+    void handle(String sqlFile, Exception exception) throws SQLException;
+
+    /**
+     * 打印错误日志 (继续执行)
+     */
+    class PrintlnLogErrorHandler implements DdlScriptErrorHandler {
+
+        public static final PrintlnLogErrorHandler INSTANCE = new PrintlnLogErrorHandler();
+
+        public static final Log log = LogFactory.getLog(PrintlnLogErrorHandler.class);
+
+        @Override
+        public void handle(String sqlFile, Exception throwable) {
+            log.error("run script sql:" + sqlFile + ", error: ", throwable);
+        }
+    }
+
+    /**
+     * 抛出错误 (中断后续文件执行)
+     */
+    class ThrowsErrorHandler implements DdlScriptErrorHandler {
+
+        public static final ThrowsErrorHandler INSTANCE = new ThrowsErrorHandler();
+
+        @Override
+        public void handle(String sqlFile, Exception throwable) throws SQLException {
+            throw new SQLException("Execute " + sqlFile + " fail. ", throwable);
+        }
+
+    }
+
+}
+
+

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

@@ -16,12 +16,10 @@
 package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
-import com.baomidou.mybatisplus.core.toolkit.ExceptionUtils;
 import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
 import com.baomidou.mybatisplus.core.toolkit.TableNameParser;
 import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import org.apache.ibatis.executor.Executor;
 import org.apache.ibatis.executor.statement.StatementHandler;
@@ -44,14 +42,28 @@ import java.util.List;
  */
 @Getter
 @Setter
-@NoArgsConstructor
 @SuppressWarnings({"rawtypes"})
 public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
+
+    /**
+     * 回调处理
+     */
     private Runnable hook;
+
     /**
-     * 表名处理器,是否处理表名的情况都在该处理器中自行判断
+     * 表名处理器,是否 处理表名的情况都在该处理器中自行判断
      */
-    private TableNameHandler tableNameHandler;
+    private TableNameHandler tableNameHandler = (sql, tableName) -> sql;
+
+    /**
+     * 默认构建
+     *
+     * @see #DynamicTableNameInnerInterceptor(TableNameHandler)
+     * @deprecated 3.5.11
+     */
+    @Deprecated
+    public DynamicTableNameInnerInterceptor() {
+    }
 
     public DynamicTableNameInnerInterceptor(TableNameHandler tableNameHandler) {
         this.tableNameHandler = tableNameHandler;
@@ -79,7 +91,23 @@ public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
     }
 
     public String changeTable(String sql) {
-        ExceptionUtils.throwMpe(null == tableNameHandler, "Please implement TableNameHandler processing logic");
+        try {
+            return processTableName(sql);
+        } finally {
+            if (hook != null) {
+                hook.run();
+            }
+        }
+    }
+
+    /**
+     * 处理表名解析替换
+     *
+     * @param sql 原始sql
+     * @return 处理完的sql
+     * @since 3.5.11
+     */
+    protected String processTableName(String sql) {
         TableNameParser parser = new TableNameParser(sql);
         List<TableNameParser.SqlToken> names = new ArrayList<>();
         parser.accept(names::add);
@@ -96,9 +124,7 @@ public class DynamicTableNameInnerInterceptor implements InnerInterceptor {
         if (last != sql.length()) {
             builder.append(sql.substring(last));
         }
-        if (hook != null) {
-            hook.run();
-        }
         return builder.toString();
     }
+
 }

+ 30 - 0
mybatis-plus-extension/src/test/java/com/baomidou/mybatisplus/test/DdlScriptTest.java

@@ -0,0 +1,30 @@
+package com.baomidou.mybatisplus.test;
+
+import com.baomidou.mybatisplus.extension.ddl.DdlScript;
+import org.h2.Driver;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DdlScriptTest {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DdlScriptTest.class);
+
+    @Test
+    void test() throws Exception {
+        var ddlScript = new DdlScript(Driver.class.getName(),
+            "jdbc:h2:mem:test;MODE=mysql;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE",
+            "sa", "", true).scriptRunner(scriptRunner -> {
+                scriptRunner.setLogWriter(null);
+        });
+        LOGGER.info("--------------execute----------------");
+        ddlScript.run( "select 1 from dual;", msg ->{});
+        LOGGER.info("--------------execute----------------");
+        ddlScript.run( "select 2 from dual;", msg ->{});
+        LOGGER.info("--------------run----------------");
+        ddlScript.run("select 1 from dual;");
+        LOGGER.info("--------------run----------------");
+        ddlScript.run("select 3 from dual#", "#");
+    }
+
+}

+ 27 - 4
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-4.9/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java

@@ -17,6 +17,7 @@ package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
+import com.baomidou.mybatisplus.jsqlparser.enums.ExpressionAppendMode;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
@@ -47,6 +48,13 @@ import java.util.stream.Collectors;
 @SuppressWarnings({"rawtypes"})
 public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
 
+    /**
+     * 条件表达式追加模式 (默认放置最后,仅作用于update,delete,select)
+     *
+     * @since 3.5.11
+     */
+    private ExpressionAppendMode expressionAppendMode = ExpressionAppendMode.LAST;
+
     protected void processSelectBody(Select selectBody, final String whereSegment) {
         if (selectBody == null) {
             return;
@@ -76,14 +84,29 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
         }
         if (where != null) {
             if (where instanceof OrExpression) {
-                return new AndExpression(new Parenthesis(where), expression);
+                return appendExpression(new Parenthesis(where), expression);
             } else {
-                return new AndExpression(where, expression);
+                return appendExpression(where, expression);
             }
         }
         return expression;
     }
 
+    /**
+     * 追加表达式,默认追加到后面,可以配置变量 {@link #expressionAppendMode} 来控制追加到前面还是后面
+     *
+     * @param currentExpression 原sql的条件表达式
+     * @param injectExpression  注入的表达式
+     * @return 追加了条件的完整表达式(where条件 / on条件)
+     * @since 3.5.11
+     */
+    protected Expression appendExpression(Expression currentExpression, Expression injectExpression) {
+        if (ExpressionAppendMode.LAST == expressionAppendMode || expressionAppendMode == null) {
+            return new AndExpression(currentExpression, injectExpression);
+        } else {
+            return new AndExpression(injectExpression, currentExpression);
+        }
+    }
     /**
      * 处理 PlainSelect
      */
@@ -403,9 +426,9 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
             return injectExpression;
         }
         if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new Parenthesis(currentExpression), injectExpression);
+            return appendExpression(new Parenthesis(currentExpression), injectExpression);
         } else {
-            return new AndExpression(currentExpression, injectExpression);
+            return appendExpression(currentExpression, injectExpression);
         }
     }
 

+ 30 - 6
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-5.0/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java

@@ -17,6 +17,7 @@ package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
+import com.baomidou.mybatisplus.jsqlparser.enums.ExpressionAppendMode;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
@@ -48,6 +49,13 @@ import java.util.stream.Collectors;
 @SuppressWarnings({"rawtypes"})
 public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
 
+    /**
+     * 条件表达式追加模式 (默认放置最后,仅作用于update,delete,select)
+     *
+     * @since 3.5.11
+     */
+    private ExpressionAppendMode expressionAppendMode = ExpressionAppendMode.LAST;
+
     protected void processSelectBody(Select selectBody, final String whereSegment) {
         if (selectBody == null) {
             return;
@@ -77,9 +85,9 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
         }
         if (where != null) {
             if (where instanceof OrExpression) {
-                return new AndExpression(new ParenthesedExpressionList<>(where), expression);
+                return appendExpression(new ParenthesedExpressionList<>(where), expression);
             } else {
-                return new AndExpression(where, expression);
+                return appendExpression(where, expression);
             }
         }
         return expression;
@@ -382,9 +390,9 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
         }
         // 构造每张表的条件
         List<Expression> expressions = tables.stream()
-                .map(item -> buildTableExpression(item, currentExpression, whereSegment))
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
+            .map(item -> buildTableExpression(item, currentExpression, whereSegment))
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList());
 
         // 没有表需要处理直接返回
         if (CollectionUtils.isEmpty(expressions)) {
@@ -404,9 +412,25 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
             return injectExpression;
         }
         if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new ParenthesedExpressionList<>(currentExpression), injectExpression);
+            return appendExpression(new ParenthesedExpressionList<>(currentExpression), injectExpression);
         } else {
+            return appendExpression(currentExpression, injectExpression);
+        }
+    }
+
+    /**
+     * 追加表达式,默认追加到后面,可以配置变量 {@link #expressionAppendMode} 来控制追加到前面还是后面
+     *
+     * @param currentExpression 原sql的条件表达式
+     * @param injectExpression  注入的表达式
+     * @return 追加了条件的完整表达式(where条件 / on条件)
+     * @since 3.5.11
+     */
+    protected Expression appendExpression(Expression currentExpression, Expression injectExpression) {
+        if (ExpressionAppendMode.LAST == expressionAppendMode || expressionAppendMode == null) {
             return new AndExpression(currentExpression, injectExpression);
+        } else {
+            return new AndExpression(injectExpression, currentExpression);
         }
     }
 

+ 81 - 0
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-5.0/src/test/java/com/baomidou/mybatisplus/test/extension/plugins/inner/TenantLineInnerInterceptorTest.java

@@ -2,8 +2,10 @@ package com.baomidou.mybatisplus.test.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
 import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import com.baomidou.mybatisplus.jsqlparser.enums.ExpressionAppendMode;
 import net.sf.jsqlparser.expression.Expression;
 import net.sf.jsqlparser.expression.LongValue;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -31,6 +33,28 @@ class TenantLineInnerInterceptorTest {
         }
     });
 
+    private final TenantLineInnerInterceptor interceptor2 = new TenantLineInnerInterceptor(new TenantLineHandler() {
+        private boolean ignoreFirst;// 需要执行 getTenantId 前必须先执行 ignoreTable
+
+        @Override
+        public Expression getTenantId() {
+            assertThat(ignoreFirst).isEqualTo(true);
+            ignoreFirst = false;
+            return new LongValue(1);
+        }
+
+        @Override
+        public boolean ignoreTable(String tableName) {
+            ignoreFirst = true;
+            return tableName.startsWith("with_as");
+        }
+    });
+
+    @BeforeEach
+    void init() {
+        interceptor2.setExpressionAppendMode(ExpressionAppendMode.FIRST);
+    }
+
     @Test
     void insert() {
         // plain
@@ -65,6 +89,59 @@ class TenantLineInnerInterceptorTest {
         assertSql("insert into entity (id,name) select t.* from (select id,name from entity3 e3) t",
             "INSERT INTO entity (id, name, tenant_id) SELECT t.* FROM (SELECT id, name, tenant_id FROM entity3 e3 WHERE e3.tenant_id = 1) t");
     }
+    @Test
+    void crud2() {
+        assertSql2("select * from entity where id = ?",
+            "SELECT * FROM entity WHERE tenant_id = 1 AND id = ?");
+
+        assertSql2("select * from entity where id = ? or name = ?",
+            "SELECT * FROM entity WHERE tenant_id = 1 AND (id = ? OR name = ?)");
+
+        assertSql2("SELECT * FROM entity WHERE (id = ? OR name = ?)",
+            "SELECT * FROM entity WHERE tenant_id = 1 AND (id = ? OR name = ?)");
+
+        /* not */
+        assertSql2("SELECT * FROM entity WHERE not (id = ? OR name = ?)",
+            "SELECT * FROM entity WHERE tenant_id = 1 AND NOT (id = ? OR name = ?)");
+
+        assertSql2("SELECT * FROM entity u WHERE not (u.id = ? OR u.name = ?)",
+            "SELECT * FROM entity u WHERE u.tenant_id = 1 AND NOT (u.id = ? OR u.name = ?)");
+
+        //leftjoin
+        assertSql2("SELECT * FROM entity e " +
+                "left join entity1 e1 on e1.id = e.id " +
+                "WHERE e.id = ? OR e.name = ?",
+            "SELECT * FROM entity e " +
+                "LEFT JOIN entity1 e1 ON e1.tenant_id = 1 AND e1.id = e.id " +
+                "WHERE e.tenant_id = 1 AND (e.id = ? OR e.name = ?)");
+
+
+
+        assertSql2("SELECT * FROM entity e WHERE e.id IN (select e1.id from entity1 e1 where e1.id = ?)",
+            "SELECT * FROM entity e WHERE e.tenant_id = 1 AND e.id IN (SELECT e1.id FROM entity1 e1 WHERE e1.tenant_id = 1 AND e1.id = ?)");
+        // 在最前
+        assertSql2("SELECT * FROM entity e WHERE e.id IN " +
+                "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
+            "SELECT * FROM entity e WHERE e.tenant_id = 1 AND e.id IN " +
+                "(SELECT e1.id FROM entity1 e1 WHERE e1.tenant_id = 1 AND e1.id = ?) AND e.id = ?");
+        // 在最后
+        assertSql2("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
+                "(select e1.id from entity1 e1 where e1.id = ?)",
+            "SELECT * FROM entity e WHERE e.tenant_id = 1 AND e.id = ? AND e.id IN " +
+                "(SELECT e1.id FROM entity1 e1 WHERE e1.tenant_id = 1 AND e1.id = ?)");
+        // 在中间
+        assertSql2("SELECT * FROM entity e WHERE e.id = ? and e.id IN " +
+                "(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?",
+            "SELECT * FROM entity e WHERE e.tenant_id = 1 AND e.id = ? AND e.id IN " +
+                "(SELECT e1.id FROM entity1 e1 WHERE e1.tenant_id = 1 AND e1.id = ?) AND e.id = ?");
+
+        /* EXISTS */
+        assertSql2("SELECT * FROM entity e WHERE EXISTS (select e1.id from entity1 e1 where e1.id = ?)",
+            "SELECT * FROM entity e WHERE e.tenant_id = 1 AND EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.tenant_id = 1 AND e1.id = ?)");
+
+        assertSql2("SELECT EXISTS (SELECT 1 FROM entity1 e WHERE e.id = ? LIMIT 1)","SELECT EXISTS (SELECT 1 FROM entity1 e WHERE e.tenant_id = 1 AND e.id = ? LIMIT 1)");
+
+    }
 
     @Test
     void delete() {
@@ -467,4 +544,8 @@ class TenantLineInnerInterceptorTest {
     void assertSql(String sql, String targetSql) {
         assertThat(interceptor.parserSingle(sql, null)).isEqualTo(targetSql);
     }
+
+    void assertSql2(String sql, String targetSql) {
+        assertThat(interceptor2.parserSingle(sql, null)).isEqualTo(targetSql);
+    }
 }

+ 36 - 0
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser-common/src/main/java/com/baomidou/mybatisplus/jsqlparser/enums/ExpressionAppendMode.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2011-2025, 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.jsqlparser.enums;
+
+/**
+ * 表达式追加模式
+ *
+ * @author nieqiurong
+ * @since 3.5.11
+ */
+public enum ExpressionAppendMode {
+
+    /**
+     * 追加至首位
+     */
+    FIRST,
+
+    /**
+     * 追加至末尾
+     */
+    LAST
+
+}

+ 65 - 0
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/DynamicTableNameHandler.java

@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2011-2025, 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;
+
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
+import net.sf.jsqlparser.schema.Table;
+import net.sf.jsqlparser.util.TablesNamesFinder;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * 动态表名解析处理
+ * <p>1.无法保留sql注释(例如 select * from test; --这是个查询 处理完会变成 select * from test)</p>
+ * <p>2.无法保留语句分隔符;(例如 select * from test; 处理完会变成 select * from test )</p>
+ * <p>3.如果使用转义符包裹了表名需要自行处理</p>
+ *
+ * @author nieqiurong
+ * @since 3.5.11
+ */
+public class DynamicTableNameHandler extends TablesNamesFinder<Void> {
+
+    private final String originSql;
+
+    private final TableNameHandler tableNameHandler;
+
+    private final Set<Table> set = new HashSet<>();
+
+    public DynamicTableNameHandler(String originSql, TableNameHandler tableNameHandler) {
+        this.originSql = originSql;
+        this.tableNameHandler = tableNameHandler;
+        init(false);
+    }
+
+    @Override
+    protected String extractTableName(Table table) {
+        String originalTableName = table.getName();
+        if (table.getASTNode() == null) {
+            return originalTableName;
+        }
+        if (set.add(table)) {
+            String tableName = tableNameHandler.dynamicTableName(originSql, originalTableName);
+            if (StringUtils.isNotBlank(tableName)) {
+                table.setName(tableName);
+                return tableName;
+            }
+        }
+        return originalTableName;
+    }
+
+}

+ 27 - 3
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/BaseMultiTableInnerInterceptor.java

@@ -17,6 +17,7 @@ package com.baomidou.mybatisplus.extension.plugins.inner;
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
+import com.baomidou.mybatisplus.jsqlparser.enums.ExpressionAppendMode;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
@@ -48,6 +49,13 @@ import java.util.stream.Collectors;
 @SuppressWarnings({"rawtypes"})
 public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport implements InnerInterceptor {
 
+    /**
+     * 条件表达式追加模式 (默认放置最后,仅作用于update,delete,select)
+     *
+     * @since 3.5.11
+     */
+    private ExpressionAppendMode expressionAppendMode = ExpressionAppendMode.LAST;
+
     protected void processSelectBody(Select selectBody, final String whereSegment) {
         if (selectBody == null) {
             return;
@@ -77,9 +85,9 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
         }
         if (where != null) {
             if (where instanceof OrExpression) {
-                return new AndExpression(new ParenthesedExpressionList<>(where), expression);
+                return appendExpression(new ParenthesedExpressionList<>(where), expression);
             } else {
-                return new AndExpression(where, expression);
+                return appendExpression(where, expression);
             }
         }
         return expression;
@@ -403,9 +411,25 @@ public abstract class BaseMultiTableInnerInterceptor extends JsqlParserSupport i
             return injectExpression;
         }
         if (currentExpression instanceof OrExpression) {
-            return new AndExpression(new ParenthesedExpressionList<>(currentExpression), injectExpression);
+            return appendExpression(new ParenthesedExpressionList<>(currentExpression), injectExpression);
         } else {
+            return appendExpression(currentExpression, injectExpression);
+        }
+    }
+
+    /**
+     * 追加表达式,默认追加到后面,可以配置变量 {@link #expressionAppendMode} 来控制追加到前面还是后面
+     *
+     * @param currentExpression 原sql的条件表达式
+     * @param injectExpression  注入的表达式
+     * @return 追加了条件的完整表达式(where条件 / on条件)
+     * @since 3.5.11
+     */
+    protected Expression appendExpression(Expression currentExpression, Expression injectExpression) {
+        if (ExpressionAppendMode.LAST == expressionAppendMode || expressionAppendMode == null) {
             return new AndExpression(currentExpression, injectExpression);
+        } else {
+            return new AndExpression(injectExpression, currentExpression);
         }
     }
 

+ 101 - 0
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/main/java/com/baomidou/mybatisplus/extension/plugins/inner/DynamicTableNameJsqlParserInnerInterceptor.java

@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2011-2025, 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.exceptions.MybatisPlusException;
+import com.baomidou.mybatisplus.extension.DynamicTableNameHandler;
+import com.baomidou.mybatisplus.extension.parser.JsqlParserGlobal;
+import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
+import lombok.Setter;
+import net.sf.jsqlparser.statement.Statement;
+import org.apache.ibatis.logging.Log;
+import org.apache.ibatis.logging.LogFactory;
+
+/**
+ * 动态表处理器 (基于JsqlParser解析器)
+ * <p>默认情况下,如果JsqlParser解析不了,则调用父类的解析进行处理</p>
+ * <p>默认情况下,如果无法处理此sql语句将忽略异常打印日志继续执行</p>
+ *
+ * @author nieqiurong
+ * @see DynamicTableNameHandler
+ * @since 3.5.11
+ */
+@Setter
+public class DynamicTableNameJsqlParserInnerInterceptor extends DynamicTableNameInnerInterceptor {
+
+    /**
+     * 日志实例
+     */
+    private static final Log LOG = LogFactory.getLog(DynamicTableNameJsqlParserInnerInterceptor.class);
+
+    /**
+     * 是否忽略解析异常
+     */
+    private boolean ignoreException = false;
+
+    /**
+     * 当JsqlParser无法解析语句时是否进行调用父类继续解析处理
+     *
+     * @see com.baomidou.mybatisplus.core.toolkit.TableNameParser
+     */
+    private boolean shouldFallback = true;
+
+    /**
+     * 是否打印解析错误日志
+     */
+    private boolean printlnErrorLog = true;
+
+    @Deprecated
+    public DynamicTableNameJsqlParserInnerInterceptor() {
+    }
+
+    public DynamicTableNameJsqlParserInnerInterceptor(TableNameHandler tableNameHandler) {
+        super(tableNameHandler);
+    }
+
+    @Override
+    protected String processTableName(String sql) {
+        try {
+            Statement statement = JsqlParserGlobal.parse(sql);
+            statement.accept(new DynamicTableNameHandler(sql, super.getTableNameHandler()));
+            return statement.toString();
+        } catch (Exception exception) {
+            printlnErrorLog("Ignoring table name processing exception: " + exception.getMessage());
+            return handleFallback(sql, exception);
+        }
+    }
+
+    protected void printlnErrorLog(String log) {
+        if (printlnErrorLog) {
+            LOG.error(log);
+        }
+    }
+
+    private String handleFallback(String sql, Exception originalException) {
+        if (shouldFallback) {
+            try {
+                return super.processTableName(sql);
+            } catch (Exception e) {
+                printlnErrorLog("Fallback to parent process failed, ignoring exception : " + e.getMessage());
+            }
+        }
+        if (ignoreException) {
+            return sql;
+        }
+        throw new MybatisPlusException("Table name processing failed and fallback not allowed", originalException);
+    }
+
+}

+ 371 - 0
mybatis-plus-jsqlparser-support/mybatis-plus-jsqlparser/src/test/java/com/baomidou/mybatisplus/test/extension/plugins/inner/DynamicTableNameJsqlParserInnerInterceptorTest.java

@@ -0,0 +1,371 @@
+package com.baomidou.mybatisplus.test.extension.plugins.inner;
+
+import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameJsqlParserInnerInterceptor;
+import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @author nieqiurong
+ */
+class DynamicTableNameJsqlParserInnerInterceptorTest {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DynamicTableNameJsqlParserInnerInterceptor.class);
+
+    private static final DynamicTableNameJsqlParserInnerInterceptor interceptor;
+
+    static {
+        interceptor = new DynamicTableNameJsqlParserInnerInterceptor((sql, tableName) -> {
+            LOGGER.info("process table : {}", tableName);
+            if (tableName.endsWith("`") || tableName.endsWith("]")) {
+                char first = tableName.charAt(0);
+                char last = tableName.charAt(tableName.length()-1);
+                return first + SqlParserUtils.removeWrapperSymbol(tableName) + "_r" + last;
+            }
+            return tableName + "_r";
+        });
+        interceptor.setShouldFallback(true);
+    }
+
+    @Test
+    @SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"})
+    void test() {
+        // 表名相互包含
+        String origin = "SELECT * FROM t_user, t_user_role";
+        assertEquals("SELECT * FROM t_user_r, t_user_role_r", interceptor.changeTable(origin));
+
+        // 表名在末尾
+        origin = "SELECT * FROM t_user";
+        assertEquals("SELECT * FROM t_user_r", interceptor.changeTable(origin));
+
+        // 表名前后有注释
+        origin = "SELECT * FROM /**/t_user/* t_user */";
+        assertEquals("SELECT * FROM t_user_r", interceptor.changeTable(origin));
+
+        // 值中带有表名
+        origin = "SELECT * FROM t_user WHERE name = 't_user'";
+        assertEquals("SELECT * FROM t_user_r WHERE name = 't_user'", interceptor.changeTable(origin));
+
+        // 别名被声明要替换
+        origin = "SELECT t_user.* FROM t_user_real t_user";
+        assertEquals("SELECT t_user.* FROM t_user_real_r t_user", interceptor.changeTable(origin));
+
+        // 别名被声明要替换
+        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_r e ON e.id = t.id", interceptor.changeTable(origin));
+    }
+
+    @Test
+    void testCreateTable() {
+        var sql = """
+            CREATE TABLE `tag`  (
+              `id` int(11) NOT NULL AUTO_INCREMENT,
+              `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '标签名字',
+              `type` int(11) NULL DEFAULT NULL COMMENT '所属类别:0文章,1类别',
+              PRIMARY KEY (`id`) USING BTREE
+            ) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '标签' ROW_FORMAT = Dynamic;
+            """;
+        assertEquals("CREATE TABLE `tag_r` (`id` int (11) NOT NULL AUTO_INCREMENT, `name` varchar (50) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '标签名字', `type` int (11) NULL DEFAULT NULL COMMENT '所属类别:0文章,1类别', PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '标签' ROW_FORMAT = Dynamic", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testCreateTableIfNotExists() {
+        var sql = """
+            CREATE TABLE IF NOT EXISTS `user_info` (
+                `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+                `username` VARCHAR(50) NOT NULL UNIQUE,
+                `email` VARCHAR(100) NOT NULL UNIQUE,
+                `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+            """;
+        assertEquals("CREATE TABLE IF NOT EXISTS `user_info_r` (`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `username` VARCHAR (50) NOT NULL UNIQUE, `email` VARCHAR (100) NOT NULL UNIQUE, `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testDropTableIfExists() {
+        var sql = "DROP TABLE IF EXISTS `tag`";
+        assertEquals("DROP TABLE IF EXISTS `tag_r`", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testIssues6730() {
+        // https://github.com/baomidou/mybatis-plus/issues/6730
+        var sql = "select * from user order by top_bottom_sort desc, 0- EXTRACT(EPOCH FROM req_delivery_time) desc";
+        assertEquals("SELECT * FROM user_r ORDER BY top_bottom_sort DESC, 0 - EXTRACT(EPOCH FROM req_delivery_time) DESC", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testSelectJoin() {
+        var sql = "SELECT * FROM entity e join entity1 e1 on e1.id = e.id WHERE e.id = ? OR e.name = ?";
+        assertEquals("SELECT * FROM entity_r e JOIN entity1_r e1 ON e1.id = e.id WHERE e.id = ? OR e.name = ?", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testSelectWithAs() {
+        var sql = "with with_as_A as (select * from entity) select * from with_as_A";
+        assertEquals("WITH with_as_A AS (SELECT * FROM entity_r) SELECT * FROM with_as_A_r", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testDuplicateKeyUpdate() {
+        var sql = "INSERT INTO entity (name,age) VALUES ('秋秋',18),('秋秋','22') ON DUPLICATE KEY UPDATE age=18";
+        assertEquals("INSERT INTO entity_r (name, age) VALUES ('秋秋', 18), ('秋秋', '22') ON DUPLICATE KEY UPDATE age = 18", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testDelete() {
+        var sql = "delete from entity where id = ?";
+        assertEquals("DELETE FROM entity_r WHERE id = ?", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testUpdate() {
+        var sql = "update entity set name = ? where id = ?";
+        assertEquals("UPDATE entity_r SET name = ? WHERE id = ?", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void testPartition() {
+        // 这种jsql解析不了
+        var sql = """
+            -- 查询2023年Q2分区数据
+            SELECT\s
+                region,
+                SUM(gross_profit) AS 区域总利润,
+                AVG(order_value) AS 平均订单金额
+            FROM\s
+                sales_data
+            PARTITION BY\s
+                (TO_DATE(order_date, 'YYYY-MM-DD'))
+            INTERVAL MONTHLY
+            FOR PARTITION BETWEEN '2023-04-01' AND '2023-06-30'
+            GROUP BY\s
+                region;
+            """;
+        assertEquals("-- 查询2023年Q2分区数据\n" +
+            "SELECT \n" +
+            "    region,\n" +
+            "    SUM(gross_profit) AS 区域总利润,\n" +
+            "    AVG(order_value) AS 平均订单金额\n" +
+            "FROM \n" +
+            "    sales_data_r\n" +
+            "PARTITION BY \n" +
+            "    (TO_DATE(order_date, 'YYYY-MM-DD'))\n" +
+            "INTERVAL MONTHLY\n" +
+            "FOR PARTITION BETWEEN '2023-04-01' AND '2023-06-30'\n" +
+            "GROUP BY \n" +
+            "    region;\n", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test2() {
+        var sql = """
+            SELECT\s
+                COUNT(*) AS 订单总数,
+                SUM(o.order_total) AS 总销售额,
+                SUM(CASE WHEN o.status = 'completed' THEN 1 ELSE 0 END) AS 完成订单数
+            FROM\s
+                orders o
+            JOIN\s
+                customers c ON o.customer_id = c.customer_id
+            JOIN\s
+                order_items oi ON o.order_id = oi.order_id
+            WHERE\s
+                c.region = 'North America'
+                AND o.order_date BETWEEN '2023-04-01' AND '2023-04-30'
+            GROUP BY\s
+                o.customer_id
+            HAVING\s
+                COUNT(*) > 10;
+            ORDER BY\s
+                total_sales DESC;
+            """;
+        assertEquals("SELECT COUNT(*) AS 订单总数, SUM(o.order_total) AS 总销售额, SUM(CASE WHEN o.status = 'completed' THEN 1 ELSE 0 END) AS 完成订单数 FROM orders_r o JOIN customers_r c ON o.customer_id = c.customer_id JOIN order_items_r oi ON o.order_id = oi.order_id WHERE c.region = 'North America' AND o.order_date BETWEEN '2023-04-01' AND '2023-04-30' GROUP BY o.customer_id HAVING COUNT(*) > 10", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test3() {
+        // 这种jsql解析不了的
+        var sql = """
+            DELIMITER $$
+            DECLARE\s
+                cur CURSOR FOR\s
+                    SELECT employee_id FROM employees WHERE salary < 50000;
+                emp_id INT;
+            BEGIN
+                OPEN cur;
+                WHILE TRUE DO
+                    FETCH cur INTO emp_id;
+                    IF cur_rowcount = 0 THEN
+                        LEAVE;
+                    END IF;
+                   \s
+                    UPDATE employees\s
+                    SET salary = salary * 1.1\s
+                    WHERE employee_id = emp_id;
+                   \s
+                    INSERT INTO audit_log (employee_id, old_salary, new_salary)
+                    VALUES (emp_id, salary_before_update, salary_after_update);
+                END WHILE;
+                CLOSE cur;
+            END
+            $$
+            DELIMITER ;
+            """;
+        assertEquals("DELIMITER $$\n" +
+            "DECLARE \n" +
+            "    cur CURSOR FOR \n" +
+            "        SELECT employee_id FROM employees_r WHERE salary < 50000;\n" +
+            "    emp_id INT;\n" +
+            "BEGIN\n" +
+            "    OPEN cur;\n" +
+            "    WHILE TRUE DO\n" +
+            "        FETCH cur INTO emp_id_r;\n" +
+            "        IF cur_rowcount = 0 THEN\n" +
+            "            LEAVE;\n" +
+            "        END IF;\n" +
+            "        \n" +
+            "        UPDATE employees_r \n" +
+            "        SET salary = salary * 1.1 \n" +
+            "        WHERE employee_id = emp_id;\n" +
+            "        \n" +
+            "        INSERT INTO audit_log_r (employee_id, old_salary, new_salary)\n" +
+            "        VALUES (emp_id, salary_before_update, salary_after_update);\n" +
+            "    END WHILE;\n" +
+            "    CLOSE cur;\n" +
+            "END\n" +
+            "$$\n" +
+            "DELIMITER ;\n", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test4() {
+        var sql = """
+            SELECT *
+            FROM employees e
+            JOIN departments d ON e.department_id = d.department_id
+            WHERE\s
+                e.last_name LIKE CONCAT('%', :lastName, '%')
+                AND (
+                    d.department_name IN (:departmentList)
+                    OR :departmentList IS NULL
+                )
+                AND (
+                    e.hire_date >= :startDate
+                    OR :startDate IS NULL
+                )
+            ORDER BY\s
+                e.employee_id
+            """;
+        assertEquals("SELECT * FROM employees_r e JOIN departments_r d ON e.department_id = d.department_id WHERE e.last_name LIKE CONCAT('%', :lastName, '%') AND (d.department_name IN (:departmentList) OR :departmentList IS NULL) AND (e.hire_date >= :startDate OR :startDate IS NULL) ORDER BY e.employee_id", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test5() {
+        var sql  = """
+            SELECT\s
+                product_id,
+                product_name,
+                stock_quantity,
+                (SELECT\s
+                    SUM(ordered_qty)\s
+                 FROM\s
+                    purchase_orders po\s
+                 WHERE\s
+                    po.product_id = products.product_id\s
+                    AND po.order_date >= CURDATE() - INTERVAL 3 MONTH) AS recent_order_volume
+            FROM\s
+                products
+            WHERE\s
+                stock_quantity < (
+                    SELECT\s
+                        AVG(recommended_stock)\s
+                    FROM\s
+                        product_settings\s
+                    WHERE\s
+                        product_id = products.product_id
+                )
+                AND recent_order_volume > 500
+            """;
+        assertEquals("SELECT product_id, product_name, stock_quantity, (SELECT SUM(ordered_qty) FROM purchase_orders_r po WHERE po.product_id = products.product_id AND po.order_date >= CURDATE() - INTERVAL 3 MONTH) AS recent_order_volume FROM products_r WHERE stock_quantity < (SELECT AVG(recommended_stock) FROM product_settings_r WHERE product_id = products.product_id) AND recent_order_volume > 500", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test6() {
+        var sql = """
+            WITH user_activity AS (
+                SELECT\s
+                    user_id,
+                    event_type,
+                    event_time,
+                    ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) AS activity_seq
+                FROM\s
+                    user_events
+            )
+            SELECT\s
+                user_id,
+                event_type,
+                event_time,
+                LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS prev_event_time
+            FROM\s
+                user_activity
+            WHERE\s
+                activity_seq = 5
+            """;
+        assertEquals("WITH user_activity AS (SELECT user_id, event_type, event_time, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time) AS activity_seq FROM user_events_r) SELECT user_id, event_type, event_time, LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS prev_event_time FROM user_activity_r WHERE activity_seq = 5", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test7() {
+        var sql = "select * from db1.test where a = ?";
+        assertEquals("SELECT * FROM db1.test_r WHERE a = ?", interceptor.changeTable(sql));
+        sql = "select * from db1.`test` where a = ?";
+        assertEquals("SELECT * FROM db1.`test_r` WHERE a = ?", interceptor.changeTable(sql));
+        sql = "select * from db1.`test` where a = ?";
+        assertEquals("SELECT * FROM db1.`test_r` WHERE a = ?", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test8() {
+        // 这种jsql解析不了的
+        var sql = "SELECT * FROM [HR].[dbo].[Employee_Salary_2023];";
+        assertEquals("SELECT * FROM [HR].[dbo].[Employee_Salary_2023_r];", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test9(){
+        // 这种jsql解析不了的
+        var sql = """
+            SELECT * FROM [SalesDB].[dbo].[Orders]
+            JOIN [MarketingDB].[dbo].[Customers]\s
+            ON Orders.CustomerID = Customers.CustomerID;
+            """;
+        assertEquals("SELECT * FROM [SalesDB].[dbo].[Orders_r]\n" +
+            "JOIN [MarketingDB].[dbo].[Customers_r] \n" +
+            "ON Orders.CustomerID = Customers.CustomerID;\n", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test10() {
+        var sql = """
+            SELECT * FROM ecommerce_orders\s
+            PARTITION (p2022, p2023)
+            WHERE order_date BETWEEN '2022-01-01' AND '2023-12-31';
+            """;
+        assertEquals("SELECT * FROM ecommerce_orders_r PARTITION(p2022, p2023) WHERE order_date BETWEEN '2022-01-01' AND '2023-12-31'", interceptor.changeTable(sql));
+    }
+
+    @Test
+    void test11() {
+        var sql = """
+            SELECT order_id, customer_id,amount,
+                  RANK() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rank
+                FROM orders;
+            """;
+        assertEquals("SELECT order_id, customer_id, amount, RANK() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rank FROM orders_r", interceptor.changeTable(sql));
+    }
+
+}

+ 61 - 3
spring-boot-starter/mybatis-plus-spring-boot-autoconfigure/src/main/java/com/baomidou/mybatisplus/autoconfigure/DdlApplicationRunner.java

@@ -17,12 +17,17 @@ package com.baomidou.mybatisplus.autoconfigure;
 
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.baomidou.mybatisplus.extension.ddl.DdlHelper;
+import com.baomidou.mybatisplus.extension.ddl.DdlScriptErrorHandler;
 import com.baomidou.mybatisplus.extension.ddl.IDdl;
+import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.jdbc.ScriptRunner;
+import org.springframework.aop.support.AopUtils;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
 
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * DDL 启动应用后执行
@@ -33,18 +38,71 @@ import java.util.List;
 @Slf4j
 public class DdlApplicationRunner implements ApplicationRunner {
 
+    /**
+     * 处理器列表
+     */
     private final List<IDdl> ddlList;
 
+    /**
+     * 是否自动提交 (默认自动提交)
+     *
+     * @since 3.5.11
+     */
+    @Setter
+    private boolean autoCommit = true;
+
+    /**
+     * 执行脚本错误处理器 (默认打印错误日志继续执行下一个文件)
+     *
+     * @since 3.5.11
+     */
+    @Setter
+    private DdlScriptErrorHandler ddlScriptErrorHandler = DdlScriptErrorHandler.PrintlnLogErrorHandler.INSTANCE;
+
+    /**
+     * 自定义 ScriptRunner 函数
+     *
+     * @since 3.5.11
+     */
+    @Setter
+    private Consumer<ScriptRunner> scriptRunnerConsumer;
+
+    /**
+     * 是否抛出异常
+     * <p>注意这里是控制{@link #ddlList}循环处理时是否抛出异常</p>
+     * <p>当设置为false时,会遍历处理完所有处理器</p>
+     * <p>当设置为true时,在遍历处理时遇到异常会抛出异常中断下一个处理器处理</p>
+     *
+     * @since 3.5.11 保持兼容性,默认不抛出
+     */
+    @Setter
+    private boolean throwException = false;
+
     public DdlApplicationRunner(List<IDdl> ddlList) {
         this.ddlList = ddlList;
     }
 
     @Override
-    public void run(ApplicationArguments args) throws Exception {
+    public void run(ApplicationArguments args) {
         if (CollectionUtils.isNotEmpty(ddlList)) {
             log.debug("  ...  DDL start create  ...  ");
-            ddlList.forEach(ddl -> ddl.runScript(dataSource -> DdlHelper.runScript(ddl.getDdlGenerator(),
-                dataSource, ddl.getSqlFiles(), true)));
+            ddlList.forEach(ddl -> ddl.runScript(dataSource -> {
+                String ddlClassName = AopUtils.getTargetClass(ddl).getName();
+                if (CollectionUtils.isEmpty(ddl.getSqlFiles())) {
+                    log.warn("{}, sql files is empty", ddlClassName);
+                    return;
+                }
+                log.info("{}, run sql files {}", ddlClassName, ddl.getSqlFiles());
+                try {
+                    DdlHelper.runScript(ddl.getDdlGenerator(),
+                        dataSource, ddl.getSqlFiles(), this.scriptRunnerConsumer, this.autoCommit, this.ddlScriptErrorHandler);
+                } catch (Exception e) {
+                    log.error("{}, run sql file error: ", ddlClassName, e);
+                    if (throwException) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }));
             log.debug("  ...  DDL end create  ...  ");
         }
     }