From a0fdc42ce5e3160a70ff9f5cdfaab2448105eb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E7=BF=8A=20SionYang?= Date: Thu, 17 Oct 2024 16:08:42 +0800 Subject: [PATCH 1/6] Enhance Disk operation for DiskUtils (#12756) * Add some check in DiskUtil and add unit test. * simply DiskUtils in control plugin. --- .../nacos/plugin/control/utils/DiskUtils.java | 442 ------------------ .../plugin/control/utils/DiskUtilsTest.java | 193 +------- .../alibaba/nacos/sys/utils/DiskUtils.java | 70 ++- .../nacos/sys/utils/DiskUtilsTest.java | 104 +++++ .../nacos/sys/utils/DiskUtilsZipTest.java | 1 + 5 files changed, 174 insertions(+), 636 deletions(-) diff --git a/plugin/control/src/main/java/com/alibaba/nacos/plugin/control/utils/DiskUtils.java b/plugin/control/src/main/java/com/alibaba/nacos/plugin/control/utils/DiskUtils.java index 5156bdcf006..9171400aa59 100644 --- a/plugin/control/src/main/java/com/alibaba/nacos/plugin/control/utils/DiskUtils.java +++ b/plugin/control/src/main/java/com/alibaba/nacos/plugin/control/utils/DiskUtils.java @@ -16,40 +16,21 @@ package com.alibaba.nacos.plugin.control.utils; -import com.alibaba.nacos.common.utils.ByteUtils; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.io.output.NullOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Objects; -import java.util.function.Consumer; -import java.util.zip.CheckedInputStream; -import java.util.zip.CheckedOutputStream; -import java.util.zip.Checksum; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; /** * IO operates on the utility class. @@ -72,107 +53,6 @@ public final class DiskUtils { private static final CharsetDecoder DECODER = CHARSET.newDecoder(); - public static void touch(String path, String fileName) throws IOException { - FileUtils.touch(Paths.get(path, fileName).toFile()); - } - - /** - * Implements the same behaviour as the "touch" utility on Unix. It creates a new file with size 0 or, if the file - * exists already, it is opened and closed without modifying it, but updating the file date and time. - * - *

NOTE: As from v1.3, this method throws an IOException if the last - * modified date of the file cannot be set. Also, as from v1.3 this method creates parent directories if they do not - * exist. - * - * @param file the File to touch - * @throws IOException If an I/O problem occurs - */ - public static void touch(File file) throws IOException { - FileUtils.touch(file); - } - - /** - * Creates a new empty file in the specified directory, using the given prefix and suffix strings to generate its - * name. The resulting {@code Path} is associated with the same {@code FileSystem} as the given directory. - * - *

The details as to how the name of the file is constructed is - * implementation dependent and therefore not specified. Where possible the {@code prefix} and {@code suffix} are - * used to construct candidate names in the same manner as the {@link File#createTempFile(String, String, - * File)} method. - * - * @param dir the path to directory in which to create the file - * @param prefix the prefix string to be used in generating the file's name; may be {@code null} - * @param suffix the suffix string to be used in generating the file's name; may be {@code null}, in which case - * "{@code .tmp}" is used - * @return the path to the newly created file that did not exist before this method was invoked - * @throws IllegalArgumentException if the prefix or suffix parameters cannot be used to generate a candidate - * file name - * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically when - * creating the directory - * @throws IOException if an I/O error occurs or {@code dir} does not exist - * @throws SecurityException In the case of the default provider, and a security manager is installed, - * the {@link SecurityManager#checkWrite(String) checkWrite} method is invoked - * to check write access to the file. - */ - public static File createTmpFile(String dir, String prefix, String suffix) throws IOException { - return Files.createTempFile(Paths.get(dir), prefix, suffix).toFile(); - } - - /** - * Creates an empty file in the default temporary-file directory, using the given prefix and suffix to generate its - * name. The resulting {@code Path} is associated with the default {@code FileSystem}. - * - * @param prefix the prefix string to be used in generating the file's name; may be {@code null} - * @param suffix the suffix string to be used in generating the file's name; may be {@code null}, in which case - * "{@code .tmp}" is used - * @return the path to the newly created file that did not exist before this method was invoked - * @throws IllegalArgumentException if the prefix or suffix parameters cannot be used to generate a candidate - * file name - * @throws UnsupportedOperationException if the array contains an attribute that cannot be set atomically when - * creating the directory - * @throws IOException if an I/O error occurs or the temporary-file directory does not exist - * @throws SecurityException In the case of the default provider, and a security manager is installed, - * the {@link SecurityManager#checkWrite(String) checkWrite} method is invoked - * to check write access to the file. - */ - public static File createTmpFile(String prefix, String suffix) throws IOException { - return Files.createTempFile(prefix, suffix).toFile(); - } - - /** - * read file which under the path. - * - * @param path directory - * @param fileName filename - * @return content - */ - public static String readFile(String path, String fileName) { - File file = openFile(path, fileName); - if (file.exists()) { - return readFile(file); - } - return null; - } - - /** - * read file content by {@link InputStream}. - * - * @param is {@link InputStream} - * @return content - */ - public static String readFile(InputStream is) { - try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { - StringBuilder textBuilder = new StringBuilder(); - String lineTxt = null; - while ((lineTxt = reader.readLine()) != null) { - textBuilder.append(lineTxt); - } - return textBuilder.toString(); - } catch (IOException e) { - return null; - } - } - /** * read this file content. * @@ -200,27 +80,6 @@ public static String readFile(File file) { } } - /** - * read this file content then return bytes. - * - * @param file {@link File} - * @return content bytes - */ - public static byte[] readFileBytes(File file) { - if (file.exists()) { - String result = readFile(file); - if (result != null) { - return ByteUtils.toBytes(result); - } - } - return null; - } - - public static byte[] readFileBytes(String path, String fileName) { - File file = openFile(path, fileName); - return readFileBytes(file); - } - /** * Writes the contents to the target file. * @@ -252,305 +111,4 @@ public static void deleteQuietly(File file) { FileUtils.deleteQuietly(file); } - public static void deleteQuietly(Path path) { - Objects.requireNonNull(path, "path"); - FileUtils.deleteQuietly(path.toFile()); - } - - /** - * delete target file. - * - * @param path directory - * @param fileName filename - * @return delete success - */ - public static boolean deleteFile(String path, String fileName) { - File file = Paths.get(path, fileName).toFile(); - if (file.exists()) { - return file.delete(); - } - return false; - } - - public static void deleteDirectory(String path) throws IOException { - FileUtils.deleteDirectory(new File(path)); - } - - public static void forceMkdir(String path) throws IOException { - FileUtils.forceMkdir(new File(path)); - } - - public static void forceMkdir(File file) throws IOException { - FileUtils.forceMkdir(file); - } - - public static void deleteDirThenMkdir(String path) throws IOException { - deleteDirectory(path); - forceMkdir(path); - } - - public static void copyDirectory(File srcDir, File destDir) throws IOException { - FileUtils.copyDirectory(srcDir, destDir); - } - - public static void copyFile(File src, File target) throws IOException { - FileUtils.copyFile(src, target); - } - - public static File openFile(String path, String fileName) { - return openFile(path, fileName, false); - } - - /** - * open file. - * - * @param path directory - * @param fileName filename - * @param rewrite if rewrite is true, will delete old file and create new one - * @return {@link File} - */ - public static File openFile(String path, String fileName, boolean rewrite) { - File directory = new File(path); - boolean mkdirs = true; - if (!directory.exists()) { - mkdirs = directory.mkdirs(); - } - if (!mkdirs) { - LOGGER.error("[DiskUtils] can't create directory"); - return null; - } - File file = new File(path, fileName); - try { - boolean create = true; - if (!file.exists()) { - file.createNewFile(); - } - if (file.exists()) { - if (rewrite) { - file.delete(); - } else { - create = false; - } - } - if (create) { - file.createNewFile(); - } - } catch (IOException e) { - throw new RuntimeException(e); - } - return file; - } - - // copy from sofa-jraft - - /** - * Compress a folder in a directory. - * - * @param rootDir directory - * @param sourceDir folder - * @param outputFile output file - * @param checksum checksum - * @throws IOException IOException - */ - public static void compress(final String rootDir, final String sourceDir, final String outputFile, - final Checksum checksum) throws IOException { - try (final FileOutputStream fos = new FileOutputStream(outputFile); - final CheckedOutputStream cos = new CheckedOutputStream(fos, checksum); - final ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(cos))) { - compressDirectoryToZipFile(rootDir, sourceDir, zos); - zos.flush(); - fos.getFD().sync(); - } - } - - // copy from sofa-jraft - - private static void compressDirectoryToZipFile(final String rootDir, final String sourceDir, - final ZipOutputStream zos) throws IOException { - final String dir = Paths.get(rootDir, sourceDir).toString(); - final File[] files = Objects.requireNonNull(new File(dir).listFiles(), "files"); - for (final File file : files) { - final String child = Paths.get(sourceDir, file.getName()).toString(); - if (file.isDirectory()) { - compressDirectoryToZipFile(rootDir, child, zos); - } else { - try (final FileInputStream fis = new FileInputStream(file); - final BufferedInputStream bis = new BufferedInputStream(fis)) { - compressIntoZipFile(child, bis, zos); - } - } - } - } - - /** - * Compress an input stream to zip file. - * - * @param childName child name in zip file - * @param inputStream input stream needed compress - * @param outputFile output file - * @param checksum check sum - * @throws IOException IOException during compress - */ - public static void compressIntoZipFile(final String childName, final InputStream inputStream, - final String outputFile, final Checksum checksum) throws IOException { - try (final FileOutputStream fileOutputStream = new FileOutputStream(outputFile); - final CheckedOutputStream checkedOutputStream = new CheckedOutputStream(fileOutputStream, checksum); - final ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(checkedOutputStream))) { - compressIntoZipFile(childName, inputStream, zipStream); - zipStream.flush(); - fileOutputStream.getFD().sync(); - } - } - - private static void compressIntoZipFile(final String childName, final InputStream inputStream, - final ZipOutputStream zipOutputStream) throws IOException { - zipOutputStream.putNextEntry(new ZipEntry(childName)); - IOUtils.copy(inputStream, zipOutputStream); - } - - // copy from sofa-jraft - - /** - * Unzip the target file to the specified folder. - * - * @param sourceFile target file - * @param outputDir specified folder - * @param checksum checksum - * @throws IOException IOException - */ - public static void decompress(final String sourceFile, final String outputDir, final Checksum checksum) - throws IOException { - try (final FileInputStream fis = new FileInputStream(sourceFile); - final CheckedInputStream cis = new CheckedInputStream(fis, checksum); - final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(cis))) { - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - final String fileName = entry.getName(); - final File entryFile = new File(Paths.get(outputDir, fileName).toString()); - FileUtils.forceMkdir(entryFile.getParentFile()); - try (final FileOutputStream fos = new FileOutputStream(entryFile); - final BufferedOutputStream bos = new BufferedOutputStream(fos)) { - IOUtils.copy(zis, bos); - bos.flush(); - fos.getFD().sync(); - } - } - // Continue to read all remaining bytes(extra metadata of ZipEntry) directly from the checked stream, - // Otherwise, the checksum value maybe unexpected. - // - // See https://coderanch.com/t/279175/java/ZipInputStream - IOUtils.copy(cis, NullOutputStream.NULL_OUTPUT_STREAM); - } - } - - /** - * Unzip the target file to byte array. - * - * @param sourceFile target file - * @param checksum checksum - * @return decompress byte array - * @throws IOException IOException during decompress - */ - public static byte[] decompress(final String sourceFile, final Checksum checksum) throws IOException { - byte[] result; - try (final FileInputStream fis = new FileInputStream(sourceFile); - final CheckedInputStream cis = new CheckedInputStream(fis, checksum); - final ZipInputStream zis = new ZipInputStream(new BufferedInputStream(cis)); - final ByteArrayOutputStream bos = new ByteArrayOutputStream(1024)) { - while (zis.getNextEntry() != null) { - IOUtils.copy(zis, bos); - bos.flush(); - } - IOUtils.copy(cis, NullOutputStream.NULL_OUTPUT_STREAM); - result = bos.toByteArray(); - } - return result; - } - - /** - * Returns an Iterator for the lines in a File. - *

- * This method opens an InputStream for the file. When you have finished with the iterator you should - * close the stream to free internal resources. This can be done by calling the {@link - * org.apache.commons.io.LineIterator#close()} or {@link org.apache.commons.io.LineIterator#closeQuietly(org.apache.commons.io.LineIterator)} - * method. - *

- * The recommended usage pattern is: - *
-     * LineIterator it = FileUtils.lineIterator(file, "UTF-8");
-     * try {
-     *   while (it.hasNext()) {
-     *     String line = it.nextLine();
-     *     /// do something with line
-     *   }
-     * } finally {
-     *   LineIterator.closeQuietly(iterator);
-     * }
-     * 
- *

- * If an exception occurs during the creation of the iterator, the underlying stream is closed. - *

- * - * @param file the file to open for input, must not be null - * @param encoding the encoding to use, null means platform default - * @return an Iterator of the lines in the file, never null - * @throws IOException in case of an I/O error (file closed) - * @since 1.2 - */ - public static LineIterator lineIterator(File file, String encoding) throws IOException { - return new LineIterator(FileUtils.lineIterator(file, encoding)); - } - - /** - * Returns an Iterator for the lines in a File using the default encoding for the VM. - * - * @param file the file to open for input, must not be null - * @return an Iterator of the lines in the file, never null - * @throws IOException in case of an I/O error (file closed) - * @see #lineIterator(File, String) - * @since 1.3 - */ - public static LineIterator lineIterator(File file) throws IOException { - return new LineIterator(FileUtils.lineIterator(file, null)); - } - - public static class LineIterator implements AutoCloseable { - - private final org.apache.commons.io.LineIterator target; - - /** - * Constructs an iterator of the lines for a Reader. - * - * @param target {@link org.apache.commons.io.LineIterator} - */ - LineIterator(org.apache.commons.io.LineIterator target) { - this.target = target; - } - - public boolean hasNext() { - return target.hasNext(); - } - - public String next() { - return target.next(); - } - - public String nextLine() { - return target.nextLine(); - } - - @Override - public void close() throws IOException { - target.close(); - } - - public void remove() { - target.remove(); - } - - public void forEachRemaining(Consumer action) { - target.forEachRemaining(action); - } - } - } diff --git a/plugin/control/src/test/java/com/alibaba/nacos/plugin/control/utils/DiskUtilsTest.java b/plugin/control/src/test/java/com/alibaba/nacos/plugin/control/utils/DiskUtilsTest.java index bb61475bcc7..25971e67c53 100644 --- a/plugin/control/src/test/java/com/alibaba/nacos/plugin/control/utils/DiskUtilsTest.java +++ b/plugin/control/src/test/java/com/alibaba/nacos/plugin/control/utils/DiskUtilsTest.java @@ -21,12 +21,9 @@ import org.junit.jupiter.api.Test; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.nio.file.Files; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -36,13 +33,15 @@ class DiskUtilsTest { - private static final String TMP_PATH = EnvUtils.getNacosHome() + File.separator + "data" + File.separator + "tmp" + File.separator; + private static final String TMP_PATH = + EnvUtils.getNacosHome() + File.separator + "data" + File.separator + "tmp" + File.separator; private static File testFile; @BeforeAll static void setup() throws IOException { - testFile = DiskUtils.createTmpFile("nacostmp", ".ut"); + testFile = Files.createTempFile("nacostmp", ".ut").toFile(); + ; } @AfterAll @@ -50,197 +49,21 @@ static void tearDown() throws IOException { testFile.deleteOnExit(); } - @Test - void testTouch() throws IOException { - File file = Paths.get(TMP_PATH, "touch.ut").toFile(); - assertFalse(file.exists()); - DiskUtils.touch(file); - assertTrue(file.exists()); - file.deleteOnExit(); - } - - @Test - void testTouchWithFileName() throws IOException { - File file = Paths.get(TMP_PATH, UUID.randomUUID().toString()).toFile(); - assertFalse(file.exists()); - DiskUtils.touch(file.getParent(), file.getName()); - assertTrue(file.exists()); - file.deleteOnExit(); - } - - @Test - void testCreateTmpFile() throws IOException { - File tmpFile = null; - try { - tmpFile = DiskUtils.createTmpFile("nacos1", ".ut"); - assertTrue(tmpFile.getName().startsWith("nacos1")); - assertTrue(tmpFile.getName().endsWith(".ut")); - } finally { - if (tmpFile != null) { - tmpFile.deleteOnExit(); - } - } - } - - @Test - void testCreateTmpFileWithPath() throws IOException { - File tmpFile = null; - try { - tmpFile = DiskUtils.createTmpFile(TMP_PATH, "nacos1", ".ut"); - assertEquals(TMP_PATH, tmpFile.getParent() + File.separator); - assertTrue(tmpFile.getName().startsWith("nacos1")); - assertTrue(tmpFile.getName().endsWith(".ut")); - } finally { - if (tmpFile != null) { - tmpFile.deleteOnExit(); - } - } - } - @Test void testReadFile() { assertNotNull(DiskUtils.readFile(testFile)); } @Test - void testReadFileWithInputStream() throws FileNotFoundException { - assertNotNull(DiskUtils.readFile(new FileInputStream(testFile))); - } - - @Test - void testReadFileWithPath() { - assertNotNull(DiskUtils.readFile(testFile.getParent(), testFile.getName())); - } - - @Test - void testReadFileBytes() { - assertNotNull(DiskUtils.readFileBytes(testFile)); - } - - @Test - void testReadFileBytesWithPath() { - assertNotNull(DiskUtils.readFileBytes(testFile.getParent(), testFile.getName())); - } - - @Test - void writeFile() { + void testWriteFile() { assertTrue(DiskUtils.writeFile(testFile, "unit test".getBytes(StandardCharsets.UTF_8), false)); assertEquals("unit test", DiskUtils.readFile(testFile)); } @Test - void deleteQuietly() throws IOException { - File tmpFile = DiskUtils.createTmpFile(UUID.randomUUID().toString(), ".ut"); + void testDeleteQuietly() throws IOException { + File tmpFile = Files.createTempFile(UUID.randomUUID().toString(), ".ut").toFile(); DiskUtils.deleteQuietly(tmpFile); assertFalse(tmpFile.exists()); } - - @Test - void testDeleteQuietlyWithPath() throws IOException { - String dir = TMP_PATH + "/" + "diskutils"; - DiskUtils.forceMkdir(dir); - DiskUtils.createTmpFile(dir, "nacos", ".ut"); - Path path = Paths.get(dir); - DiskUtils.deleteQuietly(path); - - assertFalse(path.toFile().exists()); - } - - @Test - void testDeleteFile() throws IOException { - File tmpFile = DiskUtils.createTmpFile(UUID.randomUUID().toString(), ".ut"); - assertTrue(DiskUtils.deleteFile(tmpFile.getParent(), tmpFile.getName())); - assertFalse(DiskUtils.deleteFile(tmpFile.getParent(), tmpFile.getName())); - } - - @Test - void deleteDirectory() throws IOException { - Path diskutils = Paths.get(TMP_PATH, "diskutils"); - File file = diskutils.toFile(); - if (!file.exists()) { - file.mkdir(); - } - - assertTrue(file.exists()); - DiskUtils.deleteDirectory(diskutils.toString()); - assertFalse(file.exists()); - } - - @Test - void testForceMkdir() throws IOException { - File dir = Paths.get(TMP_PATH, UUID.randomUUID().toString(), UUID.randomUUID().toString()).toFile(); - DiskUtils.forceMkdir(dir); - assertTrue(dir.exists()); - dir.deleteOnExit(); - } - - @Test - void testForceMkdirWithPath() throws IOException { - Path path = Paths.get(TMP_PATH, UUID.randomUUID().toString(), UUID.randomUUID().toString()); - DiskUtils.forceMkdir(path.toString()); - File file = path.toFile(); - assertTrue(file.exists()); - file.deleteOnExit(); - } - - @Test - void deleteDirThenMkdir() throws IOException { - Path path = Paths.get(TMP_PATH, UUID.randomUUID().toString()); - DiskUtils.forceMkdir(path.toString()); - - DiskUtils.createTmpFile(path.toString(), UUID.randomUUID().toString(), ".ut"); - DiskUtils.createTmpFile(path.toString(), UUID.randomUUID().toString(), ".ut"); - - DiskUtils.deleteDirThenMkdir(path.toString()); - - File file = path.toFile(); - assertTrue(file.exists()); - assertTrue(file.isDirectory()); - assertTrue(file.list() == null || file.list().length == 0); - - file.deleteOnExit(); - } - - @Test - void testCopyDirectory() throws IOException { - Path srcPath = Paths.get(TMP_PATH, UUID.randomUUID().toString()); - DiskUtils.forceMkdir(srcPath.toString()); - File nacos = DiskUtils.createTmpFile(srcPath.toString(), "nacos", ".ut"); - - Path destPath = Paths.get(TMP_PATH, UUID.randomUUID().toString()); - DiskUtils.copyDirectory(srcPath.toFile(), destPath.toFile()); - - File file = Paths.get(destPath.toString(), nacos.getName()).toFile(); - assertTrue(file.exists()); - - DiskUtils.deleteDirectory(srcPath.toString()); - DiskUtils.deleteDirectory(destPath.toString()); - } - - @Test - void testCopyFile() throws IOException { - File nacos = DiskUtils.createTmpFile("nacos", ".ut"); - DiskUtils.copyFile(testFile, nacos); - - assertEquals(DiskUtils.readFile(testFile), DiskUtils.readFile(nacos)); - - nacos.deleteOnExit(); - } - - @Test - void openFile() { - File file = DiskUtils.openFile(testFile.getParent(), testFile.getName()); - assertNotNull(file); - assertEquals(testFile.getPath(), file.getPath()); - assertEquals(testFile.getName(), file.getName()); - } - - @Test - void testOpenFileWithPath() { - File file = DiskUtils.openFile(testFile.getParent(), testFile.getName(), false); - assertNotNull(file); - assertEquals(testFile.getPath(), file.getPath()); - assertEquals(testFile.getName(), file.getName()); - } - } diff --git a/sys/src/main/java/com/alibaba/nacos/sys/utils/DiskUtils.java b/sys/src/main/java/com/alibaba/nacos/sys/utils/DiskUtils.java index 3b04ae9af86..94d40233630 100644 --- a/sys/src/main/java/com/alibaba/nacos/sys/utils/DiskUtils.java +++ b/sys/src/main/java/com/alibaba/nacos/sys/utils/DiskUtils.java @@ -51,6 +51,10 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import static com.alibaba.nacos.common.utils.StringUtils.FOLDER_SEPARATOR; +import static com.alibaba.nacos.common.utils.StringUtils.TOP_PATH; +import static com.alibaba.nacos.common.utils.StringUtils.WINDOWS_FOLDER_SEPARATOR; + /** * IO operates on the utility class. * @@ -72,8 +76,18 @@ public final class DiskUtils { private static final CharsetDecoder DECODER = CHARSET.newDecoder(); + /** + * Touch file. + * + * @param path path of fileName + * @param fileName fileName + * @throws IOException during touch + */ public static void touch(String path, String fileName) throws IOException { - FileUtils.touch(Paths.get(path, fileName).toFile()); + if (isIllegalPath(path) || isIllegalFileName(fileName)) { + return; + } + touch(Paths.get(path, fileName).toFile()); } /** @@ -97,8 +111,8 @@ public static void touch(File file) throws IOException { * *

The details as to how the name of the file is constructed is * implementation dependent and therefore not specified. Where possible the {@code prefix} and {@code suffix} are - * used to construct candidate names in the same manner as the {@link java.io.File#createTempFile(String, String, - * File)} method. + * used to construct candidate names in the same manner as the + * {@link java.io.File#createTempFile(String, String, File)} method. * * @param dir the path to directory in which to create the file * @param prefix the prefix string to be used in generating the file's name; may be {@code null} @@ -148,7 +162,7 @@ public static File createTmpFile(String prefix, String suffix) throws IOExceptio */ public static String readFile(String path, String fileName) { File file = openFile(path, fileName); - if (file.exists()) { + if (null != file && file.exists()) { return readFile(file); } return null; @@ -217,7 +231,17 @@ public static byte[] readFileBytes(File file) { return null; } + /** + * read this file content then return bytes. + * + * @param path path of file + * @param fileName file name + * @return content bytes + */ public static byte[] readFileBytes(String path, String fileName) { + if (isIllegalPath(path) || isIllegalFileName(fileName)) { + return null; + } File file = openFile(path, fileName); return readFileBytes(file); } @@ -239,8 +263,8 @@ public static boolean writeFile(File file, byte[] content, boolean append) { } catch (IOException ioe) { if (ioe.getMessage() != null) { String errMsg = ioe.getMessage(); - if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUOTA_CN) || errMsg - .contains(DISK_QUOTA_EN)) { + if (NO_SPACE_CN.equals(errMsg) || NO_SPACE_EN.equals(errMsg) || errMsg.contains(DISK_QUOTA_CN) + || errMsg.contains(DISK_QUOTA_EN)) { LOGGER.warn("磁盘满,自杀退出"); System.exit(0); } @@ -267,6 +291,9 @@ public static void deleteQuietly(Path path) { * @return delete success */ public static boolean deleteFile(String path, String fileName) { + if (isIllegalPath(path) || isIllegalFileName(fileName)) { + return false; + } File file = Paths.get(path, fileName).toFile(); if (file.exists()) { return file.delete(); @@ -312,6 +339,9 @@ public static File openFile(String path, String fileName) { * @return {@link File} */ public static File openFile(String path, String fileName, boolean rewrite) { + if (isIllegalPath(path) || isIllegalFileName(fileName)) { + return null; + } File directory = new File(path); boolean mkdirs = true; if (!directory.exists()) { @@ -395,6 +425,9 @@ private static void compressDirectoryToZipFile(final String rootDir, final Strin */ public static void compressIntoZipFile(final String childName, final InputStream inputStream, final String outputFile, final Checksum checksum) throws IOException { + if (isIllegalFileName(childName)) { + return; + } try (final FileOutputStream fileOutputStream = new FileOutputStream(outputFile); final CheckedOutputStream checkedOutputStream = new CheckedOutputStream(fileOutputStream, checksum); final ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(checkedOutputStream))) { @@ -428,6 +461,9 @@ public static void decompress(final String sourceFile, final String outputDir, f ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { final String fileName = entry.getName(); + if (isIllegalFileName(fileName)) { + continue; + } final File entryFile = new File(Paths.get(outputDir, fileName).toString()); FileUtils.forceMkdir(entryFile.getParentFile()); try (final FileOutputStream fos = new FileOutputStream(entryFile); @@ -469,13 +505,29 @@ public static byte[] decompress(final String sourceFile, final Checksum checksum return result; } + /** + * Whether is illegal file name, it should not be start with root path '/' or '\\' and should not contain top path + * ... + * + * @param fileName File name + * @return {@code true} when file name contain .. or start with root path. + */ + public static boolean isIllegalFileName(String fileName) { + return fileName.contains(TOP_PATH) || fileName.startsWith(FOLDER_SEPARATOR) || fileName.startsWith( + WINDOWS_FOLDER_SEPARATOR); + } + + public static boolean isIllegalPath(String path) { + return path.contains(TOP_PATH); + } + /** * Returns an Iterator for the lines in a File. *

* This method opens an InputStream for the file. When you have finished with the iterator you should - * close the stream to free internal resources. This can be done by calling the {@link - * org.apache.commons.io.LineIterator#close()} or {@link org.apache.commons.io.LineIterator#closeQuietly(org.apache.commons.io.LineIterator)} - * method. + * close the stream to free internal resources. This can be done by calling the + * {@link org.apache.commons.io.LineIterator#close()} or + * {@link org.apache.commons.io.LineIterator#closeQuietly(org.apache.commons.io.LineIterator)} method. *

* The recommended usage pattern is: *
diff --git a/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsTest.java b/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsTest.java
index 19e29326cc8..d5cb5c19c08 100644
--- a/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsTest.java
+++ b/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsTest.java
@@ -25,6 +25,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.URISyntaxException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
@@ -35,8 +36,10 @@
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
 
 class DiskUtilsTest {
     
@@ -77,6 +80,38 @@ void testTouchWithFileName() throws IOException {
         file.deleteOnExit();
     }
     
+    @Test
+    void testTouchWithIllegalPath() throws IOException {
+        File tmpDir = new File(EnvUtil.getNacosTmpDir());
+        String fileName = UUID.randomUUID().toString();
+        File expectedFile = Paths.get(tmpDir.getParent(), fileName).toFile();
+        assertFalse(expectedFile.exists());
+        DiskUtils.touch(tmpDir.getAbsolutePath() + "/..", fileName);
+        assertFalse(expectedFile.exists());
+        expectedFile.deleteOnExit();
+    }
+    
+    @Test
+    void testTouchWithIllegalFileName() throws IOException {
+        File tmpDir = new File(EnvUtil.getNacosTmpDir());
+        String fileName = UUID.randomUUID().toString();
+        File expectedFile = Paths.get(tmpDir.getParent(), fileName).toFile();
+        assertFalse(expectedFile.exists());
+        DiskUtils.touch(tmpDir.getAbsolutePath(), "../" + fileName);
+        assertFalse(expectedFile.exists());
+        expectedFile.deleteOnExit();
+    }
+    
+    @Test
+    void testTouchWithIllegalFileName2() throws IOException {
+        String fileName = UUID.randomUUID().toString();
+        File expectedFile = Paths.get("/", fileName).toFile();
+        assertFalse(expectedFile.exists());
+        DiskUtils.touch("", "/" + fileName);
+        assertFalse(expectedFile.exists());
+        expectedFile.deleteOnExit();
+    }
+    
     @Test
     void testCreateTmpFile() throws IOException {
         File tmpFile = null;
@@ -111,11 +146,43 @@ void testReadFile() {
         assertNotNull(DiskUtils.readFile(testFile));
     }
     
+    @Test
+    void testReadNonExistFile() {
+        File file = new File("non-exist");
+        assertNull(DiskUtils.readFile(file));
+    }
+    
+    @Test
+    void testReadNonExistFile2() {
+        File file = new File("non-path/non-exist");
+        file.deleteOnExit();
+        assertEquals("", DiskUtils.readFile(file.getParentFile().getAbsolutePath(), file.getName()));
+    }
+    
+    @Test
+    void testReadFileWithIllegalPath() {
+        String path = testFile.getParentFile().getAbsolutePath() + "/../" + testFile.getParentFile().getName();
+        assertNull(DiskUtils.readFile(path, testFile.getName()));
+    }
+    
+    @Test
+    void testReadFileWithIllegalFileName() {
+        String path = testFile.getParentFile().getAbsolutePath();
+        String fileName = "../" + testFile.getParentFile().getName() + "/" + testFile.getName();
+        assertNull(DiskUtils.readFile(path, fileName));
+    }
+    
     @Test
     void testReadFileWithInputStream() throws FileNotFoundException {
         assertNotNull(DiskUtils.readFile(new FileInputStream(testFile)));
     }
     
+    @Test
+    void testReadFileWithInputStreamWithException() {
+        InputStream inputStream = mock(InputStream.class);
+        assertNull(DiskUtils.readFile(inputStream));
+    }
+    
     @Test
     void testReadFileWithPath() {
         assertNotNull(DiskUtils.readFile(testFile.getParent(), testFile.getName()));
@@ -126,17 +193,41 @@ void testReadFileBytes() {
         assertNotNull(DiskUtils.readFileBytes(testFile));
     }
     
+    @Test
+    void testReadFileBytesNonExist() {
+        assertNull(DiskUtils.readFileBytes(new File("non-exist")));
+    }
+    
     @Test
     void testReadFileBytesWithPath() {
         assertNotNull(DiskUtils.readFileBytes(testFile.getParent(), testFile.getName()));
     }
     
+    @Test
+    void testReadFileBytesWithIllegalPath() {
+        String path = testFile.getParentFile().getAbsolutePath() + "/../" + testFile.getParentFile().getName();
+        assertNull(DiskUtils.readFileBytes(path, testFile.getName()));
+    }
+    
+    @Test
+    void testReadFileBytesWithIllegalFileName() {
+        String path = testFile.getParentFile().getAbsolutePath();
+        String fileName = "/../" + testFile.getParentFile().getName() + "/" + testFile.getName();
+        assertNull(DiskUtils.readFileBytes(path, fileName));
+    }
+    
     @Test
     void writeFile() {
         assertTrue(DiskUtils.writeFile(testFile, "unit test".getBytes(StandardCharsets.UTF_8), false));
         assertEquals("unit test", DiskUtils.readFile(testFile));
     }
     
+    @Test
+    void writeFileWithNonExist() {
+        File file = new File("\u0000non-exist");
+        assertFalse(DiskUtils.writeFile(file, "unit test".getBytes(StandardCharsets.UTF_8), false));
+    }
+    
     @Test
     void deleteQuietly() throws IOException {
         File tmpFile = DiskUtils.createTmpFile(UUID.randomUUID().toString(), ".ut");
@@ -162,6 +253,19 @@ void testDeleteFile() throws IOException {
         assertFalse(DiskUtils.deleteFile(tmpFile.getParent(), tmpFile.getName()));
     }
     
+    @Test
+    void testDeleteFileIllegalPath() {
+        String path = testFile.getParentFile().getAbsolutePath() + "/../" + testFile.getParentFile().getName();
+        assertFalse(DiskUtils.deleteFile(path, testFile.getName()));
+    }
+    
+    @Test
+    void testDeleteFileIllegalFileName() {
+        String path = testFile.getParentFile().getAbsolutePath();
+        String fileName = "../" + testFile.getParentFile().getName() + "/" + testFile.getName();
+        assertFalse(DiskUtils.deleteFile(path, fileName));
+    }
+    
     @Test
     void deleteDirectory() throws IOException {
         Path diskutils = Paths.get(EnvUtil.getNacosTmpDir(), "diskutils");
diff --git a/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsZipTest.java b/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsZipTest.java
index a1a37d6a844..1080a201b65 100644
--- a/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsZipTest.java
+++ b/sys/src/test/java/com/alibaba/nacos/sys/utils/DiskUtilsZipTest.java
@@ -91,6 +91,7 @@ public void testCompressAndDecompressWithStream() throws IOException {
         Path path = Paths.get(rootPath, "test.zip");
         try (InputStream inputStream = new ByteArrayInputStream("test".getBytes())) {
             DiskUtils.compressIntoZipFile("test", inputStream, path.toString(), new CRC32());
+            DiskUtils.compressIntoZipFile("../test", inputStream, path.toString(), new CRC32());
             byte[] actual = DiskUtils.decompress(path.toString(), new CRC32());
             assertEquals("test", new String(actual));
         } finally {

From 2b178bec38a24664269ce92db4684abfee6eeb3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=A8=E7=BF=8A=20SionYang?= 
Date: Thu, 17 Oct 2024 16:32:59 +0800
Subject: [PATCH 2/6] Refactor update password api auth check and add unit
 test. (#12757)

---
 .../auth/AbstractProtocolAuthService.java     |  7 ++-
 .../auth/GrpcProtocolAuthServiceTest.java     | 28 +++++++++-
 .../auth/HttpProtocolAuthServiceTest.java     | 55 ++++++++++++++-----
 .../nacos/auth/mock/MockResourceParser.java   | 30 ++++++++++
 .../grpc/ConfigGrpcResourceParserTest.java    | 18 ++++++
 .../auth/impl/NacosAuthPluginService.java     |  1 -
 .../auth/impl/controller/UserController.java  |  3 +-
 .../auth/impl/roles/NacosRoleServiceImpl.java | 12 +++-
 .../impl/roles/NacosRoleServiceImplTest.java  | 19 +++++--
 9 files changed, 147 insertions(+), 26 deletions(-)
 create mode 100644 auth/src/test/java/com/alibaba/nacos/auth/mock/MockResourceParser.java

diff --git a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java
index 27f472dc7df..150738db186 100644
--- a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java
+++ b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java
@@ -29,6 +29,7 @@
 import com.alibaba.nacos.plugin.auth.spi.server.AuthPluginService;
 
 import java.util.Optional;
+import java.util.Properties;
 
 /**
  * Abstract protocol auth service.
@@ -84,7 +85,11 @@ public boolean validateAuthority(IdentityContext identityContext, Permission per
      * @return resource
      */
     protected Resource parseSpecifiedResource(Secured secured) {
-        return new Resource(null, null, secured.resource(), SignType.SPECIFIED, null);
+        Properties properties = new Properties();
+        for (String each : secured.tags()) {
+            properties.put(each, each);
+        }
+        return new Resource(null, null, secured.resource(), SignType.SPECIFIED, properties);
     }
     
     /**
diff --git a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java
index 490fb6132c7..b9a937d1833 100644
--- a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java
+++ b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java
@@ -21,6 +21,7 @@
 import com.alibaba.nacos.auth.annotation.Secured;
 import com.alibaba.nacos.auth.config.AuthConfigs;
 import com.alibaba.nacos.auth.mock.MockAuthPluginService;
+import com.alibaba.nacos.auth.mock.MockResourceParser;
 import com.alibaba.nacos.plugin.auth.api.IdentityContext;
 import com.alibaba.nacos.plugin.auth.api.Permission;
 import com.alibaba.nacos.plugin.auth.api.Resource;
@@ -85,7 +86,8 @@ void testParseResourceWithSpecifiedResource() throws NoSuchMethodException {
         assertEquals(SignType.SPECIFIED, actual.getType());
         assertNull(actual.getNamespaceId());
         assertNull(actual.getGroup());
-        assertNull(actual.getProperties());
+        assertNotNull(actual.getProperties());
+        assertTrue(actual.getProperties().isEmpty());
     }
     
     @Test
@@ -96,6 +98,14 @@ void testParseResourceWithNonExistType() throws NoSuchMethodException {
         assertEquals(Resource.EMPTY_RESOURCE, actual);
     }
     
+    @Test
+    @Secured(signType = "non-exist", parser = MockResourceParser.class)
+    void testParseResourceWithNonExistTypeException() throws NoSuchMethodException {
+        Secured secured = getMethodSecure("testParseResourceWithNonExistTypeException");
+        Resource actual = protocolAuthService.parseResource(namingRequest, secured);
+        assertEquals(Resource.EMPTY_RESOURCE, actual);
+    }
+    
     @Test
     @Secured()
     void testParseResourceWithNamingType() throws NoSuchMethodException {
@@ -152,6 +162,22 @@ void testValidateAuthorityWithPlugin() throws AccessException {
                 new Permission(Resource.EMPTY_RESOURCE, "")));
     }
     
+    @Test
+    @Secured(signType = SignType.CONFIG)
+    void testEnabledAuthWithPlugin() throws NoSuchMethodException {
+        Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN);
+        Secured secured = getMethodSecure("testEnabledAuthWithPlugin");
+        assertTrue(protocolAuthService.enableAuth(secured));
+    }
+    
+    @Test
+    @Secured(signType = SignType.CONFIG)
+    void testEnabledAuthWithoutPlugin() throws NoSuchMethodException {
+        Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin");
+        Secured secured = getMethodSecure("testEnabledAuthWithoutPlugin");
+        assertFalse(protocolAuthService.enableAuth(secured));
+    }
+    
     private Secured getMethodSecure(String methodName) throws NoSuchMethodException {
         Method method = GrpcProtocolAuthServiceTest.class.getDeclaredMethod(methodName);
         return method.getAnnotation(Secured.class);
diff --git a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java
index cf6cb59a2b3..42e59024458 100644
--- a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java
+++ b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java
@@ -21,6 +21,7 @@
 import com.alibaba.nacos.auth.annotation.Secured;
 import com.alibaba.nacos.auth.config.AuthConfigs;
 import com.alibaba.nacos.auth.mock.MockAuthPluginService;
+import com.alibaba.nacos.auth.mock.MockResourceParser;
 import com.alibaba.nacos.plugin.auth.api.IdentityContext;
 import com.alibaba.nacos.plugin.auth.api.Permission;
 import com.alibaba.nacos.plugin.auth.api.Resource;
@@ -56,12 +57,12 @@ class HttpProtocolAuthServiceTest {
     @Mock
     private HttpServletRequest request;
     
-    private HttpProtocolAuthService httpProtocolAuthService;
+    private HttpProtocolAuthService protocolAuthService;
     
     @BeforeEach
     void setUp() throws Exception {
-        httpProtocolAuthService = new HttpProtocolAuthService(authConfigs);
-        httpProtocolAuthService.initialize();
+        protocolAuthService = new HttpProtocolAuthService(authConfigs);
+        protocolAuthService.initialize();
         Mockito.when(request.getParameter(eq(CommonParams.NAMESPACE_ID))).thenReturn("testNNs");
         Mockito.when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testNG");
         Mockito.when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS");
@@ -71,22 +72,32 @@ void setUp() throws Exception {
     }
     
     @Test
-    @Secured(resource = "testResource")
+    @Secured(resource = "testResource", tags = {"testTag"})
     void testParseResourceWithSpecifiedResource() throws NoSuchMethodException {
         Secured secured = getMethodSecure("testParseResourceWithSpecifiedResource");
-        Resource actual = httpProtocolAuthService.parseResource(request, secured);
+        Resource actual = protocolAuthService.parseResource(request, secured);
         assertEquals("testResource", actual.getName());
         assertEquals(SignType.SPECIFIED, actual.getType());
         assertNull(actual.getNamespaceId());
         assertNull(actual.getGroup());
-        assertNull(actual.getProperties());
+        assertNotNull(actual.getProperties());
+        assertEquals(1, actual.getProperties().size());
+        assertEquals("testTag", actual.getProperties().get("testTag"));
     }
     
     @Test
     @Secured(signType = "non-exist")
     void testParseResourceWithNonExistType() throws NoSuchMethodException {
         Secured secured = getMethodSecure("testParseResourceWithNonExistType");
-        Resource actual = httpProtocolAuthService.parseResource(request, secured);
+        Resource actual = protocolAuthService.parseResource(request, secured);
+        assertEquals(Resource.EMPTY_RESOURCE, actual);
+    }
+    
+    @Test
+    @Secured(signType = "non-exist", parser = MockResourceParser.class)
+    void testParseResourceWithNonExistTypeException() throws NoSuchMethodException {
+        Secured secured = getMethodSecure("testParseResourceWithNonExistTypeException");
+        Resource actual = protocolAuthService.parseResource(request, secured);
         assertEquals(Resource.EMPTY_RESOURCE, actual);
     }
     
@@ -94,7 +105,7 @@ void testParseResourceWithNonExistType() throws NoSuchMethodException {
     @Secured()
     void testParseResourceWithNamingType() throws NoSuchMethodException {
         Secured secured = getMethodSecure("testParseResourceWithNamingType");
-        Resource actual = httpProtocolAuthService.parseResource(request, secured);
+        Resource actual = protocolAuthService.parseResource(request, secured);
         assertEquals(SignType.NAMING, actual.getType());
         assertEquals("testS", actual.getName());
         assertEquals("testNNs", actual.getNamespaceId());
@@ -106,7 +117,7 @@ void testParseResourceWithNamingType() throws NoSuchMethodException {
     @Secured(signType = SignType.CONFIG)
     void testParseResourceWithConfigType() throws NoSuchMethodException {
         Secured secured = getMethodSecure("testParseResourceWithConfigType");
-        Resource actual = httpProtocolAuthService.parseResource(request, secured);
+        Resource actual = protocolAuthService.parseResource(request, secured);
         assertEquals(SignType.CONFIG, actual.getType());
         assertEquals("testD", actual.getName());
         assertEquals("testNNs", actual.getNamespaceId());
@@ -116,36 +127,52 @@ void testParseResourceWithConfigType() throws NoSuchMethodException {
     
     @Test
     void testParseIdentity() {
-        IdentityContext actual = httpProtocolAuthService.parseIdentity(request);
+        IdentityContext actual = protocolAuthService.parseIdentity(request);
         assertNotNull(actual);
     }
     
     @Test
     void testValidateIdentityWithoutPlugin() throws AccessException {
         IdentityContext identityContext = new IdentityContext();
-        assertTrue(httpProtocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE));
+        assertTrue(protocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE));
     }
     
     @Test
     void testValidateIdentityWithPlugin() throws AccessException {
         Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN);
         IdentityContext identityContext = new IdentityContext();
-        assertFalse(httpProtocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE));
+        assertFalse(protocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE));
     }
     
     @Test
     void testValidateAuthorityWithoutPlugin() throws AccessException {
-        assertTrue(httpProtocolAuthService.validateAuthority(new IdentityContext(),
+        assertTrue(protocolAuthService.validateAuthority(new IdentityContext(),
                 new Permission(Resource.EMPTY_RESOURCE, "")));
     }
     
     @Test
     void testValidateAuthorityWithPlugin() throws AccessException {
         Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN);
-        assertFalse(httpProtocolAuthService.validateAuthority(new IdentityContext(),
+        assertFalse(protocolAuthService.validateAuthority(new IdentityContext(),
                 new Permission(Resource.EMPTY_RESOURCE, "")));
     }
     
+    @Test
+    @Secured(signType = SignType.CONFIG)
+    void testEnabledAuthWithPlugin() throws NoSuchMethodException {
+        Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN);
+        Secured secured = getMethodSecure("testEnabledAuthWithPlugin");
+        assertTrue(protocolAuthService.enableAuth(secured));
+    }
+    
+    @Test
+    @Secured(signType = SignType.CONFIG)
+    void testEnabledAuthWithoutPlugin() throws NoSuchMethodException {
+        Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin");
+        Secured secured = getMethodSecure("testEnabledAuthWithoutPlugin");
+        assertFalse(protocolAuthService.enableAuth(secured));
+    }
+    
     private Secured getMethodSecure(String methodName) throws NoSuchMethodException {
         Method method = HttpProtocolAuthServiceTest.class.getDeclaredMethod(methodName);
         return method.getAnnotation(Secured.class);
diff --git a/auth/src/test/java/com/alibaba/nacos/auth/mock/MockResourceParser.java b/auth/src/test/java/com/alibaba/nacos/auth/mock/MockResourceParser.java
new file mode 100644
index 00000000000..11cc1ef7899
--- /dev/null
+++ b/auth/src/test/java/com/alibaba/nacos/auth/mock/MockResourceParser.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.auth.mock;
+
+import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
+import com.alibaba.nacos.auth.annotation.Secured;
+import com.alibaba.nacos.auth.parser.ResourceParser;
+import com.alibaba.nacos.plugin.auth.api.Resource;
+
+public class MockResourceParser implements ResourceParser {
+    
+    @Override
+    public Resource parse(Object request, Secured secured) {
+        throw new NacosRuntimeException(500);
+    }
+}
diff --git a/auth/src/test/java/com/alibaba/nacos/auth/parser/grpc/ConfigGrpcResourceParserTest.java b/auth/src/test/java/com/alibaba/nacos/auth/parser/grpc/ConfigGrpcResourceParserTest.java
index 03d0457f574..15538631db8 100644
--- a/auth/src/test/java/com/alibaba/nacos/auth/parser/grpc/ConfigGrpcResourceParserTest.java
+++ b/auth/src/test/java/com/alibaba/nacos/auth/parser/grpc/ConfigGrpcResourceParserTest.java
@@ -18,6 +18,7 @@
 
 import com.alibaba.nacos.api.common.Constants;
 import com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest;
+import com.alibaba.nacos.api.config.remote.request.ConfigChangeNotifyRequest;
 import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest;
 import com.alibaba.nacos.api.remote.request.Request;
 import com.alibaba.nacos.auth.annotation.Secured;
@@ -98,6 +99,23 @@ void testParseWithConfigBatchListenRequest() throws NoSuchMethodException {
         assertEquals(StringUtils.EMPTY, actual.getGroup());
         assertEquals(StringUtils.EMPTY, actual.getName());
         assertEquals(Constants.Config.CONFIG_MODULE, actual.getType());
+        request.getConfigListenContexts().clear();
+        actual = resourceParser.parse(request, secured);
+        assertEquals(StringUtils.EMPTY, actual.getNamespaceId());
+        assertEquals(StringUtils.EMPTY, actual.getGroup());
+        assertEquals(StringUtils.EMPTY, actual.getName());
+        assertEquals(Constants.Config.CONFIG_MODULE, actual.getType());
+    }
+    
+    @Test
+    @Secured(signType = Constants.Config.CONFIG_MODULE)
+    void testParseWithReflectionRequest() throws NoSuchMethodException {
+        Secured secured = getMethodSecure();
+        Request request = ConfigChangeNotifyRequest.build("rTestD", "rTestG", "rTestNs");
+        Resource actual = resourceParser.parse(request, secured);
+        assertEquals("rTestNs", actual.getNamespaceId());
+        assertEquals("rTestG", actual.getGroup());
+        assertEquals("rTestD", actual.getName());
     }
     
     private Request mockConfigRequest(String tenant, String group, String dataId) {
diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java
index 0332ba729b0..eef3a3e7902 100644
--- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java
+++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java
@@ -99,7 +99,6 @@ private String resolveToken(IdentityContext identityContext) {
     public Boolean validateAuthority(IdentityContext identityContext, Permission permission) throws AccessException {
         NacosUser user = (NacosUser) identityContext.getParameter(AuthConstants.NACOS_USER_KEY);
         authenticationManager.authorize(permission, user);
-        
         return true;
     }
     
diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java
index ed904b83a6e..af23a2d5ba4 100644
--- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java
+++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java
@@ -175,7 +175,8 @@ public Object deleteUser(@RequestParam String username) {
      * @since 1.2.0
      */
     @PutMapping
-    @Secured(resource = AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE)
+    @Secured(resource = AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE, tags = {
+            AuthConstants.UPDATE_PASSWORD_ENTRY_POINT})
     public Object updateUser(@RequestParam String username, @RequestParam String newPassword,
             HttpServletResponse response, HttpServletRequest request) throws IOException {
         // admin or same user
diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java
index ffd28b1e1c2..7e6803d4b5d 100644
--- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java
+++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java
@@ -41,6 +41,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.regex.Pattern;
@@ -120,8 +121,7 @@ private void reload() {
      * @return true if granted, false otherwise
      */
     public boolean hasPermission(NacosUser nacosUser, Permission permission) {
-        //update password
-        if (AuthConstants.UPDATE_PASSWORD_ENTRY_POINT.equals(permission.getResource().getName())) {
+        if (isUpdatePasswordPermission(permission)) {
             return true;
         }
         
@@ -161,6 +161,14 @@ public boolean hasPermission(NacosUser nacosUser, Permission permission) {
         return false;
     }
     
+    /**
+     * If API is update user password, don't do permission check, because there is permission check in API logic.
+     */
+    private boolean isUpdatePasswordPermission(Permission permission) {
+        Properties properties = permission.getResource().getProperties();
+        return null != properties && properties.contains(AuthConstants.UPDATE_PASSWORD_ENTRY_POINT);
+    }
+    
     public List getRoles(String username) {
         List roleInfoList = roleInfoMap.get(username);
         if (!authConfigs.isCachingEnabled() || roleInfoList == null) {
diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java
index aea87556a72..315b3b3cac2 100644
--- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java
+++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java
@@ -38,6 +38,7 @@
 import java.lang.reflect.Method;
 import java.util.Collections;
 import java.util.List;
+import java.util.Properties;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -113,10 +114,14 @@ void hasPermission() {
         
         Permission permission2 = new Permission();
         permission2.setAction("rw");
-        Resource resource = new Resource("public", "group", AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, "rw", null);
+        Resource resource = new Resource("public", "group", AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, "rw",
+                new Properties());
         permission2.setResource(resource);
         boolean res2 = nacosRoleService.hasPermission(nacosUser, permission2);
-        assertTrue(res2);
+        assertFalse(res2);
+        resource.getProperties().put(AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, AuthConstants.UPDATE_PASSWORD_ENTRY_POINT);
+        boolean res3 = nacosRoleService.hasPermission(nacosUser, permission2);
+        assertTrue(res3);
     }
     
     @Test
@@ -127,7 +132,8 @@ void getRoles() {
     
     @Test
     void getRolesFromDatabase() {
-        Page roleInfoPage = nacosRoleService.getRolesFromDatabase("nacos", "ROLE_ADMIN", 1, Integer.MAX_VALUE);
+        Page roleInfoPage = nacosRoleService.getRolesFromDatabase("nacos", "ROLE_ADMIN", 1,
+                Integer.MAX_VALUE);
         assertEquals(0, roleInfoPage.getTotalCount());
     }
     
@@ -141,8 +147,8 @@ void getPermissions() {
     
     @Test
     void getPermissionsByRoleFromDatabase() {
-        Page permissionsByRoleFromDatabase = nacosRoleService.getPermissionsByRoleFromDatabase("role-admin", 1,
-                Integer.MAX_VALUE);
+        Page permissionsByRoleFromDatabase = nacosRoleService.getPermissionsByRoleFromDatabase(
+                "role-admin", 1, Integer.MAX_VALUE);
         assertNull(permissionsByRoleFromDatabase);
     }
     
@@ -169,7 +175,8 @@ void deleteRole() {
     
     @Test
     void getPermissionsFromDatabase() {
-        Page permissionsFromDatabase = nacosRoleService.getPermissionsFromDatabase("role-admin", 1, Integer.MAX_VALUE);
+        Page permissionsFromDatabase = nacosRoleService.getPermissionsFromDatabase("role-admin", 1,
+                Integer.MAX_VALUE);
         assertEquals(0, permissionsFromDatabase.getTotalCount());
     }
     

From 6ebe0f7e4c7d06aa362fd15744e7a305d2374647 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=A8=E7=BF=8A=20SionYang?= 
Date: Mon, 21 Oct 2024 16:59:27 +0800
Subject: [PATCH 3/6] Add Unit test for persistence module. (#12768)

---
 .../ExternalDataSourceServiceImpl.java        |   2 +-
 .../persistence/exception/NJdbcException.java |   4 +
 .../EmbeddedPaginationHelperImpl.java         | 136 +--
 .../embedded/operate/DatabaseOperate.java     |  23 -
 .../ExternalStoragePaginationHelperImpl.java  | 149 +--
 .../utils/ConnectionCheckUtil.java            |  18 +-
 .../DatasourceConfigurationTest.java          | 133 +++
 .../datasource/DynamicDataSourceTest.java     |  41 +-
 .../ExternalDataSourceServiceImplTest.java    | 156 ++-
 .../LocalDataSourceServiceImplTest.java       |  83 +-
 .../datasource/mock/MockConnection.java       | 314 ++++++
 .../datasource/mock/MockDriver.java           |  63 ++
 .../mock/MockPreparedStatement.java           | 539 ++++++++++
 .../datasource/mock/MockResultSet.java        | 998 ++++++++++++++++++
 .../datasource/mock/MockStatement.java        | 246 +++++
 .../exception/NJdbcExceptionTest.java         |  79 ++
 .../nacos/persistence/model/PageTest.java     |  34 +
 .../persistence/model/event/EventTest.java    |  48 +
 .../repository/RowMapperManagerTest.java      |  89 ++
 .../EmbeddedPaginationHelperImplTest.java     | 350 ++++++
 .../EmbeddedStorageContextHolderTest.java     |  70 ++
 .../hook/EmbeddedApplyHookHolderTest.java     |  57 +
 .../operate/BaseDatabaseOperateTest.java      | 205 ++++
 .../embedded/operate/DatabaseOperateTest.java |  47 +
 .../StandaloneDatabaseOperateImplTest.java    | 136 ++-
 .../embedded/sql/ModifyRequestTest.java       |  35 +
 .../embedded/sql/SelectRequestTest.java       |  41 +
 ...ternalStoragePaginationHelperImplTest.java | 343 ++++++
 .../utils/PersistenceExecutorTest.java        |  17 +
 .../test/resources/META-INF/derby-schema.sql  | 227 ++++
 .../resources/META-INF/test-derby-import.sql  |  22 +
 31 files changed, 4426 insertions(+), 279 deletions(-)
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/configuration/DatasourceConfigurationTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockConnection.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockDriver.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockPreparedStatement.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockResultSet.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockStatement.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/exception/NJdbcExceptionTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/model/PageTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/model/event/EventTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/RowMapperManagerTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImplTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedStorageContextHolderTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/hook/EmbeddedApplyHookHolderTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/BaseDatabaseOperateTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperateTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/ModifyRequestTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/SelectRequestTest.java
 create mode 100644 persistence/src/test/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImplTest.java
 create mode 100644 persistence/src/test/resources/META-INF/derby-schema.sql
 create mode 100644 persistence/src/test/resources/META-INF/test-derby-import.sql

diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImpl.java b/persistence/src/main/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImpl.java
index 739487b83cd..9e9ef72ab1d 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImpl.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImpl.java
@@ -185,7 +185,7 @@ public boolean checkMasterWritable() {
                 return result == 0;
             }
         } catch (CannotGetJdbcConnectionException e) {
-            LOGGER.error("[db-error] " + e.toString(), e);
+            LOGGER.error("[db-error] " + e, e);
             return false;
         }
         
diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/exception/NJdbcException.java b/persistence/src/main/java/com/alibaba/nacos/persistence/exception/NJdbcException.java
index 2d9581da783..f352779e673 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/exception/NJdbcException.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/exception/NJdbcException.java
@@ -49,4 +49,8 @@ public NJdbcException(String msg, Throwable cause) {
     public NJdbcException(Throwable cause) {
         super("", cause);
     }
+    
+    public String getOriginExceptionName() {
+        return originExceptionName;
+    }
 }
diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImpl.java b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImpl.java
index 2b77bf37cca..0d702e0d98f 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImpl.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImpl.java
@@ -31,7 +31,7 @@
  * @author boyan
  * @date 2010-5-6
  */
-public class EmbeddedPaginationHelperImpl implements PaginationHelper {
+public class EmbeddedPaginationHelperImpl implements PaginationHelper {
     
     private final DatabaseOperate databaseOperate;
     
@@ -59,66 +59,27 @@ public Page fetchPage(final String sqlCountRows, final String sqlFetchRows, f
     @Override
     public Page fetchPage(final String sqlCountRows, final String sqlFetchRows, Object[] args, final int pageNo,
             final int pageSize, final Long lastMaxId, final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        
-        // Query the total number of current records
-        Integer rowCountInt = databaseOperate.queryOne(sqlCountRows, args, Integer.class);
-        if (rowCountInt == null) {
-            throw new IllegalArgumentException("fetchPageLimit error");
-        }
-        
-        // Count pages
-        int pageCount = rowCountInt / pageSize;
-        if (rowCountInt > pageSize * pageCount) {
-            pageCount++;
-        }
-        
-        // Create Page object
-        final Page page = new Page<>();
-        page.setPageNumber(pageNo);
-        page.setPagesAvailable(pageCount);
-        page.setTotalCount(rowCountInt);
-        
-        if (pageNo > pageCount) {
-            return page;
-        }
-        
-        List result = databaseOperate.queryMany(sqlFetchRows, args, rowMapper);
-        for (E item : result) {
-            page.getPageItems().add(item);
-        }
-        return page;
+        return doFetchPage(sqlCountRows, args, sqlFetchRows, args, pageNo, pageSize, rowMapper);
     }
     
     @Override
     public Page fetchPageLimit(final String sqlCountRows, final String sqlFetchRows, final Object[] args,
             final int pageNo, final int pageSize, final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        // Query the total number of current records
-        Integer rowCountInt = databaseOperate.queryOne(sqlCountRows, Integer.class);
-        if (rowCountInt == null) {
-            throw new IllegalArgumentException("fetchPageLimit error");
-        }
-        
-        // Count pages
-        int pageCount = rowCountInt / pageSize;
-        if (rowCountInt > pageSize * pageCount) {
-            pageCount++;
-        }
-        
+        return doFetchPage(sqlCountRows, null, sqlFetchRows, args, pageNo, pageSize, rowMapper);
+    }
+    
+    @Override
+    public Page fetchPageLimit(final String sqlCountRows, final Object[] args1, final String sqlFetchRows,
+            final Object[] args2, final int pageNo, final int pageSize, final RowMapper rowMapper) {
+        return doFetchPage(sqlCountRows, args1, sqlFetchRows, args2, pageNo, pageSize, rowMapper);
+    }
+    
+    @Override
+    public Page fetchPageLimit(final String sqlFetchRows, final Object[] args, final int pageNo, final int pageSize,
+            final RowMapper rowMapper) {
+        checkPageInfo(pageNo, pageSize);
         // Create Page object
         final Page page = new Page<>();
-        page.setPageNumber(pageNo);
-        page.setPagesAvailable(pageCount);
-        page.setTotalCount(rowCountInt);
-        
-        if (pageNo > pageCount) {
-            return page;
-        }
         
         List result = databaseOperate.queryMany(sqlFetchRows, args, rowMapper);
         for (E item : result) {
@@ -128,13 +89,38 @@ public Page fetchPageLimit(final String sqlCountRows, final String sqlFetchRo
     }
     
     @Override
-    public Page fetchPageLimit(final String sqlCountRows, final Object[] args1, final String sqlFetchRows,
-            final Object[] args2, final int pageNo, final int pageSize, final RowMapper rowMapper) {
+    public Page fetchPageLimit(MapperResult countMapperResult, MapperResult mapperResult, int pageNo, int pageSize,
+            RowMapper rowMapper) {
+        return fetchPageLimit(countMapperResult.getSql(), countMapperResult.getParamList().toArray(),
+                mapperResult.getSql(), mapperResult.getParamList().toArray(), pageNo, pageSize, rowMapper);
+    }
+    
+    @Override
+    public void updateLimit(final String sql, final Object[] args) {
+        EmbeddedStorageContextHolder.addSqlContext(sql, args);
+        try {
+            databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext());
+        } finally {
+            EmbeddedStorageContextHolder.cleanAllContext();
+        }
+    }
+    
+    private void checkPageInfo(final int pageNo, final int pageSize) {
         if (pageNo <= 0 || pageSize <= 0) {
             throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
         }
+    }
+    
+    private Page doFetchPage(final String sqlCountRows, final Object[] countAgrs, final String sqlFetchRows,
+            final Object[] fetchArgs, final int pageNo, final int pageSize, final RowMapper rowMapper) {
+        checkPageInfo(pageNo, pageSize);
         // Query the total number of current records
-        Integer rowCountInt = databaseOperate.queryOne(sqlCountRows, args1, Integer.class);
+        Integer rowCountInt = null;
+        if (null != countAgrs) {
+            rowCountInt = databaseOperate.queryOne(sqlCountRows, countAgrs, Integer.class);
+        } else {
+            rowCountInt = databaseOperate.queryOne(sqlCountRows, Integer.class);
+        }
         if (rowCountInt == null) {
             throw new IllegalArgumentException("fetchPageLimit error");
         }
@@ -155,44 +141,10 @@ public Page fetchPageLimit(final String sqlCountRows, final Object[] args1, f
             return page;
         }
         
-        List result = databaseOperate.queryMany(sqlFetchRows, args2, rowMapper);
-        for (E item : result) {
-            page.getPageItems().add(item);
-        }
-        return page;
-    }
-    
-    @Override
-    public Page fetchPageLimit(final String sqlFetchRows, final Object[] args, final int pageNo, final int pageSize,
-            final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        // Create Page object
-        final Page page = new Page<>();
-        
-        List result = databaseOperate.queryMany(sqlFetchRows, args, rowMapper);
+        List result = databaseOperate.queryMany(sqlFetchRows, fetchArgs, rowMapper);
         for (E item : result) {
             page.getPageItems().add(item);
         }
         return page;
     }
-    
-    @Override
-    public Page fetchPageLimit(MapperResult countMapperResult, MapperResult mapperResult, int pageNo, int pageSize,
-            RowMapper rowMapper) {
-        return fetchPageLimit(countMapperResult.getSql(), countMapperResult.getParamList().toArray(),
-                mapperResult.getSql(), mapperResult.getParamList().toArray(), pageNo, pageSize, rowMapper);
-    }
-    
-    @Override
-    public void updateLimit(final String sql, final Object[] args) {
-        EmbeddedStorageContextHolder.addSqlContext(sql, args);
-        try {
-            databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext());
-        } finally {
-            EmbeddedStorageContextHolder.cleanAllContext();
-        }
-    }
-    
 }
diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperate.java b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperate.java
index 568d73f9eeb..9e154336518 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperate.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperate.java
@@ -24,7 +24,6 @@
 import java.io.File;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
@@ -152,26 +151,4 @@ default Boolean blockUpdate(BiConsumer consumer) {
         }
     }
     
-    /**
-     * data modify transaction The SqlContext to be executed in the current thread will be executed and automatically
-     * cleared.
-     *
-     * @return is success
-     */
-    default CompletableFuture futureUpdate() {
-        try {
-            CompletableFuture future = new CompletableFuture<>();
-            update(EmbeddedStorageContextHolder.getCurrentSqlContext(), (o, throwable) -> {
-                if (Objects.nonNull(throwable)) {
-                    future.completeExceptionally(throwable);
-                    return;
-                }
-                future.complete(o);
-            });
-            return future;
-        } finally {
-            EmbeddedStorageContextHolder.cleanAllContext();
-        }
-    }
-    
 }
diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImpl.java b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImpl.java
index 04d6e1e74d9..90fb7ec30e7 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImpl.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImpl.java
@@ -31,7 +31,7 @@
  * @author liaochuntao
  */
 
-public class ExternalStoragePaginationHelperImpl implements PaginationHelper {
+public class ExternalStoragePaginationHelperImpl implements PaginationHelper {
     
     private final JdbcTemplate jdbcTemplate;
     
@@ -59,72 +59,13 @@ public Page fetchPage(final String sqlCountRows, final String sqlFetchRows, f
     @Override
     public Page fetchPage(final String sqlCountRows, final String sqlFetchRows, Object[] args, final int pageNo,
             final int pageSize, final Long lastMaxId, final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        
-        // Query the total number of current records.
-        Integer rowCountInt = jdbcTemplate.queryForObject(sqlCountRows, args, Integer.class);
-        if (rowCountInt == null) {
-            throw new IllegalArgumentException("fetchPageLimit error");
-        }
-        
-        // Compute pages count
-        int pageCount = rowCountInt / pageSize;
-        if (rowCountInt > pageSize * pageCount) {
-            pageCount++;
-        }
-        
-        // Create Page object
-        final Page page = new Page<>();
-        page.setPageNumber(pageNo);
-        page.setPagesAvailable(pageCount);
-        page.setTotalCount(rowCountInt);
-        
-        if (pageNo > pageCount) {
-            return page;
-        }
-        
-        List result = jdbcTemplate.query(sqlFetchRows, args, rowMapper);
-        for (E item : result) {
-            page.getPageItems().add(item);
-        }
-        return page;
+        return doFetchPage(sqlCountRows, args, sqlFetchRows, args, pageNo, pageSize, rowMapper);
     }
     
     @Override
     public Page fetchPageLimit(final String sqlCountRows, final String sqlFetchRows, final Object[] args,
             final int pageNo, final int pageSize, final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        // Query the total number of current records
-        Integer rowCountInt = jdbcTemplate.queryForObject(sqlCountRows, Integer.class);
-        if (rowCountInt == null) {
-            throw new IllegalArgumentException("fetchPageLimit error");
-        }
-        
-        // Compute pages count
-        int pageCount = rowCountInt / pageSize;
-        if (rowCountInt > pageSize * pageCount) {
-            pageCount++;
-        }
-        
-        // Create Page object
-        final Page page = new Page<>();
-        page.setPageNumber(pageNo);
-        page.setPagesAvailable(pageCount);
-        page.setTotalCount(rowCountInt);
-        
-        if (pageNo > pageCount) {
-            return page;
-        }
-        
-        List result = jdbcTemplate.query(sqlFetchRows, args, rowMapper);
-        for (E item : result) {
-            page.getPageItems().add(item);
-        }
-        return page;
+        return doFetchPage(sqlCountRows, null, sqlFetchRows, args, pageNo, pageSize, rowMapper);
     }
     
     @Override
@@ -137,12 +78,48 @@ public Page fetchPageLimit(MapperResult countMapperResult, MapperResult mapperRe
     @Override
     public Page fetchPageLimit(final String sqlCountRows, final Object[] args1, final String sqlFetchRows,
             final Object[] args2, final int pageNo, final int pageSize, final RowMapper rowMapper) {
+        return doFetchPage(sqlCountRows, args1, sqlFetchRows, args2, pageNo, pageSize, rowMapper);
+    }
+    
+    @Override
+    public Page fetchPageLimit(final String sqlFetchRows, final Object[] args, final int pageNo, final int pageSize,
+            final RowMapper rowMapper) {
+        checkPageInfo(pageNo, pageSize);
+        // Create Page object
+        final Page page = new Page<>();
+        List result = jdbcTemplate.query(sqlFetchRows, args, rowMapper);
+        for (E item : result) {
+            page.getPageItems().add(item);
+        }
+        return page;
+    }
+    
+    @Override
+    public void updateLimit(final String sql, final Object[] args) {
+        try {
+            jdbcTemplate.update(sql, args);
+        } finally {
+            EmbeddedStorageContextHolder.cleanAllContext();
+        }
+    }
+    
+    private void checkPageInfo(final int pageNo, final int pageSize) {
         if (pageNo <= 0 || pageSize <= 0) {
             throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
         }
+    }
+    
+    private Page doFetchPage(final String sqlCountRows, final Object[] countAgrs, final String sqlFetchRows,
+            final Object[] fetchArgs, final int pageNo, final int pageSize, final RowMapper rowMapper) {
+        checkPageInfo(pageNo, pageSize);
         // Query the total number of current records
-        Integer rowCountInt = jdbcTemplate.queryForObject(sqlCountRows, args1, Integer.class);
-        if (rowCountInt == null) {
+        Integer rowCountInt = null;
+        if (null != countAgrs) {
+            rowCountInt = jdbcTemplate.queryForObject(sqlCountRows, countAgrs, Integer.class);
+        } else {
+            rowCountInt = jdbcTemplate.queryForObject(sqlCountRows, Integer.class);
+        }
+        if (null == rowCountInt) {
             throw new IllegalArgumentException("fetchPageLimit error");
         }
         
@@ -161,52 +138,10 @@ public Page fetchPageLimit(final String sqlCountRows, final Object[] args1, f
         if (pageNo > pageCount) {
             return page;
         }
-        List result = jdbcTemplate.query(sqlFetchRows, args2, rowMapper);
-        for (E item : result) {
-            page.getPageItems().add(item);
-        }
-        return page;
-    }
-    
-    @Override
-    public Page fetchPageLimit(final String sqlFetchRows, final Object[] args, final int pageNo, final int pageSize,
-            final RowMapper rowMapper) {
-        if (pageNo <= 0 || pageSize <= 0) {
-            throw new IllegalArgumentException("pageNo and pageSize must be greater than zero");
-        }
-        // Create Page object
-        final Page page = new Page<>();
-        List result = jdbcTemplate.query(sqlFetchRows, args, rowMapper);
+        List result = jdbcTemplate.query(sqlFetchRows, fetchArgs, rowMapper);
         for (E item : result) {
             page.getPageItems().add(item);
         }
         return page;
     }
-    
-    @Override
-    public void updateLimit(final String sql, final Object[] args) {
-        try {
-            jdbcTemplate.update(sql, args);
-        } finally {
-            EmbeddedStorageContextHolder.cleanAllContext();
-        }
-    }
-    
-    /**
-     * Update limit with response.
-     *
-     * @param sql  sql
-     * @param args args
-     * @return update row count
-     */
-    public int updateLimitWithResponse(final String sql, final Object[] args) {
-        String sqlUpdate = sql;
-        
-        try {
-            return jdbcTemplate.update(sqlUpdate, args);
-        } finally {
-            EmbeddedStorageContextHolder.cleanAllContext();
-        }
-    }
-    
 }
diff --git a/persistence/src/main/java/com/alibaba/nacos/persistence/utils/ConnectionCheckUtil.java b/persistence/src/main/java/com/alibaba/nacos/persistence/utils/ConnectionCheckUtil.java
index 77c55f5ba5c..108a0003f90 100644
--- a/persistence/src/main/java/com/alibaba/nacos/persistence/utils/ConnectionCheckUtil.java
+++ b/persistence/src/main/java/com/alibaba/nacos/persistence/utils/ConnectionCheckUtil.java
@@ -17,8 +17,6 @@
 package com.alibaba.nacos.persistence.utils;
 
 import com.zaxxer.hikari.HikariDataSource;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * DataSource Connection CheckUtil.
@@ -27,28 +25,16 @@
  */
 public class ConnectionCheckUtil {
     
-    private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionCheckUtil.class);
-    
     /**
      * check HikariDataSource connection ,avoid [no datasource set] text.
      *
      * @param ds HikariDataSource object
      */
     public static void checkDataSourceConnection(HikariDataSource ds) {
-        java.sql.Connection connection = null;
-        try {
-            connection = ds.getConnection();
+        try (java.sql.Connection connection = ds.getConnection()) {
+            connection.isClosed();
         } catch (Exception e) {
             throw new RuntimeException(e);
-        } finally {
-            if (connection != null) {
-                try {
-                    connection.close();
-                } catch (Exception e) {
-                    LOGGER.error(e.getMessage(), e);
-                }
-            }
         }
     }
-    
 }
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/configuration/DatasourceConfigurationTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/configuration/DatasourceConfigurationTest.java
new file mode 100644
index 00000000000..544f0ca3f40
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/configuration/DatasourceConfigurationTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.configuration;
+
+import com.alibaba.nacos.persistence.constants.PersistenceConstant;
+import com.alibaba.nacos.sys.env.EnvUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+@ExtendWith(MockitoExtension.class)
+class DatasourceConfigurationTest {
+    
+    @Mock
+    ConfigurableApplicationContext context;
+    
+    DatasourceConfiguration datasourceConfiguration;
+    
+    MockEnvironment environment;
+    
+    @BeforeEach
+    void setUp() {
+        environment = new MockEnvironment();
+        EnvUtil.setEnvironment(environment);
+        datasourceConfiguration = new DatasourceConfiguration();
+        DatasourceConfiguration.useExternalDB = false;
+        DatasourceConfiguration.embeddedStorage = true;
+    }
+    
+    @AfterEach
+    void tearDown() {
+        DatasourceConfiguration.useExternalDB = false;
+        DatasourceConfiguration.embeddedStorage = true;
+        EnvUtil.setIsStandalone(true);
+        EnvUtil.setEnvironment(null);
+        System.clearProperty(PersistenceConstant.EMBEDDED_STORAGE);
+    }
+    
+    @Test
+    void testInitializeForEmptyDatasourceForStandaloneMode() {
+        datasourceConfiguration.initialize(context);
+        assertTrue(DatasourceConfiguration.isEmbeddedStorage());
+        assertFalse(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForEmptyDatasourceForClusterMode() {
+        EnvUtil.setIsStandalone(false);
+        DatasourceConfiguration.embeddedStorage = false;
+        datasourceConfiguration.initialize(context);
+        assertFalse(DatasourceConfiguration.isEmbeddedStorage());
+        assertTrue(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForDerbyForStandaloneMode() {
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, PersistenceConstant.DERBY);
+        System.setProperty(PersistenceConstant.EMBEDDED_STORAGE, "true");
+        datasourceConfiguration.initialize(context);
+        assertTrue(DatasourceConfiguration.isEmbeddedStorage());
+        assertFalse(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForDerbyForClusterMode() {
+        EnvUtil.setIsStandalone(false);
+        DatasourceConfiguration.embeddedStorage = false;
+        System.setProperty(PersistenceConstant.EMBEDDED_STORAGE, "true");
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, PersistenceConstant.DERBY);
+        datasourceConfiguration.initialize(context);
+        assertTrue(DatasourceConfiguration.isEmbeddedStorage());
+        assertFalse(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForMySqlForStandaloneMode() {
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, PersistenceConstant.MYSQL);
+        datasourceConfiguration.initialize(context);
+        assertFalse(DatasourceConfiguration.isEmbeddedStorage());
+        assertTrue(DatasourceConfiguration.isUseExternalDB());
+        
+    }
+    
+    @Test
+    void testInitializeForMySqlForClusterMode() {
+        EnvUtil.setIsStandalone(false);
+        DatasourceConfiguration.embeddedStorage = false;
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, PersistenceConstant.MYSQL);
+        datasourceConfiguration.initialize(context);
+        assertFalse(DatasourceConfiguration.isEmbeddedStorage());
+        assertTrue(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForPgSqlForStandaloneMode() {
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, "postgresql");
+        datasourceConfiguration.initialize(context);
+        assertFalse(DatasourceConfiguration.isEmbeddedStorage());
+        assertTrue(DatasourceConfiguration.isUseExternalDB());
+    }
+    
+    @Test
+    void testInitializeForPgSqlForClusterMode() {
+        EnvUtil.setIsStandalone(false);
+        DatasourceConfiguration.embeddedStorage = false;
+        environment.setProperty(PersistenceConstant.DATASOURCE_PLATFORM_PROPERTY, "postgresql");
+        datasourceConfiguration.initialize(context);
+        assertFalse(DatasourceConfiguration.isEmbeddedStorage());
+        assertTrue(DatasourceConfiguration.isUseExternalDB());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/DynamicDataSourceTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/DynamicDataSourceTest.java
index 6bf4c37c669..e1932d155d0 100644
--- a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/DynamicDataSourceTest.java
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/DynamicDataSourceTest.java
@@ -17,15 +17,19 @@
 package com.alibaba.nacos.persistence.datasource;
 
 import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration;
+import com.alibaba.nacos.sys.env.EnvUtil;
+import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.mock.env.MockEnvironment;
 import org.springframework.test.util.ReflectionTestUtils;
 
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 @ExtendWith(MockitoExtension.class)
 class DynamicDataSourceTest {
@@ -41,18 +45,45 @@ class DynamicDataSourceTest {
     
     @BeforeEach
     void setUp() {
+        EnvUtil.setEnvironment(new MockEnvironment());
         dataSource = DynamicDataSource.getInstance();
+    }
+    
+    @AfterEach
+    void tearDown() {
+        DatasourceConfiguration.setEmbeddedStorage(true);
+        DatasourceConfiguration.setUseExternalDB(false);
+        ReflectionTestUtils.setField(dataSource, "localDataSourceService", null);
+        ReflectionTestUtils.setField(dataSource, "basicDataSourceService", null);
+        EnvUtil.setEnvironment(null);
+    }
+    
+    @Test
+    void testGetDataSourceWithAlreadyInitialized() {
         ReflectionTestUtils.setField(dataSource, "localDataSourceService", localDataSourceService);
         ReflectionTestUtils.setField(dataSource, "basicDataSourceService", basicDataSourceService);
+        DatasourceConfiguration.setEmbeddedStorage(true);
+        assertInstanceOf(LocalDataSourceServiceImpl.class, dataSource.getDataSource());
+        
+        DatasourceConfiguration.setEmbeddedStorage(false);
+        assertInstanceOf(ExternalDataSourceServiceImpl.class, dataSource.getDataSource());
     }
     
     @Test
-    void testGetDataSource() {
+    void testInitWithEmbeddedStorage() {
         DatasourceConfiguration.setEmbeddedStorage(true);
-        assertTrue(dataSource.getDataSource() instanceof LocalDataSourceServiceImpl);
-        
+        assertInstanceOf(LocalDataSourceServiceImpl.class, dataSource.getDataSource());
+    }
+    
+    @Test
+    void testInitWithExternalStorage() {
         DatasourceConfiguration.setEmbeddedStorage(false);
-        assertTrue(dataSource.getDataSource() instanceof ExternalDataSourceServiceImpl);
+        assertInstanceOf(ExternalDataSourceServiceImpl.class, dataSource.getDataSource());
     }
     
+    @Test
+    void testInitWithException() {
+        EnvUtil.setEnvironment(null);
+        assertThrows(RuntimeException.class, () -> dataSource.getDataSource());
+    }
 }
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImplTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImplTest.java
index 5c41a0ca991..630b3a90394 100644
--- a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImplTest.java
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/ExternalDataSourceServiceImplTest.java
@@ -16,6 +16,9 @@
 
 package com.alibaba.nacos.persistence.datasource;
 
+import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration;
+import com.alibaba.nacos.persistence.exception.NJdbcException;
+import com.alibaba.nacos.sys.env.EnvUtil;
 import com.zaxxer.hikari.HikariDataSource;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -24,21 +27,31 @@
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
 import org.springframework.jdbc.UncategorizedSQLException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.mock.env.MockEnvironment;
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.transaction.support.TransactionTemplate;
 
+import javax.sql.DataSource;
 import java.sql.SQLException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
@@ -75,34 +88,138 @@ void setUp() {
         ReflectionTestUtils.setField(service, "dataSourceList", dataSourceList);
     }
     
+    @Test
+    void testInit() {
+        try {
+            MockEnvironment environment = new MockEnvironment();
+            EnvUtil.setEnvironment(environment);
+            environment.setProperty("db.num", "2");
+            environment.setProperty("db.user", "user");
+            environment.setProperty("db.password", "password");
+            environment.setProperty("db.url.0", "1.1.1.1");
+            environment.setProperty("db.url.1", "2.2.2.2");
+            environment.setProperty("db.pool.config.driverClassName",
+                    "com.alibaba.nacos.persistence.datasource.mock.MockDriver");
+            DatasourceConfiguration.setUseExternalDB(true);
+            ExternalDataSourceServiceImpl service1 = new ExternalDataSourceServiceImpl();
+            assertDoesNotThrow(service1::init);
+            assertEquals("", service1.getDataSourceType());
+            assertEquals("1.1.1.1", service1.getCurrentDbUrl());
+            assertNotNull(service1.getJdbcTemplate());
+            assertNotNull(service1.getTransactionTemplate());
+        } finally {
+            DatasourceConfiguration.setUseExternalDB(false);
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
+    @Test
+    void testInitInvalidConfig() {
+        try {
+            MockEnvironment environment = new MockEnvironment();
+            EnvUtil.setEnvironment(environment);
+            DatasourceConfiguration.setUseExternalDB(true);
+            ExternalDataSourceServiceImpl service1 = new ExternalDataSourceServiceImpl();
+            assertThrows(RuntimeException.class, service1::init);
+        } finally {
+            DatasourceConfiguration.setUseExternalDB(false);
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
+    @Test
+    void testReload() {
+        try {
+            MockEnvironment environment = new MockEnvironment();
+            EnvUtil.setEnvironment(environment);
+            environment.setProperty("db.num", "1");
+            environment.setProperty("db.user", "user");
+            environment.setProperty("db.password", "password");
+            environment.setProperty("db.url.0", "1.1.1.1");
+            environment.setProperty("db.pool.config.driverClassName",
+                    "com.alibaba.nacos.persistence.datasource.mock.MockDriver");
+            DatasourceConfiguration.setUseExternalDB(true);
+            HikariDataSource dataSource = mock(HikariDataSource.class);
+            JdbcTemplate oldJt = mock(JdbcTemplate.class);
+            ReflectionTestUtils.setField(service, "testJtList", Collections.singletonList(oldJt));
+            ReflectionTestUtils.setField(service, "dataSourceList", Collections.singletonList(dataSource));
+            assertDoesNotThrow(service::reload);
+            verify(jt).setDataSource(any(DataSource.class));
+            verify(oldJt).setDataSource(null);
+            verify(dataSource).close();
+        } finally {
+            DatasourceConfiguration.setUseExternalDB(false);
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
     @Test
     void testCheckMasterWritable() {
-        
         when(testMasterWritableJT.queryForObject(eq(" SELECT @@read_only "), eq(Integer.class))).thenReturn(0);
         assertTrue(service.checkMasterWritable());
     }
     
+    @Test
+    void testCheckMasterWritableWithoutResult() {
+        when(testMasterWritableJT.queryForObject(eq(" SELECT @@read_only "), eq(Integer.class))).thenReturn(null);
+        assertFalse(service.checkMasterWritable());
+    }
+    
+    @Test
+    void testCheckMasterWritableWithException() {
+        when(testMasterWritableJT.queryForObject(eq(" SELECT @@read_only "), eq(Integer.class))).thenThrow(
+                new CannotGetJdbcConnectionException("test"));
+        assertFalse(service.checkMasterWritable());
+    }
+    
     @Test
     void testGetCurrentDbUrl() {
-        
         HikariDataSource bds = new HikariDataSource();
         bds.setJdbcUrl("test.jdbc.url");
         when(jt.getDataSource()).thenReturn(bds);
-        
         assertEquals("test.jdbc.url", service.getCurrentDbUrl());
     }
     
+    @Test
+    void testGetCurrentDbUrlWithoutDatasource() {
+        assertEquals("", service.getCurrentDbUrl());
+    }
+    
     @Test
     void testGetHealth() {
-        
         List isHealthList = new ArrayList<>();
         ReflectionTestUtils.setField(service, "isHealthList", isHealthList);
         assertEquals("UP", service.getHealth());
     }
     
+    @Test
+    void testGetHealthWithMasterDown() {
+        HikariDataSource dataSource = mock(HikariDataSource.class);
+        when(dataSource.getJdbcUrl()).thenReturn("1.1.1.1");
+        ReflectionTestUtils.setField(service, "dataSourceList", Collections.singletonList(dataSource));
+        List isHealthList = new ArrayList<>();
+        isHealthList.add(Boolean.FALSE);
+        ReflectionTestUtils.setField(service, "isHealthList", isHealthList);
+        assertEquals("DOWN:1.1.1.1", service.getHealth());
+    }
+    
+    @Test
+    void testGetHealthWithSlaveDown() {
+        HikariDataSource dataSource = mock(HikariDataSource.class);
+        when(dataSource.getJdbcUrl()).thenReturn("2.2.2.2");
+        List dataSourceList = new ArrayList<>();
+        dataSourceList.add(null);
+        dataSourceList.add(dataSource);
+        ReflectionTestUtils.setField(service, "dataSourceList", dataSourceList);
+        List isHealthList = new ArrayList<>();
+        isHealthList.add(Boolean.TRUE);
+        isHealthList.add(Boolean.FALSE);
+        ReflectionTestUtils.setField(service, "isHealthList", isHealthList);
+        assertEquals("WARN:2.2.2.2", service.getHealth());
+    }
+    
     @Test
     void testCheckDbHealthTaskRun() {
-        
         List testJtList = new ArrayList<>();
         testJtList.add(jt);
         ReflectionTestUtils.setField(service, "testJtList", testJtList);
@@ -142,10 +259,37 @@ void testCheckDbHealthTaskRunWhenSqlException() {
         isHealthList.add(Boolean.FALSE);
         ReflectionTestUtils.setField(service, "isHealthList", isHealthList);
         
-        when(jt.queryForMap(anyString())).thenThrow(new UncategorizedSQLException("Expected exception", "", new SQLException()));
+        when(jt.queryForMap(anyString())).thenThrow(
+                new UncategorizedSQLException("Expected exception", "", new SQLException()));
+        service.new CheckDbHealthTask().run();
+        assertEquals(1, isHealthList.size());
+        assertFalse(isHealthList.get(0));
+    }
+    
+    @Test
+    void testCheckDbHealthTaskRunWhenSqlExceptionForSlave() {
+        List testJtList = new ArrayList<>();
+        testJtList.add(jt);
+        ReflectionTestUtils.setField(service, "testJtList", testJtList);
+        
+        List isHealthList = new ArrayList<>();
+        isHealthList.add(Boolean.FALSE);
+        ReflectionTestUtils.setField(service, "isHealthList", isHealthList);
+        ReflectionTestUtils.setField(service, "masterIndex", 1);
+        
+        when(jt.queryForMap(anyString())).thenThrow(
+                new UncategorizedSQLException("Expected exception", "", new SQLException()));
         service.new CheckDbHealthTask().run();
         assertEquals(1, isHealthList.size());
         assertFalse(isHealthList.get(0));
     }
     
+    @Test
+    void testMasterSelectWithException() {
+        HikariDataSource dataSource = mock(HikariDataSource.class);
+        ReflectionTestUtils.setField(service, "dataSourceList", Collections.singletonList(dataSource));
+        when(testMasterJT.update("DELETE FROM config_info WHERE data_id='com.alibaba.nacos.testMasterDB'")).thenThrow(
+                new NJdbcException("test"));
+        assertDoesNotThrow(() -> service.new SelectMasterTask().run());
+    }
 }
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/LocalDataSourceServiceImplTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/LocalDataSourceServiceImplTest.java
index dd8dd298fc4..b5a87d81a60 100644
--- a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/LocalDataSourceServiceImplTest.java
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/LocalDataSourceServiceImplTest.java
@@ -16,6 +16,9 @@
 
 package com.alibaba.nacos.persistence.datasource;
 
+import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException;
+import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration;
+import com.alibaba.nacos.sys.env.EnvUtil;
 import com.zaxxer.hikari.HikariDataSource;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -24,11 +27,22 @@
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.mock.env.MockEnvironment;
 import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.transaction.support.TransactionTemplate;
 
+import javax.sql.DataSource;
+import java.sql.SQLException;
+import java.util.concurrent.Callable;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
@@ -45,14 +59,79 @@ class LocalDataSourceServiceImplTest {
     
     @BeforeEach
     void setUp() {
+        DatasourceConfiguration.setUseExternalDB(false);
         service = new LocalDataSourceServiceImpl();
         ReflectionTestUtils.setField(service, "jt", jt);
         ReflectionTestUtils.setField(service, "tjt", tjt);
     }
     
+    @Test
+    void testInitWhenUseExternalDB() throws Exception {
+        try {
+            DatasourceConfiguration.setUseExternalDB(true);
+            EnvUtil.setEnvironment(null);
+            LocalDataSourceServiceImpl service1 = new LocalDataSourceServiceImpl();
+            assertDoesNotThrow(service1::init);
+        } finally {
+            DatasourceConfiguration.setUseExternalDB(false);
+        }
+    }
+    
+    @Test
+    void testInit() throws Exception {
+        try {
+            EnvUtil.setEnvironment(new MockEnvironment());
+            LocalDataSourceServiceImpl service1 = new LocalDataSourceServiceImpl();
+            assertDoesNotThrow(service1::init);
+            assertNotNull(service1.getJdbcTemplate());
+            assertNotNull(service1.getTransactionTemplate());
+            assertEquals("derby", service1.getDataSourceType());
+        } finally {
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
+    @Test
+    void testReloadWithNullDatasource() {
+        assertThrowsExactly(RuntimeException.class, service::reload, "datasource is null");
+    }
+    
+    @Test
+    void testReloadWithException() throws SQLException {
+        DataSource ds = mock(DataSource.class);
+        when(jt.getDataSource()).thenReturn(ds);
+        when(ds.getConnection()).thenThrow(new SQLException());
+        assertThrows(NacosRuntimeException.class, service::reload);
+    }
+    
+    @Test
+    void testCleanAndReopen() throws Exception {
+        try {
+            EnvUtil.setEnvironment(new MockEnvironment());
+            LocalDataSourceServiceImpl service1 = new LocalDataSourceServiceImpl();
+            assertDoesNotThrow(service1::init);
+            assertDoesNotThrow(service1::cleanAndReopenDerby);
+        } finally {
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
+    @Test
+    void testRestoreDerby() throws Exception {
+        try {
+            EnvUtil.setEnvironment(new MockEnvironment());
+            LocalDataSourceServiceImpl service1 = new LocalDataSourceServiceImpl();
+            assertDoesNotThrow(service1::init);
+            Callable callback = mock(Callable.class);
+            assertDoesNotThrow(() -> service1.restoreDerby(service1.getCurrentDbUrl(), callback));
+            verify(callback).call();
+        } finally {
+            EnvUtil.setEnvironment(null);
+        }
+    }
+    
     @Test
     void testGetDataSource() {
-        
         HikariDataSource dataSource = new HikariDataSource();
         dataSource.setJdbcUrl("test.jdbc.url");
         when(jt.getDataSource()).thenReturn(dataSource);
@@ -61,13 +140,11 @@ void testGetDataSource() {
     
     @Test
     void testCheckMasterWritable() {
-        
         assertTrue(service.checkMasterWritable());
     }
     
     @Test
     void testSetAndGetHealth() {
-        
         service.setHealthStatus("DOWN");
         assertEquals("DOWN", service.getHealth());
         
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockConnection.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockConnection.java
new file mode 100644
index 00000000000..d2088b62a66
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockConnection.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.datasource.mock;
+
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.CallableStatement;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.NClob;
+import java.sql.PreparedStatement;
+import java.sql.SQLClientInfoException;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Savepoint;
+import java.sql.Statement;
+import java.sql.Struct;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.Executor;
+
+public class MockConnection implements Connection {
+    
+    @Override
+    public String nativeSQL(String sql) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public void setAutoCommit(boolean autoCommit) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean getAutoCommit() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void commit() throws SQLException {
+    
+    }
+    
+    @Override
+    public void rollback() throws SQLException {
+    
+    }
+    
+    @Override
+    public void rollback(Savepoint savepoint) throws SQLException {
+    
+    }
+    
+    @Override
+    public void close() throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isClosed() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public DatabaseMetaData getMetaData() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void setReadOnly(boolean readOnly) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isReadOnly() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setCatalog(String catalog) throws SQLException {
+    
+    }
+    
+    @Override
+    public String getCatalog() throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public void setTransactionIsolation(int level) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getTransactionIsolation() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void clearWarnings() throws SQLException {
+    
+    }
+    
+    @Override
+    public Statement createStatement() throws SQLException {
+        return new MockStatement();
+    }
+    
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+        return new MockStatement();
+    }
+    
+    @Override
+    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
+            throws SQLException {
+        return new MockStatement();
+    }
+    
+    @Override
+    public CallableStatement prepareCall(String sql) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
+            int resultSetHoldability) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Map> getTypeMap() throws SQLException {
+        return Collections.emptyMap();
+    }
+    
+    @Override
+    public void setTypeMap(Map> map) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setHoldability(int holdability) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getHoldability() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public Savepoint setSavepoint() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Savepoint setSavepoint(String name) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+    
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql) throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+            throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
+            int resultSetHoldability) throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+        return new MockPreparedStatement();
+    }
+    
+    @Override
+    public Clob createClob() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Blob createBlob() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public NClob createNClob() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public SQLXML createSQLXML() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isValid(int timeout) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setClientInfo(String name, String value) throws SQLClientInfoException {
+    
+    }
+    
+    @Override
+    public void setClientInfo(Properties properties) throws SQLClientInfoException {
+    
+    }
+    
+    @Override
+    public String getClientInfo(String name) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public Properties getClientInfo() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void setSchema(String schema) throws SQLException {
+    
+    }
+    
+    @Override
+    public String getSchema() throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public void abort(Executor executor) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getNetworkTimeout() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public  T unwrap(Class iface) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isWrapperFor(Class iface) throws SQLException {
+        return false;
+    }
+}
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockDriver.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockDriver.java
new file mode 100644
index 00000000000..2cbb8a179d9
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockDriver.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.datasource.mock;
+
+import java.sql.Connection;
+import java.sql.Driver;
+import java.sql.DriverPropertyInfo;
+import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
+import java.util.Properties;
+import java.util.logging.Logger;
+
+public class MockDriver implements Driver {
+    
+    @Override
+    public Connection connect(String url, Properties info) throws SQLException {
+        return new MockConnection();
+    }
+    
+    @Override
+    public boolean acceptsURL(String url) throws SQLException {
+        return true;
+    }
+    
+    @Override
+    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
+        return new DriverPropertyInfo[0];
+    }
+    
+    @Override
+    public int getMajorVersion() {
+        return 0;
+    }
+    
+    @Override
+    public int getMinorVersion() {
+        return 0;
+    }
+    
+    @Override
+    public boolean jdbcCompliant() {
+        return false;
+    }
+    
+    @Override
+    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
+        return null;
+    }
+}
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockPreparedStatement.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockPreparedStatement.java
new file mode 100644
index 00000000000..f257a58a1ef
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockPreparedStatement.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.datasource.mock;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.ParameterMetaData;
+import java.sql.PreparedStatement;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+
+@SuppressWarnings("checkstyle:OverloadMethodsDeclarationOrder")
+public class MockPreparedStatement implements PreparedStatement {
+    
+    @Override
+    public ResultSet executeQuery() throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int executeUpdate() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setNull(int parameterIndex, int sqlType) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBoolean(int parameterIndex, boolean x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setByte(int parameterIndex, byte x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setShort(int parameterIndex, short x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setInt(int parameterIndex, int x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setLong(int parameterIndex, long x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setFloat(int parameterIndex, float x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setDouble(int parameterIndex, double x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setString(int parameterIndex, String x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBytes(int parameterIndex, byte[] x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setDate(int parameterIndex, Date x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setTime(int parameterIndex, Time x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void clearParameters() throws SQLException {
+    
+    }
+    
+    @Override
+    public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setObject(int parameterIndex, Object x) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean execute() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void addBatch() throws SQLException {
+    
+    }
+    
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setRef(int parameterIndex, Ref x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBlob(int parameterIndex, Blob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setClob(int parameterIndex, Clob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setArray(int parameterIndex, Array x) throws SQLException {
+    
+    }
+    
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setURL(int parameterIndex, URL x) throws SQLException {
+    
+    }
+    
+    @Override
+    public ParameterMetaData getParameterMetaData() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void setRowId(int parameterIndex, RowId x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNString(int parameterIndex, String value) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNClob(int parameterIndex, NClob value) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setClob(int parameterIndex, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setNClob(int parameterIndex, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public ResultSet executeQuery(String sql) throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int executeUpdate(String sql) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void close() throws SQLException {
+    
+    }
+    
+    @Override
+    public int getMaxFieldSize() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setMaxFieldSize(int max) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getMaxRows() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setMaxRows(int max) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setEscapeProcessing(boolean enable) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getQueryTimeout() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setQueryTimeout(int seconds) throws SQLException {
+    
+    }
+    
+    @Override
+    public void cancel() throws SQLException {
+    
+    }
+    
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void clearWarnings() throws SQLException {
+    
+    }
+    
+    @Override
+    public void setCursorName(String name) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean execute(String sql) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public ResultSet getResultSet() throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int getUpdateCount() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean getMoreResults() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setFetchDirection(int direction) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchDirection() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setFetchSize(int rows) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchSize() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getResultSetConcurrency() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getResultSetType() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void addBatch(String sql) throws SQLException {
+    
+    }
+    
+    @Override
+    public void clearBatch() throws SQLException {
+    
+    }
+    
+    @Override
+    public int[] executeBatch() throws SQLException {
+        return new int[0];
+    }
+    
+    @Override
+    public Connection getConnection() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean getMoreResults(int current) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public ResultSet getGeneratedKeys() throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean execute(String sql, String[] columnNames) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public int getResultSetHoldability() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean isClosed() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setPoolable(boolean poolable) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isPoolable() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void closeOnCompletion() throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isCloseOnCompletion() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public  T unwrap(Class iface) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isWrapperFor(Class iface) throws SQLException {
+        return false;
+    }
+}
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockResultSet.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockResultSet.java
new file mode 100644
index 00000000000..697885e8f8b
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockResultSet.java
@@ -0,0 +1,998 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.datasource.mock;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.math.BigDecimal;
+import java.net.URL;
+import java.sql.Array;
+import java.sql.Blob;
+import java.sql.Clob;
+import java.sql.Date;
+import java.sql.NClob;
+import java.sql.Ref;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.RowId;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.SQLXML;
+import java.sql.Statement;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.Calendar;
+import java.util.Map;
+
+@SuppressWarnings("checkstyle:OverloadMethodsDeclarationOrder")
+public class MockResultSet implements ResultSet {
+    
+    @Override
+    public boolean next() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void close() throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean wasNull() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public String getString(int columnIndex) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public boolean getBoolean(int columnIndex) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public byte getByte(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public short getShort(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getInt(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public long getLong(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public float getFloat(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public double getDouble(int columnIndex) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public byte[] getBytes(int columnIndex) throws SQLException {
+        return new byte[0];
+    }
+    
+    @Override
+    public Date getDate(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Time getTime(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Timestamp getTimestamp(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getAsciiStream(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getUnicodeStream(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getBinaryStream(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public String getString(String columnLabel) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public boolean getBoolean(String columnLabel) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public byte getByte(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public short getShort(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getInt(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public long getLong(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public float getFloat(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public double getDouble(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public byte[] getBytes(String columnLabel) throws SQLException {
+        return new byte[0];
+    }
+    
+    @Override
+    public Date getDate(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Time getTime(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Timestamp getTimestamp(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getAsciiStream(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getUnicodeStream(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public InputStream getBinaryStream(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void clearWarnings() throws SQLException {
+    
+    }
+    
+    @Override
+    public String getCursorName() throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public ResultSetMetaData getMetaData() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Object getObject(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Object getObject(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public int findColumn(String columnLabel) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public Reader getCharacterStream(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Reader getCharacterStream(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isBeforeFirst() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean isAfterLast() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean isFirst() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean isLast() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void beforeFirst() throws SQLException {
+    
+    }
+    
+    @Override
+    public void afterLast() throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean first() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean last() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public int getRow() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean absolute(int row) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean relative(int rows) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean previous() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setFetchDirection(int direction) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchDirection() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setFetchSize(int rows) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchSize() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getType() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getConcurrency() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean rowUpdated() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean rowInserted() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean rowDeleted() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void updateNull(int columnIndex) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBoolean(int columnIndex, boolean x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateByte(int columnIndex, byte x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateShort(int columnIndex, short x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateInt(int columnIndex, int x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateLong(int columnIndex, long x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateFloat(int columnIndex, float x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateDouble(int columnIndex, double x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateString(int columnIndex, String x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBytes(int columnIndex, byte[] x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateDate(int columnIndex, Date x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateTime(int columnIndex, Time x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateObject(int columnIndex, Object x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNull(String columnLabel) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBoolean(String columnLabel, boolean x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateByte(String columnLabel, byte x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateShort(String columnLabel, short x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateInt(String columnLabel, int x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateLong(String columnLabel, long x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateFloat(String columnLabel, float x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateDouble(String columnLabel, double x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateString(String columnLabel, String x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBytes(String columnLabel, byte[] x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateDate(String columnLabel, Date x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateTime(String columnLabel, Time x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateObject(String columnLabel, Object x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void insertRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public void deleteRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public void refreshRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public void cancelRowUpdates() throws SQLException {
+    
+    }
+    
+    @Override
+    public void moveToInsertRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public void moveToCurrentRow() throws SQLException {
+    
+    }
+    
+    @Override
+    public Statement getStatement() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Object getObject(int columnIndex, Map> map) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Ref getRef(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Blob getBlob(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Clob getClob(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Array getArray(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Object getObject(String columnLabel, Map> map) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Ref getRef(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Blob getBlob(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Clob getClob(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Array getArray(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Date getDate(int columnIndex, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Date getDate(String columnLabel, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Time getTime(int columnIndex, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Time getTime(String columnLabel, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public URL getURL(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public URL getURL(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void updateRef(int columnIndex, Ref x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateRef(String columnLabel, Ref x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(int columnIndex, Blob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(String columnLabel, Blob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(int columnIndex, Clob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(String columnLabel, Clob x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateArray(int columnIndex, Array x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateArray(String columnLabel, Array x) throws SQLException {
+    
+    }
+    
+    @Override
+    public RowId getRowId(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public RowId getRowId(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void updateRowId(int columnIndex, RowId x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateRowId(String columnLabel, RowId x) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getHoldability() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean isClosed() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void updateNString(int columnIndex, String nString) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNString(String columnLabel, String nString) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
+    
+    }
+    
+    @Override
+    public NClob getNClob(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public NClob getNClob(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public SQLXML getSQLXML(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public SQLXML getSQLXML(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
+    
+    }
+    
+    @Override
+    public String getNString(int columnIndex) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public String getNString(String columnLabel) throws SQLException {
+        return "";
+    }
+    
+    @Override
+    public Reader getNCharacterStream(int columnIndex) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public Reader getNCharacterStream(String columnLabel) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(int columnIndex, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(String columnLabel, Reader reader, long length) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(int columnIndex, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateClob(String columnLabel, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(int columnIndex, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public void updateNClob(String columnLabel, Reader reader) throws SQLException {
+    
+    }
+    
+    @Override
+    public  T getObject(int columnIndex, Class type) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public  T getObject(String columnLabel, Class type) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public  T unwrap(Class iface) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isWrapperFor(Class iface) throws SQLException {
+        return false;
+    }
+}
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockStatement.java b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockStatement.java
new file mode 100644
index 00000000000..7a0c8959b5c
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/datasource/mock/MockStatement.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.datasource.mock;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLWarning;
+import java.sql.Statement;
+
+public class MockStatement implements Statement {
+    
+    @Override
+    public ResultSet executeQuery(String sql) throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public void close() throws SQLException {
+    
+    }
+    
+    @Override
+    public int getMaxFieldSize() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setMaxFieldSize(int max) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getMaxRows() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setMaxRows(int max) throws SQLException {
+    
+    }
+    
+    @Override
+    public void setEscapeProcessing(boolean enable) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getQueryTimeout() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setQueryTimeout(int seconds) throws SQLException {
+    
+    }
+    
+    @Override
+    public void cancel() throws SQLException {
+    
+    }
+    
+    @Override
+    public SQLWarning getWarnings() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public void clearWarnings() throws SQLException {
+    
+    }
+    
+    @Override
+    public void setCursorName(String name) throws SQLException {
+    
+    }
+    
+    @Override
+    public ResultSet getResultSet() throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int getUpdateCount() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean getMoreResults() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean getMoreResults(int current) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setFetchDirection(int direction) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchDirection() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void setFetchSize(int rows) throws SQLException {
+    
+    }
+    
+    @Override
+    public int getFetchSize() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getResultSetConcurrency() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int getResultSetType() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public void addBatch(String sql) throws SQLException {
+    
+    }
+    
+    @Override
+    public void clearBatch() throws SQLException {
+    
+    }
+    
+    @Override
+    public int[] executeBatch() throws SQLException {
+        return new int[0];
+    }
+    
+    @Override
+    public Connection getConnection() throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public ResultSet getGeneratedKeys() throws SQLException {
+        return new MockResultSet();
+    }
+    
+    @Override
+    public int executeUpdate(String sql) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean execute(String sql) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public boolean execute(String sql, String[] columnNames) throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public int getResultSetHoldability() throws SQLException {
+        return 0;
+    }
+    
+    @Override
+    public boolean isClosed() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void setPoolable(boolean poolable) throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isPoolable() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public void closeOnCompletion() throws SQLException {
+    
+    }
+    
+    @Override
+    public boolean isCloseOnCompletion() throws SQLException {
+        return false;
+    }
+    
+    @Override
+    public  T unwrap(Class iface) throws SQLException {
+        return null;
+    }
+    
+    @Override
+    public boolean isWrapperFor(Class iface) throws SQLException {
+        return false;
+    }
+}
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/exception/NJdbcExceptionTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/exception/NJdbcExceptionTest.java
new file mode 100644
index 00000000000..0b2fd9ebdf9
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/exception/NJdbcExceptionTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.exception;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+class NJdbcExceptionTest {
+    
+    private Throwable cause;
+    
+    @BeforeEach
+    public void setUp() {
+        cause = new IllegalStateException("IllegalStateException");
+    }
+    
+    @Test
+    public void tesConstructorWithMessage() {
+        String msg = "test msg";
+        NJdbcException exception = new NJdbcException(msg);
+        assertEquals(msg, exception.getMessage());
+        assertNull(exception.getCause());
+        assertNull(exception.getOriginExceptionName());
+    }
+    
+    @Test
+    public void testConstructorWithMessageAndOriginExceptionName() {
+        String msg = "test msg";
+        String originExceptionName = "OriginException";
+        NJdbcException exception = new NJdbcException(msg, originExceptionName);
+        assertEquals(msg, exception.getMessage());
+        assertEquals(originExceptionName, exception.getOriginExceptionName());
+    }
+    
+    @Test
+    public void testConstructorWithMessageCauseAndOriginExceptionName() {
+        String msg = "test msg";
+        String originExceptionName = "OriginException";
+        NJdbcException exception = new NJdbcException(msg, cause, originExceptionName);
+        assertEquals("test msg; nested exception is java.lang.IllegalStateException: IllegalStateException", exception.getMessage());
+        assertSame(cause, exception.getCause());
+        assertEquals(originExceptionName, exception.getOriginExceptionName());
+    }
+    
+    @Test
+    public void testConstructorWithMessageAndCause() {
+        String msg = "test msg";
+        NJdbcException exception = new NJdbcException(msg, cause);
+        assertEquals("test msg; nested exception is java.lang.IllegalStateException: IllegalStateException", exception.getMessage());
+        assertSame(cause, exception.getCause());
+        assertNull(exception.getOriginExceptionName());
+    }
+    
+    @Test
+    public void testConstructorWithCause() {
+        NJdbcException exception = new NJdbcException(cause);
+        assertEquals("; nested exception is java.lang.IllegalStateException: IllegalStateException", exception.getMessage());
+        assertSame(cause, exception.getCause());
+        assertNull(exception.getOriginExceptionName());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/model/PageTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/model/PageTest.java
new file mode 100644
index 00000000000..fb5900026e0
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/model/PageTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.model;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class PageTest {
+    
+    @Test
+    void setPageItems() {
+        Page page = new Page<>();
+        assertEquals(0, page.getPageItems().size());
+        page.setPageItems(Collections.singletonList(new Object()));
+        assertEquals(1, page.getPageItems().size());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/model/event/EventTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/model/event/EventTest.java
new file mode 100644
index 00000000000..13d817a2c67
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/model/event/EventTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.model.event;
+
+import com.alibaba.nacos.common.utils.JacksonUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EventTest {
+    
+    @Test
+    void testDerbyImportEvent() {
+        DerbyImportEvent event = new DerbyImportEvent(true);
+        assertTrue(event.isFinished());
+    }
+    
+    @Test
+    void testDerbyLoadEvent() {
+        DerbyLoadEvent event = DerbyLoadEvent.INSTANCE;
+        assertNotNull(event);
+    }
+    
+    @Test
+    void testRaftDbErrorEvent() {
+        RaftDbErrorEvent event = new RaftDbErrorEvent(new Exception("test"));
+        assertNotNull(event);
+        String json = JacksonUtils.toJson(event);
+        RaftDbErrorEvent deserialized = JacksonUtils.toObj(json, RaftDbErrorEvent.class);
+        assertInstanceOf(Throwable.class, deserialized.getEx());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/RowMapperManagerTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/RowMapperManagerTest.java
new file mode 100644
index 00000000000..2ec50ca8300
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/RowMapperManagerTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class RowMapperManagerTest {
+    
+    @Mock
+    ResultSet resultSet;
+    
+    @Mock
+    ResultSetMetaData resultSetMetaData;
+    
+    @BeforeEach
+    void setUp() throws SQLException {
+    }
+    
+    @AfterEach
+    void tearDown() {
+        RowMapperManager.mapperMap.clear();
+        RowMapperManager.registerRowMapper(RowMapperManager.MAP_ROW_MAPPER.getClass().getCanonicalName(),
+                RowMapperManager.MAP_ROW_MAPPER);
+    }
+    
+    @Test
+    void testRegisterRowMapper() {
+        MockMapRowMapper mapper1 = new MockMapRowMapper();
+        RowMapperManager.registerRowMapper(MockMapRowMapperObj.class.getCanonicalName(), mapper1);
+        assertEquals(mapper1, RowMapperManager.getRowMapper(MockMapRowMapperObj.class.getCanonicalName()));
+        MockMapRowMapper mapper2 = new MockMapRowMapper();
+        RowMapperManager.registerRowMapper(MockMapRowMapperObj.class.getCanonicalName(), mapper2);
+        assertEquals(mapper2, RowMapperManager.getRowMapper(MockMapRowMapperObj.class.getCanonicalName()));
+    }
+    
+    @Test
+    void testDefaultRowMapper() throws SQLException {
+        when(resultSet.getObject(1)).thenReturn(1L);
+        when(resultSet.getObject(2)).thenReturn("test");
+        when(resultSet.getMetaData()).thenReturn(resultSetMetaData);
+        when(resultSetMetaData.getColumnCount()).thenReturn(2);
+        when(resultSetMetaData.getColumnLabel(1)).thenReturn("id");
+        when(resultSetMetaData.getColumnLabel(2)).thenReturn("name");
+        Map actual = RowMapperManager.MAP_ROW_MAPPER.mapRow(resultSet, 1);
+        assertEquals(1L, actual.get("id"));
+        assertEquals("test", actual.get("name"));
+    }
+    
+    private static class MockMapRowMapper implements RowMapper {
+        
+        @Override
+        public MockMapRowMapperObj mapRow(ResultSet rs, int rowNum) throws SQLException {
+            return new MockMapRowMapperObj();
+        }
+    }
+    
+    private static class MockMapRowMapperObj {
+    
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImplTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImplTest.java
new file mode 100644
index 00000000000..22c64c9d223
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedPaginationHelperImplTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded;
+
+import com.alibaba.nacos.persistence.model.Page;
+import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate;
+import com.alibaba.nacos.plugin.datasource.model.MapperResult;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class EmbeddedPaginationHelperImplTest {
+    
+    private static final String QUERY_SQL = "SELECT * FROM config_info LIMIT 1";
+    
+    private static final String QUERY_COUNT_SQL = "SELECT count(*) FROM config_info";
+    
+    @Mock
+    DatabaseOperate databaseOperate;
+    
+    @Mock
+    RowMapper rowMapper;
+    
+    EmbeddedPaginationHelperImpl embeddedPaginationHelper;
+    
+    @BeforeEach
+    void setUp() {
+        embeddedPaginationHelper = new EmbeddedPaginationHelperImpl<>(databaseOperate);
+    }
+    
+    @AfterEach
+    void tearDown() {
+    }
+    
+    @Test
+    void testFetchPageWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPage("", "", new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPage("", "", new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageWithoutResult() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1, null));
+    }
+    
+    @Test
+    void testFetchPageOnePage() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(1);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageFull() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(2);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageNotFull() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageNextPage() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 2, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMoreThanItemCount() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        Page actual = embeddedPaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 3, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit("", "", new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit("", "", new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithoutResult() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1, null));
+    }
+    
+    @Test
+    void testFetchPageLimitOnePage() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(1);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageFull() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(2);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageNotFull() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageNextPage() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 2, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMoreThanItemCount() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 3, 2,
+                rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginWithIllegalPageInfo() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginWithoutResult() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginPageOnePage() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(1);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageFull() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(2);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageNotFull() {
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageNextPage() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 2, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMoreThanItemCount() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(databaseOperate.queryOne(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        Page actual = embeddedPaginationHelper.fetchPageLimit(countMapper, queryMapper, 3, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitSimpleWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(QUERY_SQL, new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> embeddedPaginationHelper.fetchPageLimit(QUERY_SQL, new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitSimpleWithData() {
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(databaseOperate.queryMany(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = embeddedPaginationHelper.fetchPageLimit(QUERY_SQL, new Object[] {}, 3, 1, rowMapper);
+        assertEquals(0, actual.getTotalCount());
+        assertEquals(0, actual.getPageNumber());
+        assertEquals(0, actual.getPagesAvailable());
+        assertEquals(3, actual.getPageItems().size());
+    }
+    
+    @Test
+    void updateLimit() {
+        Object[] args = new Object[] {};
+        embeddedPaginationHelper.updateLimit(QUERY_SQL, args);
+        verify(databaseOperate).update(argThat(modifyRequests -> {
+            if (modifyRequests.size() != 1) {
+                return false;
+            }
+            if (!QUERY_SQL.equals(modifyRequests.get(0).getSql())) {
+                return false;
+            }
+            return 0 == modifyRequests.get(0).getArgs().length;
+        }));
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedStorageContextHolderTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedStorageContextHolderTest.java
new file mode 100644
index 00000000000..2774a6f72a2
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/EmbeddedStorageContextHolderTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded;
+
+import com.alibaba.nacos.persistence.repository.embedded.sql.ModifyRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class EmbeddedStorageContextHolderTest {
+    
+    private static final String TEST_SQL = "SELECT * FROM config_info";
+    
+    @BeforeEach
+    void setUp() {
+    }
+    
+    @AfterEach
+    void tearDown() {
+        EmbeddedStorageContextHolder.cleanAllContext();
+    }
+    
+    @Test
+    void testAddSqlContextRollbackOnUpdateFail() {
+        EmbeddedStorageContextHolder.addSqlContext(true, TEST_SQL, "test");
+        List requests = EmbeddedStorageContextHolder.getCurrentSqlContext();
+        assertEquals(1, requests.size());
+        assertEquals(TEST_SQL, requests.get(0).getSql());
+        assertEquals(0, requests.get(0).getExecuteNo());
+        assertEquals("test", requests.get(0).getArgs()[0]);
+        assertTrue(requests.get(0).isRollBackOnUpdateFail());
+    }
+    
+    @Test
+    void testPutExtendInfo() {
+        EmbeddedStorageContextHolder.putExtendInfo("testPutExtendInfo", "test_value");
+        assertTrue(EmbeddedStorageContextHolder.containsExtendInfo("testPutExtendInfo"));
+        assertEquals("test_value", EmbeddedStorageContextHolder.getCurrentExtendInfo().get("testPutExtendInfo"));
+    }
+    
+    @Test
+    void testPutAllExtendInfo() {
+        Map map = new HashMap<>();
+        map.put("testPutAllExtendInfo", "test_value");
+        EmbeddedStorageContextHolder.putAllExtendInfo(map);
+        assertTrue(EmbeddedStorageContextHolder.containsExtendInfo("testPutAllExtendInfo"));
+        assertEquals("test_value", EmbeddedStorageContextHolder.getCurrentExtendInfo().get("testPutAllExtendInfo"));
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/hook/EmbeddedApplyHookHolderTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/hook/EmbeddedApplyHookHolderTest.java
new file mode 100644
index 00000000000..8e26d170fe9
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/hook/EmbeddedApplyHookHolderTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded.hook;
+
+import com.alibaba.nacos.consistency.entity.WriteRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class EmbeddedApplyHookHolderTest {
+    
+    Set cached;
+    
+    @BeforeEach
+    void setUp() {
+        cached = new HashSet<>(EmbeddedApplyHookHolder.getInstance().getAllHooks());
+        EmbeddedApplyHookHolder.getInstance().getAllHooks().clear();
+    }
+    
+    @AfterEach
+    void tearDown() {
+        EmbeddedApplyHookHolder.getInstance().getAllHooks().clear();
+        EmbeddedApplyHookHolder.getInstance().getAllHooks().addAll(cached);
+    }
+    
+    @Test
+    void testRegister() {
+        assertEquals(0, EmbeddedApplyHookHolder.getInstance().getAllHooks().size());
+        EmbeddedApplyHook mockHook = new EmbeddedApplyHook() {
+            @Override
+            public void afterApply(WriteRequest log) {
+            
+            }
+        };
+        assertEquals(1, EmbeddedApplyHookHolder.getInstance().getAllHooks().size());
+        assertEquals(mockHook, EmbeddedApplyHookHolder.getInstance().getAllHooks().iterator().next());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/BaseDatabaseOperateTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/BaseDatabaseOperateTest.java
new file mode 100644
index 00000000000..538aed780b3
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/BaseDatabaseOperateTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded.operate;
+
+import com.alibaba.nacos.persistence.exception.NJdbcException;
+import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder;
+import com.alibaba.nacos.persistence.repository.embedded.sql.ModifyRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.jdbc.BadSqlGrammarException;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.transaction.IllegalTransactionStateException;
+import org.springframework.transaction.support.SimpleTransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionTemplate;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class BaseDatabaseOperateTest {
+    
+    private static final String TEST_SQL = "UPDATE config_info SET data_id = 'test' WHERE id = ?;";
+    
+    private static final Object[] ARGS = new Object[1];
+    
+    @Spy
+    BaseDatabaseOperate baseDatabaseOperate;
+    
+    @Mock
+    BiConsumer consumer;
+    
+    @Mock
+    TransactionTemplate transactionTemplate;
+    
+    @Mock
+    JdbcTemplate jdbcTemplate;
+    
+    @AfterEach
+    void tearDown() {
+        EmbeddedStorageContextHolder.cleanAllContext();
+    }
+    
+    @Test
+    void testUpdateSuccessWithConsumer() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenReturn(1);
+        assertTrue(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer).accept(eq(Boolean.TRUE), eq(null));
+    }
+    
+    @Test
+    void testUpdateSuccessWithConsumerAndRollback() {
+        List requests = mockRequest(true);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenReturn(1);
+        assertTrue(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer).accept(eq(Boolean.TRUE), eq(null));
+    }
+    
+    @Test
+    void testUpdateFailedWithConsumerAndRollback() {
+        List requests = mockRequest(true);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenReturn(0);
+        assertFalse(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer).accept(eq(Boolean.FALSE), any(IllegalTransactionStateException.class));
+    }
+    
+    @Test
+    void testUpdateFailedWithConsumerAndBadSqlException() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenThrow(
+                new BadSqlGrammarException("test", TEST_SQL, new SQLException("test")));
+        assertFalse(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer).accept(eq(Boolean.FALSE), any(BadSqlGrammarException.class));
+    }
+    
+    @Test
+    void testUpdateWithConsumerAndBadSqlException() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenThrow(
+                new BadSqlGrammarException("test", TEST_SQL, new SQLException("test")));
+        assertFalse(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer).accept(eq(Boolean.FALSE), any(BadSqlGrammarException.class));
+    }
+    
+    @Test
+    void testUpdateWithConsumerAndCannotGetJdbcConnectionException() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenThrow(new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class,
+                () -> baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer, never()).accept(any(), any());
+    }
+    
+    @Test
+    void testUpdateWithConsumerAndDataAccessException() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenThrow(new NJdbcException("test"));
+        assertThrows(NJdbcException.class,
+                () -> baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, consumer));
+        verify(consumer, never()).accept(any(), any());
+    }
+    
+    @Test
+    void testUpdateSuccessWithoutConsumer() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenReturn(1);
+        assertTrue(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, null));
+    }
+    
+    @Test
+    void testUpdateFailedWithoutConsumerAndRollback() {
+        List requests = mockRequest(true);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenReturn(0);
+        assertFalse(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, null));
+    }
+    
+    @Test
+    void testUpdateWithoutConsumerAndDataIntegrityViolationException() {
+        List requests = mockRequest(false);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).then(invocationOnMock -> {
+            TransactionCallback callback = invocationOnMock.getArgument(0, TransactionCallback.class);
+            return callback.doInTransaction(new SimpleTransactionStatus());
+        });
+        when(jdbcTemplate.update(TEST_SQL, ARGS)).thenThrow(new DataIntegrityViolationException("test"));
+        assertFalse(baseDatabaseOperate.update(transactionTemplate, jdbcTemplate, requests, null));
+    }
+    
+    private List mockRequest(boolean rollback) {
+        ModifyRequest modifyRequest1 = new ModifyRequest();
+        modifyRequest1.setSql(TEST_SQL);
+        modifyRequest1.setArgs(ARGS);
+        modifyRequest1.setRollBackOnUpdateFail(rollback);
+        List modifyRequests = new ArrayList<>();
+        modifyRequests.add(modifyRequest1);
+        return modifyRequests;
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperateTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperateTest.java
new file mode 100644
index 00000000000..05fb706586b
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/DatabaseOperateTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded.operate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Spy;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class DatabaseOperateTest {
+    
+    @Spy
+    private DatabaseOperate databaseOperate;
+    
+    @BeforeEach
+    void setUp() {
+    }
+    
+    @Test
+    void testDefaultUpdate() {
+        when(databaseOperate.update(anyList(), eq(null))).thenReturn(true);
+        assertTrue(databaseOperate.update(Collections.emptyList()));
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/StandaloneDatabaseOperateImplTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/StandaloneDatabaseOperateImplTest.java
index 6f10f72518f..f2ecbac620f 100644
--- a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/StandaloneDatabaseOperateImplTest.java
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/operate/StandaloneDatabaseOperateImplTest.java
@@ -17,6 +17,7 @@
 package com.alibaba.nacos.persistence.repository.embedded.operate;
 
 import com.alibaba.nacos.common.model.RestResult;
+import com.alibaba.nacos.persistence.exception.NJdbcException;
 import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder;
 import com.alibaba.nacos.persistence.repository.embedded.sql.ModifyRequest;
 import com.alibaba.nacos.sys.env.EnvUtil;
@@ -25,10 +26,10 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.InjectMocks;
 import org.mockito.Mock;
-import org.mockito.Spy;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
+import org.springframework.jdbc.CannotGetJdbcConnectionException;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.RowMapper;
 import org.springframework.mock.env.MockEnvironment;
@@ -36,27 +37,30 @@
 import org.springframework.transaction.support.TransactionCallback;
 import org.springframework.transaction.support.TransactionTemplate;
 
+import java.io.File;
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
 class StandaloneDatabaseOperateImplTest {
     
-    @Spy
-    @InjectMocks
     private StandaloneDatabaseOperateImpl operate;
     
     @Mock
@@ -71,9 +75,6 @@ class StandaloneDatabaseOperateImplTest {
     @Mock
     private BiConsumer biConsumer;
     
-    /*   @Mock
-       private File file;
-       */
     @Mock
     private TransactionTemplate transactionTemplate;
     
@@ -86,6 +87,8 @@ static void beforeAll() {
     
     @BeforeEach
     void setUp() {
+        operate = new StandaloneDatabaseOperateImpl();
+        operate.init();
         ReflectionTestUtils.setField(operate, "jdbcTemplate", jdbcTemplate);
         ReflectionTestUtils.setField(operate, "transactionTemplate", transactionTemplate);
     }
@@ -93,6 +96,7 @@ void setUp() {
     @AfterAll
     static void afterAll() {
         EnvUtil.setEnvironment(null);
+        EmbeddedStorageContextHolder.cleanAllContext();
     }
     
     @Test
@@ -102,6 +106,14 @@ void testQueryOne1() {
         Long num = 1L;
         when(jdbcTemplate.queryForObject(sql, clazz)).thenReturn(num);
         assertEquals(operate.queryOne(sql, clazz), (Long) 1L);
+        when(jdbcTemplate.queryForObject(sql, clazz)).thenThrow(new IncorrectResultSizeDataAccessException("test", 1));
+        assertNull(operate.queryOne(sql, clazz));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(sql, clazz)).thenThrow(new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryOne(sql, clazz));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(sql, clazz)).thenThrow(new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryOne(sql, clazz));
     }
     
     @Test
@@ -114,6 +126,17 @@ void testQueryOne2() {
         Object[] args = new Object[] {configInfo.getId(), configInfo.getDataId(), configInfo.getGroup()};
         when(jdbcTemplate.queryForObject(sql, args, MockConfigInfo.class)).thenReturn(configInfo);
         assertEquals(operate.queryOne(sql, args, MockConfigInfo.class), configInfo);
+        when(jdbcTemplate.queryForObject(sql, args, MockConfigInfo.class)).thenThrow(
+                new IncorrectResultSizeDataAccessException("test", 1));
+        assertNull(operate.queryOne(sql, args, MockConfigInfo.class));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(sql, args, MockConfigInfo.class)).thenThrow(
+                new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryOne(sql, args, MockConfigInfo.class));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(sql, args, MockConfigInfo.class)).thenThrow(
+                new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryOne(sql, args, MockConfigInfo.class));
     }
     
     @Test
@@ -126,6 +149,17 @@ void testQueryOne3() {
         Object[] args = new Object[] {configInfo.getId(), configInfo.getDataId(), configInfo.getGroup()};
         when(jdbcTemplate.queryForObject(eq(sql), eq(args), any(RowMapper.class))).thenReturn(configInfo);
         assertEquals(operate.queryOne(sql, args, rowMapper), configInfo);
+        when(jdbcTemplate.queryForObject(eq(sql), eq(args), any(RowMapper.class))).thenThrow(
+                new IncorrectResultSizeDataAccessException("test", 1));
+        assertNull(operate.queryOne(sql, args, rowMapper));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(eq(sql), eq(args), any(RowMapper.class))).thenThrow(
+                new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryOne(sql, args, rowMapper));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForObject(eq(sql), eq(args), any(RowMapper.class))).thenThrow(
+                new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryOne(sql, args, rowMapper));
     }
     
     @Test
@@ -174,6 +208,13 @@ void testQueryMany1() {
         configInfos.add(configInfo2);
         when(jdbcTemplate.query(eq(sql), eq(args), any(RowMapper.class))).thenReturn(configInfos);
         assertEquals(configInfos, operate.queryMany(sql, args, rowMapper));
+        when(jdbcTemplate.query(eq(sql), eq(args), any(RowMapper.class))).thenThrow(
+                new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryMany(sql, args, rowMapper));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.query(eq(sql), eq(args), any(RowMapper.class))).thenThrow(
+                new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryMany(sql, args, rowMapper));
     }
     
     @Test
@@ -197,6 +238,11 @@ void testQueryMany2() {
         
         when(jdbcTemplate.queryForList(sql, args)).thenReturn(resultList);
         assertEquals(operate.queryMany(sql, args), resultList);
+        when(jdbcTemplate.queryForList(sql, args)).thenThrow(new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryMany(sql, args));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForList(sql, args)).thenThrow(new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryMany(sql, args));
     }
     
     @Test
@@ -211,6 +257,17 @@ void testQueryMany3() {
         Class clazz = dataId1.getClass();
         when(jdbcTemplate.queryForList(sql, args, clazz)).thenReturn(resultList);
         assertEquals(operate.queryMany(sql, args, clazz), resultList);
+        when(jdbcTemplate.queryForList(sql, args, clazz)).thenThrow(
+                new IncorrectResultSizeDataAccessException("test", 1));
+        assertNull(operate.queryMany(sql, args, clazz));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForList(sql, args, clazz)).thenThrow(new CannotGetJdbcConnectionException("test"));
+        assertThrows(CannotGetJdbcConnectionException.class, () -> operate.queryMany(sql, args, clazz));
+        reset(jdbcTemplate);
+        when(jdbcTemplate.queryForList(sql, args, clazz)).thenThrow(
+                new NJdbcException("test", "OriginalExceptionName"));
+        assertThrows(NJdbcException.class, () -> operate.queryMany(sql, args, clazz));
+        
     }
     
     @Test
@@ -265,12 +322,41 @@ void testQueryMany6() {
     }
     
     @Test
-    void testDataImport() throws ExecutionException, InterruptedException {
-        RestResult errorResult = RestResult.builder().withCode(500).withMsg("null").withData(null).build();
-        CompletableFuture> errorFuture = new CompletableFuture<>();
-        errorFuture.complete(errorResult);
-        doReturn(errorFuture).when(operate).dataImport(null);
-        assertEquals(operate.dataImport(null).get(), errorResult);
+    void testDataImportSuccess() throws ExecutionException, InterruptedException, URISyntaxException {
+        File file = new File(getClass().getClassLoader().getResource("META-INF/test-derby-import.sql").toURI());
+        int[] executeResult = new int[21];
+        for (int i = executeResult.length - 1; i > executeResult.length - 6; i--) {
+            executeResult[i] = 1;
+        }
+        when(jdbcTemplate.batchUpdate(any(String[].class))).thenReturn(executeResult);
+        CompletableFuture> result = operate.dataImport(file);
+        TimeUnit.MILLISECONDS.sleep(1000L);
+        assertTrue(result.get().ok());
+        assertEquals(200, result.get().getCode());
+    }
+    
+    @Test
+    void testDataImportFailed() throws ExecutionException, InterruptedException, URISyntaxException {
+        File file = new File(getClass().getClassLoader().getResource("META-INF/test-derby-import.sql").toURI());
+        int[] executeResult = new int[5];
+        when(jdbcTemplate.batchUpdate(any(String[].class))).thenReturn(executeResult);
+        CompletableFuture> result = operate.dataImport(file);
+        TimeUnit.MILLISECONDS.sleep(1000L);
+        assertFalse(result.get().ok());
+        assertEquals(500, result.get().getCode());
+        assertNull(result.get().getMessage());
+    }
+    
+    @Test
+    void testDataImportException() throws ExecutionException, InterruptedException, URISyntaxException {
+        File file = new File(getClass().getClassLoader().getResource("META-INF/test-derby-import.sql").toURI());
+        when(jdbcTemplate.batchUpdate(any(String[].class))).thenThrow(new NJdbcException("test import failed"));
+        CompletableFuture> result = operate.dataImport(file);
+        TimeUnit.MILLISECONDS.sleep(1000L);
+        assertFalse(result.get().ok());
+        assertEquals(500, result.get().getCode());
+        assertEquals("com.alibaba.nacos.persistence.exception.NJdbcException: test import failed",
+                result.get().getMessage());
     }
     
     @Test
@@ -341,6 +427,15 @@ void testBlockUpdate2() {
         assertTrue(operate.blockUpdate(biConsumer));
     }
     
+    @Test
+    void testBlockUpdateWithException() {
+        String sql = "UPDATE config_info SET data_id = 'test' WHERE id = 1;";
+        EmbeddedStorageContextHolder.addSqlContext(sql);
+        when(transactionTemplate.execute(any(TransactionCallback.class))).thenThrow(new NJdbcException("test"));
+        assertThrows(NJdbcException.class, () -> operate.blockUpdate(biConsumer));
+        assertTrue(EmbeddedStorageContextHolder.getCurrentSqlContext().isEmpty());
+    }
+    
     @Test
     void testDoDataImport() {
         List modifyRequests = new ArrayList<>();
@@ -353,15 +448,4 @@ void testDoDataImport() {
         when(tempJdbcTemplate.batchUpdate(sql)).thenReturn(new int[] {1});
         assertTrue(operate.doDataImport(tempJdbcTemplate, modifyRequests));
     }
-    
-    @Test
-    void testFutureUpdate() throws ExecutionException, InterruptedException {
-        String sql = "SELECT 1";
-        EmbeddedStorageContextHolder.addSqlContext(sql);
-        CompletableFuture future = new CompletableFuture<>();
-        future.complete(true);
-        doAnswer((invocationOnMock) -> null).when(operate).futureUpdate();
-        when(operate.futureUpdate()).thenReturn(future);
-        assertTrue(operate.futureUpdate().get());
-    }
 }
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/ModifyRequestTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/ModifyRequestTest.java
new file mode 100644
index 00000000000..af885cf64df
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/ModifyRequestTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded.sql;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ModifyRequestTest {
+    
+    @Test
+    void testToString() {
+        ModifyRequest request = new ModifyRequest();
+        assertEquals("SQL{executeNo=0, sql='null', args=null}", request.toString());
+        request.setRollBackOnUpdateFail(true);
+        request.setArgs(new Object[] {1, "test"});
+        request.setSql("SELECT 1");
+        request.setExecuteNo(1);
+        assertEquals("SQL{executeNo=1, sql='SELECT 1', args=[1, test]}", request.toString());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/SelectRequestTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/SelectRequestTest.java
new file mode 100644
index 00000000000..20ff0d66441
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/embedded/sql/SelectRequestTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.embedded.sql;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class SelectRequestTest {
+    
+    @Test
+    void testBuild() {
+        SelectRequest.SelectRequestBuilder builder = SelectRequest.builder();
+        builder.queryType(QueryType.QUERY_ONE_WITH_MAPPER_WITH_ARGS);
+        builder.sql("SELECT 1");
+        builder.args(new Object[] {"1"});
+        builder.className(Integer.class.getCanonicalName());
+        SelectRequest request = builder.build();
+        assertEquals(QueryType.QUERY_ONE_WITH_MAPPER_WITH_ARGS, request.getQueryType());
+        assertEquals("SELECT 1", request.getSql());
+        assertEquals("java.lang.Integer", request.getClassName());
+        assertEquals(1, request.getArgs().length);
+        assertEquals("1", request.getArgs()[0]);
+        assertEquals("SelectRequest{queryType=0, sql='SELECT 1', args=[1], className='java.lang.Integer'}",
+                request.toString());
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImplTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImplTest.java
new file mode 100644
index 00000000000..c36bc32f677
--- /dev/null
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/repository/extrnal/ExternalStoragePaginationHelperImplTest.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.alibaba.nacos.persistence.repository.extrnal;
+
+import com.alibaba.nacos.persistence.model.Page;
+import com.alibaba.nacos.plugin.datasource.model.MapperResult;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class ExternalStoragePaginationHelperImplTest {
+    
+    private static final String QUERY_SQL = "SELECT * FROM config_info LIMIT 1";
+    
+    private static final String QUERY_COUNT_SQL = "SELECT count(*) FROM config_info";
+    
+    @Mock
+    JdbcTemplate jdbcTemplate;
+    
+    @Mock
+    RowMapper rowMapper;
+    
+    ExternalStoragePaginationHelperImpl externalStoragePaginationHelper;
+    
+    @BeforeEach
+    void setUp() {
+        externalStoragePaginationHelper = new ExternalStoragePaginationHelperImpl<>(jdbcTemplate);
+    }
+    
+    @AfterEach
+    void tearDown() {
+    }
+    
+    @Test
+    void testFetchPageWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPage("", "", new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPage("", "", new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageWithoutResult() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                        null));
+    }
+    
+    @Test
+    void testFetchPageOnePage() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(1);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1,
+                1, rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageFull() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(2);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1,
+                1, rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageNotFull() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1,
+                2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMorePageNextPage() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 2,
+                2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageMoreThanItemCount() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        Page actual = externalStoragePaginationHelper.fetchPage(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 3,
+                2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit("", "", new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit("", "", new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithoutResult() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL, new Object[] {}, 1, 1,
+                        null));
+    }
+    
+    @Test
+    void testFetchPageLimitOnePage() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(1);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL,
+                new Object[] {}, 1, 1, rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageFull() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(2);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL,
+                new Object[] {}, 1, 1, rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageNotFull() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL,
+                new Object[] {}, 1, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMorePageNextPage() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL,
+                new Object[] {}, 2, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitMoreThanItemCount() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, Integer.class)).thenReturn(3);
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_COUNT_SQL, QUERY_SQL,
+                new Object[] {}, 3, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginWithIllegalPageInfo() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginWithoutResult() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(null);
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, null));
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginPageOnePage() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(1);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, rowMapper);
+        assertEquals(1, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(1, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageFull() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(2);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 1, rowMapper);
+        assertEquals(2, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageNotFull() {
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 1, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(1, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(2, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMorePageNextPage() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(
+                Collections.singletonList(new Object()));
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 2, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(2, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(1, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitWithPluginMoreThanItemCount() {
+        MapperResult countMapper = new MapperResult(QUERY_COUNT_SQL, new ArrayList<>());
+        MapperResult queryMapper = new MapperResult(QUERY_SQL, new ArrayList<>());
+        when(jdbcTemplate.queryForObject(QUERY_COUNT_SQL, new Object[] {}, Integer.class)).thenReturn(3);
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(countMapper, queryMapper, 3, 2, rowMapper);
+        assertEquals(3, actual.getTotalCount());
+        assertEquals(3, actual.getPageNumber());
+        assertEquals(2, actual.getPagesAvailable());
+        assertEquals(0, actual.getPageItems().size());
+    }
+    
+    @Test
+    void testFetchPageLimitSimpleWithIllegalPageInfo() {
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(QUERY_SQL, new Object[] {}, 0, 0, null));
+        assertThrows(IllegalArgumentException.class,
+                () -> externalStoragePaginationHelper.fetchPageLimit(QUERY_SQL, new Object[] {}, 1, 0, null));
+    }
+    
+    @Test
+    void testFetchPageLimitSimpleWithData() {
+        List pageItems = new LinkedList<>();
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        pageItems.add(new Object());
+        when(jdbcTemplate.query(QUERY_SQL, new Object[] {}, rowMapper)).thenReturn(pageItems);
+        Page actual = externalStoragePaginationHelper.fetchPageLimit(QUERY_SQL, new Object[]{}, 3, 1, rowMapper);
+        assertEquals(0, actual.getTotalCount());
+        assertEquals(0, actual.getPageNumber());
+        assertEquals(0, actual.getPagesAvailable());
+        assertEquals(3, actual.getPageItems().size());
+    }
+    
+    @Test
+    void updateLimit() {
+        Object[] args = new Object[] {};
+        externalStoragePaginationHelper.updateLimit(QUERY_SQL, args);
+        verify(jdbcTemplate).update(QUERY_SQL, args);
+    }
+}
\ No newline at end of file
diff --git a/persistence/src/test/java/com/alibaba/nacos/persistence/utils/PersistenceExecutorTest.java b/persistence/src/test/java/com/alibaba/nacos/persistence/utils/PersistenceExecutorTest.java
index 0aaa90a6518..d530c13d777 100644
--- a/persistence/src/test/java/com/alibaba/nacos/persistence/utils/PersistenceExecutorTest.java
+++ b/persistence/src/test/java/com/alibaba/nacos/persistence/utils/PersistenceExecutorTest.java
@@ -40,4 +40,21 @@ void testExecuteEmbeddedDump() throws InterruptedException {
         
     }
     
+    @Test
+    void testScheduleTask() throws InterruptedException {
+        final AtomicInteger count = new AtomicInteger(0);
+        Runnable runnable = count::incrementAndGet;
+        PersistenceExecutor.scheduleTask(runnable, 0, 300, TimeUnit.MILLISECONDS);
+        TimeUnit.MILLISECONDS.sleep(200);
+        assertEquals(1, count.get());
+    }
+    
+    @Test
+    void testExecuteSnapshot() throws InterruptedException {
+        final AtomicInteger count = new AtomicInteger(0);
+        Runnable runnable = count::incrementAndGet;
+        PersistenceExecutor.executeSnapshot(runnable);
+        TimeUnit.MILLISECONDS.sleep(200);
+        assertEquals(1, count.get());
+    }
 }
\ No newline at end of file
diff --git a/persistence/src/test/resources/META-INF/derby-schema.sql b/persistence/src/test/resources/META-INF/derby-schema.sql
new file mode 100644
index 00000000000..601d38ab2e6
--- /dev/null
+++ b/persistence/src/test/resources/META-INF/derby-schema.sql
@@ -0,0 +1,227 @@
+build/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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.
+ */
+
+CREATE SCHEMA nacos AUTHORIZATION nacos;
+
+CREATE TABLE config_info (
+  id bigint NOT NULL generated by default as identity,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) default '',
+  app_name varchar(128),
+  content CLOB,
+  md5 varchar(32) DEFAULT NULL,
+  gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  src_user varchar(128) DEFAULT NULL,
+  src_ip varchar(50) DEFAULT NULL,
+  c_desc varchar(256) DEFAULT NULL,
+  c_use varchar(64) DEFAULT NULL,
+  effect varchar(64) DEFAULT NULL,
+  type varchar(64) DEFAULT NULL,
+  c_schema LONG VARCHAR DEFAULT NULL,
+  encrypted_data_key LONG VARCHAR DEFAULT NULL,
+  constraint configinfo_id_key PRIMARY KEY (id),
+  constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id));
+
+CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id);
+CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id);
+CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id);
+
+CREATE TABLE his_config_info (
+  id bigint NOT NULL,
+  nid bigint NOT NULL generated by default as identity,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) default '',
+  app_name varchar(128),
+  content CLOB,
+  md5 varchar(32) DEFAULT NULL,
+  gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000',
+  gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000',
+  src_user varchar(128),
+  src_ip varchar(50) DEFAULT NULL,
+  op_type char(10) DEFAULT NULL,
+  encrypted_data_key LONG VARCHAR DEFAULT NULL,
+  constraint hisconfiginfo_nid_key PRIMARY KEY (nid));
+
+CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id);
+CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create);
+CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified);
+
+
+CREATE TABLE config_info_beta (
+  id bigint NOT NULL generated by default as identity,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) default '',
+  app_name varchar(128),
+  content CLOB,
+  beta_ips varchar(1024),
+  md5 varchar(32) DEFAULT NULL,
+  gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  src_user varchar(128),
+  src_ip varchar(50) DEFAULT NULL,
+  encrypted_data_key LONG VARCHAR DEFAULT NULL,
+  constraint configinfobeta_id_key PRIMARY KEY (id),
+  constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id));
+
+CREATE TABLE config_info_tag (
+  id bigint NOT NULL generated by default as identity,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) default '',
+  tag_id varchar(128) NOT NULL,
+  app_name varchar(128),
+  content CLOB,
+  md5 varchar(32) DEFAULT NULL,
+  gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  src_user varchar(128),
+  src_ip varchar(50) DEFAULT NULL,
+  constraint configinfotag_id_key PRIMARY KEY (id),
+  constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id));
+
+CREATE TABLE config_info_aggr (
+  id bigint NOT NULL generated by default as identity,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) default '',
+  datum_id varchar(255) NOT NULL,
+  app_name varchar(128),
+  content CLOB,
+  gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00',
+  constraint configinfoaggr_id_key PRIMARY KEY (id),
+  constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id));
+
+CREATE TABLE app_list (
+ id bigint NOT NULL generated by default as identity,
+ app_name varchar(128) NOT NULL,
+ is_dynamic_collect_disabled smallint DEFAULT 0,
+ last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0',
+ sub_info_lock_owner varchar(128),
+ sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0',
+ constraint applist_id_key PRIMARY KEY (id),
+ constraint uk_appname UNIQUE (app_name));
+
+CREATE TABLE app_configdata_relation_subs (
+  id bigint NOT NULL generated by default as identity,
+  app_name varchar(128) NOT NULL,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  gmt_modified timestamp DEFAULT '2010-05-05 00:00:00',
+  constraint configdatarelationsubs_id_key PRIMARY KEY (id),
+  constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id));
+
+
+CREATE TABLE app_configdata_relation_pubs (
+  id bigint NOT NULL generated by default as identity,
+  app_name varchar(128) NOT NULL,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  gmt_modified timestamp DEFAULT '2010-05-05 00:00:00',
+  constraint configdatarelationpubs_id_key PRIMARY KEY (id),
+  constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id));
+
+CREATE TABLE config_tags_relation (
+  id bigint NOT NULL,
+  tag_name varchar(128) NOT NULL,
+  tag_type varchar(64) DEFAULT NULL,
+  data_id varchar(255) NOT NULL,
+  group_id varchar(128) NOT NULL,
+  tenant_id varchar(128) DEFAULT '',
+  nid bigint NOT NULL generated by default as identity,
+  constraint config_tags_id_key PRIMARY KEY (nid),
+  constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type));
+
+CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id);
+
+CREATE TABLE group_capacity (
+  id bigint NOT NULL generated by default as identity,
+  group_id varchar(128) DEFAULT '',
+  quota int DEFAULT 0,
+  usage int DEFAULT 0,
+  max_size int DEFAULT 0,
+  max_aggr_count int DEFAULT 0,
+  max_aggr_size int DEFAULT 0,
+  max_history_count int DEFAULT 0,
+  gmt_create timestamp DEFAULT '2010-05-05 00:00:00',
+  gmt_modified timestamp DEFAULT '2010-05-05 00:00:00',
+  constraint group_capacity_id_key PRIMARY KEY (id),
+  constraint uk_group_id UNIQUE (group_id));
+
+CREATE TABLE tenant_capacity (
+  id bigint NOT NULL generated by default as identity,
+  tenant_id varchar(128) DEFAULT '',
+  quota int DEFAULT 0,
+  usage int DEFAULT 0,
+  max_size int DEFAULT 0,
+  max_aggr_count int DEFAULT 0,
+  max_aggr_size int DEFAULT 0,
+  max_history_count int DEFAULT 0,
+  gmt_create timestamp DEFAULT '2010-05-05 00:00:00',
+  gmt_modified timestamp DEFAULT '2010-05-05 00:00:00',
+  constraint tenant_capacity_id_key PRIMARY KEY (id),
+  constraint uk_tenant_id UNIQUE (tenant_id));
+
+CREATE TABLE tenant_info (
+  id bigint NOT NULL generated by default as identity,
+  kp varchar(128) NOT NULL,
+  tenant_id varchar(128)  DEFAULT '',
+  tenant_name varchar(128)  DEFAULT '',
+  tenant_desc varchar(256)  DEFAULT NULL,
+  create_source varchar(32) DEFAULT NULL,
+  gmt_create bigint NOT NULL,
+  gmt_modified bigint NOT NULL,
+  constraint tenant_info_id_key PRIMARY KEY (id),
+  constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id));
+CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id);
+
+CREATE TABLE users (
+	username varchar(50) NOT NULL PRIMARY KEY,
+	password varchar(500) NOT NULL,
+	enabled boolean NOT NULL DEFAULT true
+);
+
+CREATE TABLE roles (
+	username varchar(50) NOT NULL,
+	role varchar(50) NOT NULL,
+	constraint uk_username_role UNIQUE (username,role)
+);
+
+CREATE TABLE permissions (
+    role varchar(50) NOT NULL,
+    resource varchar(512) NOT NULL,
+    action varchar(8) NOT NULL,
+    constraint uk_role_permission UNIQUE (role,resource,action)
+);
+
+
+/******************************************/
+/*   ipv6 support   */
+/******************************************/
+ALTER TABLE `config_info_tag`
+MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`;
+
+ALTER TABLE `his_config_info`
+MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `src_user`;
+
+ALTER TABLE `config_info`
+MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`;
+
+ALTER TABLE `config_info_beta`
+MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`;
\ No newline at end of file
diff --git a/persistence/src/test/resources/META-INF/test-derby-import.sql b/persistence/src/test/resources/META-INF/test-derby-import.sql
new file mode 100644
index 00000000000..180145f08b6
--- /dev/null
+++ b/persistence/src/test/resources/META-INF/test-derby-import.sql
@@ -0,0 +1,22 @@
+/*
+ * Copyright 1999-2023 Alibaba Group Holding Ltd.
+ *
+ * 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.
+ */
+
+/* Just for test, not real table structure. */
+INSERT INTO `config_info` (id, data_id, group_id, namespace, content) VALUES (1, "test_id1", "test_group", "", "test_content");
+INSERT INTO `config_info` (id, data_id, group_id, namespace, content) VALUES (2, "test_id2", "test_group", "", "test_content");
+INSERT INTO `config_info` (id, data_id, group_id, namespace, content) VALUES (3, "test_id3", "test_group", "", "test_content");
+INSERT INTO `config_info` (id, data_id, group_id, namespace, content) VALUES (4, "test_id4", "test_group", "", "test_content");
+INSERT INTO `config_info` (id, data_id, group_id, namespace, content) VALUES (5, "test_id5", "test_group", "", "test_content");

From b04d2266b565266b93f3a129311b93899ab2e975 Mon Sep 17 00:00:00 2001
From: "shalk(xiao kun)" 
Date: Thu, 24 Oct 2024 09:37:07 +0800
Subject: [PATCH 4/6] bump maven-enforcer-plugin to 3.5.0 (#12778)

---
 pom.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9b3039d847e..714a60842bc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -105,7 +105,7 @@
         2.2
         1.0.2
         2.7
-        1.4.1
+        3.5.0
         3.5.1
         2.10.4
         3.2.2
@@ -123,7 +123,7 @@
         3.1.2
         1.1.5
         
-        1.0-beta-4
+        1.9.0
         1.3.0
         
         

From 4334cd16c17af53177708268625ad89acb2b4da4 Mon Sep 17 00:00:00 2001
From: Matthew 
Date: Mon, 28 Oct 2024 11:38:28 +0800
Subject: [PATCH 5/6] [ISSUE#12748] Support custom client configuration
 timeout. (#12764)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Support custom client configuration timeout.(#12748)

* Add UT.(#12748)
---
 .../config/impl/ConfigHttpClientManager.java  |  2 +-
 .../alibaba/nacos/client/utils/ParamUtil.java | 29 +++++++++++++++++++
 .../nacos/client/utils/ParamUtilTest.java     | 29 +++++++++++++++++++
 3 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java
index 9afc49cfcf1..86de1116312 100644
--- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java
+++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java
@@ -56,7 +56,7 @@ public class ConfigHttpClientManager implements Closeable {
     
     private static final int CON_TIME_OUT_MILLIS = ParamUtil.getConnectTimeout();
     
-    private static final int READ_TIME_OUT_MILLIS = 3000;
+    private static final int READ_TIME_OUT_MILLIS = ParamUtil.getReadTimeout();
     
     private final LimiterHttpClientRequestInterceptor limiterHttpClientRequestInterceptor = new LimiterHttpClientRequestInterceptor();
     
diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java
index 60d82f44e05..593407911bf 100644
--- a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java
+++ b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java
@@ -58,6 +58,8 @@ public class ParamUtil {
     
     private static int connectTimeout;
     
+    private static int readTimeout;
+    
     private static double perTaskConfigSize = 3000;
     
     private static final String NACOS_CLIENT_APP_KEY = "nacos.client.appKey";
@@ -72,8 +74,12 @@ public class ParamUtil {
     
     private static final String NACOS_CONNECT_TIMEOUT_KEY = "NACOS.CONNECT.TIMEOUT";
     
+    private static final String NACOS_READ_TIMEOUT_KEY = "NACOS.READ.TIMEOUT";
+    
     private static final String DEFAULT_NACOS_CONNECT_TIMEOUT = "1000";
     
+    private static final String DEFAULT_NACOS_READ_TIMEOUT = "3000";
+    
     private static final String PER_TASK_CONFIG_SIZE_KEY = "PER_TASK_CONFIG_SIZE";
     
     private static final String DEFAULT_PER_TASK_CONFIG_SIZE_KEY = "3000";
@@ -97,6 +103,9 @@ public class ParamUtil {
         connectTimeout = initConnectionTimeout();
         LOGGER.info("[settings] [http-client] connect timeout:{}", connectTimeout);
         
+        readTimeout = initReadTimeout();
+        LOGGER.info("[settings] [http-client] read timeout:{}", readTimeout);
+        
         clientVersion = VersionUtils.version;
         
         perTaskConfigSize = initPerTaskConfigSize();
@@ -115,6 +124,18 @@ private static int initConnectionTimeout() {
         }
     }
     
+    private static int initReadTimeout() {
+        String tmp = DEFAULT_NACOS_READ_TIMEOUT;
+        try {
+            tmp = NacosClientProperties.PROTOTYPE.getProperty(NACOS_READ_TIMEOUT_KEY, DEFAULT_NACOS_READ_TIMEOUT);
+            return Integer.parseInt(tmp);
+        } catch (NumberFormatException e) {
+            final String msg = "[http-client] invalid read timeout:" + tmp;
+            LOGGER.error("[settings] " + msg, e);
+            throw new IllegalArgumentException(msg, e);
+        }
+    }
+    
     private static double initPerTaskConfigSize() {
         try {
             return Double.parseDouble(NacosClientProperties.PROTOTYPE.getProperty(PER_TASK_CONFIG_SIZE_KEY,
@@ -165,6 +186,14 @@ public static void setConnectTimeout(int connectTimeout) {
         ParamUtil.connectTimeout = connectTimeout;
     }
     
+    public static int getReadTimeout() {
+        return readTimeout;
+    }
+    
+    public static void setReadTimeout(int readTimeout) {
+        ParamUtil.readTimeout = readTimeout;
+    }
+    
     public static double getPerTaskConfigSize() {
         return perTaskConfigSize;
     }
diff --git a/client/src/test/java/com/alibaba/nacos/client/utils/ParamUtilTest.java b/client/src/test/java/com/alibaba/nacos/client/utils/ParamUtilTest.java
index 008acdcbf0c..796f7f07348 100644
--- a/client/src/test/java/com/alibaba/nacos/client/utils/ParamUtilTest.java
+++ b/client/src/test/java/com/alibaba/nacos/client/utils/ParamUtilTest.java
@@ -47,6 +47,8 @@ class ParamUtilTest {
     
     private int defaultConnectTimeout;
     
+    private int defaultReadTimeout;
+    
     private double defaultPerTaskConfigSize;
     
     private String defaultNodesPath;
@@ -58,6 +60,7 @@ void before() {
         defaultContextPath = "nacos";
         defaultVersion = VersionUtils.version;
         defaultConnectTimeout = 1000;
+        defaultReadTimeout = 3000;
         defaultPerTaskConfigSize = 3000.0;
         defaultNodesPath = "serverlist";
     }
@@ -69,9 +72,11 @@ void after() {
         ParamUtil.setDefaultContextPath(defaultContextPath);
         ParamUtil.setClientVersion(defaultVersion);
         ParamUtil.setConnectTimeout(defaultConnectTimeout);
+        ParamUtil.setReadTimeout(defaultReadTimeout);
         ParamUtil.setPerTaskConfigSize(defaultPerTaskConfigSize);
         ParamUtil.setDefaultNodesPath(defaultNodesPath);
         System.clearProperty("NACOS.CONNECT.TIMEOUT");
+        System.clearProperty("NACOS_READ_TIMEOUT");
         System.clearProperty("PER_TASK_CONFIG_SIZE");
         System.clearProperty(PropertyKeyConst.SystemEnv.ALIBABA_ALIWARE_ENDPOINT_URL);
     }
@@ -126,6 +131,16 @@ void testSetConnectTimeout() {
         assertEquals(expect, ParamUtil.getConnectTimeout());
     }
     
+    @Test
+    void testSetReadTimeout() {
+        int defaultVal = ParamUtil.getReadTimeout();
+        assertEquals(defaultReadTimeout, defaultVal);
+        
+        int expect = 3000;
+        ParamUtil.setReadTimeout(expect);
+        assertEquals(expect, ParamUtil.getReadTimeout());
+    }
+    
     @Test
     void testGetPerTaskConfigSize() {
         double defaultVal = ParamUtil.getPerTaskConfigSize();
@@ -184,6 +199,20 @@ void testInitConnectionTimeoutWithException() throws Throwable {
         });
     }
     
+    @Test
+    void testInitReadTimeoutWithException() throws Throwable {
+        assertThrows(IllegalArgumentException.class, () -> {
+            Method method = ParamUtil.class.getDeclaredMethod("initReadTimeout");
+            method.setAccessible(true);
+            System.setProperty("NACOS.READ.TIMEOUT", "test");
+            try {
+                method.invoke(null);
+            } catch (InvocationTargetException e) {
+                throw e.getCause();
+            }
+        });
+    }
+    
     @Test
     void testInitPerTaskConfigSizeWithException() throws Throwable {
         assertThrows(IllegalArgumentException.class, () -> {

From e2d44f2fd2b12124d844d152b7025e3f758d00e6 Mon Sep 17 00:00:00 2001
From: "blake.qiu" <46370663+Bo-Qiu@users.noreply.github.com>
Date: Thu, 31 Oct 2024 14:54:14 +0800
Subject: [PATCH 6/6] fix(#12769): remove config history by derby. (#12796)

---
 .../datasource/impl/derby/HistoryConfigInfoMapperByDerby.java | 4 ++--
 .../impl/derby/HistoryConfigInfoMapperByDerbyTest.java        | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java
index 08942c9a4fe..041d580ff7e 100644
--- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java
+++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java
@@ -33,8 +33,8 @@ public class HistoryConfigInfoMapperByDerby extends AbstractMapperByDerby implem
 
     @Override
     public MapperResult removeConfigHistory(MapperContext context) {
-        String sql = "DELETE FROM his_config_info WHERE id IN( "
-                + "SELECT id FROM his_config_info WHERE gmt_modified < ? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)";
+        String sql = "DELETE FROM his_config_info WHERE nid IN( "
+                + "SELECT nid FROM his_config_info WHERE gmt_modified < ? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)";
         return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME),
                 context.getWhereParameter(FieldConstant.LIMIT_SIZE)));
     }
diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java
index 434a4d583fc..61d16757141 100644
--- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java
+++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java
@@ -63,7 +63,7 @@ void setUp() throws Exception {
     void testRemoveConfigHistory() {
         MapperResult mapperResult = historyConfigInfoMapperByDerby.removeConfigHistory(context);
         assertEquals(mapperResult.getSql(),
-                "DELETE FROM his_config_info WHERE id IN( SELECT id FROM his_config_info WHERE gmt_modified < ? "
+                "DELETE FROM his_config_info WHERE nid IN( SELECT nid FROM his_config_info WHERE gmt_modified < ? "
                         + "OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)");
         assertArrayEquals(new Object[] {startTime, limitSize}, mapperResult.getParamList().toArray());
     }