最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java執(zhí)行shell命令及日志收集避坑指南分享

 更新時間:2024年04月29日 09:06:34   作者:java小霜  
這篇文章主要介紹了java執(zhí)行shell命令及日志收集避坑指南,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

有時候我們需要調(diào)用系統(tǒng)命令執(zhí)行一些東西,可能是為了方便,也可能是沒有辦法必須要調(diào)用。涉及執(zhí)行系統(tǒng)命令的東西,則就不能做跨平臺了,這和java語言的初衷是相背的。

廢話不多說,java如何執(zhí)行shell命令?自然是調(diào)用java語言類庫提供的接口API了。

1. java執(zhí)行shell的api

執(zhí)行shell命令,可以說系統(tǒng)級的調(diào)用,編程語言自然必定會提供相應(yīng)api操作了。

在java中,有兩個api供調(diào)用:Runtime.exec(), Process API. 簡單使用如下:

1.1. Runtime.exec() 實現(xiàn)

調(diào)用實現(xiàn)如下:

import java.io.InputStream;
 
public class RuntimeExecTest {
    @Test
    public static void testRuntimeExec() {
        try {
            Process process = Runtime.getRuntime()
                                .exec("cmd.exe /c dir");
            process.waitFor();
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

簡單的說就是只有一行調(diào)用即可:Runtime.getRuntime().exec("cmd.exe /c dir") ; 看起來非常簡潔。

1.2. ProcessBuilder 實現(xiàn)

使用ProcessBuilder需要自己操作更多東西,也因此可以自主設(shè)置更多東西。

(但實際上底層與Runtime是一樣的了),用例如下:

public class ProcessBuilderTest {
    @Test
    public void testProcessBuilder() {
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("ipconfig");
        //將標(biāo)準(zhǔn)輸入流和錯誤輸入流合并,通過標(biāo)準(zhǔn)輸入流讀取信息
        processBuilder.redirectErrorStream(true);
        try {
            //啟動進程
            Process start = processBuilder.start();
            //獲取輸入流
            InputStream inputStream = start.getInputStream();
            //轉(zhuǎn)成字符輸入流
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "gbk");
            int len = -1;
            char[] c = new char[1024];
            StringBuffer outputString = new StringBuffer();
            //讀取進程輸入流中的內(nèi)容
            while ((len = inputStreamReader.read(c)) != -1) {
                String s = new String(c, 0, len);
                outputString.append(s);
                System.out.print(s);
            }
            inputStream.close();
        } 
        catch (IOException e) {
            e.printStackTrace();
        }
    }
}

看起來是要麻煩些,但實際上是差不多的,只是上一個用例沒有處理輸出日志而已。

但總體來說的 ProcessBuilder 的可控性更強,所以一般使用這個會更自由些。

以下Runtime.exec()的實現(xiàn):

    // java.lang.Runtime#exec
    public Process exec(String[] cmdarray, String[] envp, File dir)
        throws IOException {
        // 僅為 ProcessBuilder 的一個封裝
        return new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();
    }

2. 調(diào)用shell思考事項

從上面來看,要調(diào)用系統(tǒng)命令,并非難事。那是否就意味著我們可以隨便調(diào)用現(xiàn)成方案進行處理工作呢?當(dāng)然不是,我們應(yīng)當(dāng)要考慮幾個問題?

1. 調(diào)用系統(tǒng)命令是進程級別的調(diào)用;

  • 進程與線程的差別大家懂的,更加重量級,開銷更大。
  • 在java中,我們更多的是使用多線程進行并發(fā)。
  • 但如果用于系統(tǒng)調(diào)用,那就是進程級并發(fā)了,而且外部進程不再受jvm控制,出了問題也就不好玩了。
  • 所以,不要隨便調(diào)用系統(tǒng)命令是個不錯的實踐。

2. 調(diào)用系統(tǒng)命令是硬件相關(guān)的調(diào)用;

  • java語言的思想是一次編寫,到處使用。
  • 但如果你使用的系統(tǒng)調(diào)用,則不好處理了,因為每個系統(tǒng)支持的命令并非完全一樣的,你的代碼也就會因環(huán)境的不一樣而表現(xiàn)不一致了。
  • 健壯性就下來了,所以,少用為好。

3. 內(nèi)存是否夠用?

  • 一般我們jvm作為一個獨立進程運行,會被分配足夠多的內(nèi)存,以保證運行的順暢與高效。
  • 這時,可能留給系統(tǒng)的空間就不會太多了,而此時再調(diào)用系統(tǒng)進程運行業(yè)務(wù),則得提前預(yù)估下咯。

4. 進程何時停止?

  • 當(dāng)我調(diào)起一個系統(tǒng)進程之后,我們后續(xù)如何操作?
  • 比如是異步調(diào)用的話,可能就忽略掉結(jié)果了。
  • 而如果是同步調(diào)用的話,則當(dāng)前線程必須等待進程退出,這樣會讓我們的業(yè)務(wù)大大簡單化了。
  • 因為異步需要考慮的事情往往很多。

5. 如何獲取進程日志信息?

  • 一個shell進程的調(diào)用,可能是一個比較耗時的操作,此時應(yīng)該是只要任何進度,就應(yīng)該匯報出來,從而避免外部看起來一直沒有響應(yīng),從而無法判定是死掉了還是在運行中。
  • 而外部進程的通信,又不像一個普通io的調(diào)用,直接輸出結(jié)果信息。
  • 這往往需要我們通過兩個輸出流進行捕獲。而如何讀取這兩個輸出流數(shù)據(jù),就成了我們獲取日志信息的關(guān)鍵了。
  • ProcessBuilder 是使用inputStream 和 errStream 來表示兩個輸出流, 分別對應(yīng)操作系統(tǒng)的標(biāo)準(zhǔn)輸出流和錯誤輸出流。
  • 但這兩個流都是阻塞io流,如果處理不當(dāng),則會引起系統(tǒng)假死的風(fēng)險。

6. 進程的異常如何捕獲?

  • 在jvm線程里產(chǎn)生的異常,可以很方便的直接使用try...catch... 捕獲,而shell調(diào)用的異常呢?
  • 它實際上并不能直接拋出異常,我們可以通過進程的返回碼來判定是否發(fā)生了異常,這些錯誤碼一般會遵循操作系統(tǒng)的錯誤定義規(guī)范,但時如果是我們自己寫的shell或者其他同學(xué)寫的shell就無法保證了。
  • 所以,往往除了我們要捕獲錯誤之外,至少要規(guī)定0為正確的返回碼。
  • 其他錯誤碼也盡量不要亂用。
  • 其次,我們還應(yīng)該在發(fā)生錯誤時,能從錯誤輸出流信息中,獲取到些許的蛛絲馬跡,以便我們可以快速排錯。

以上問題,如果都能處理得當(dāng),那么我認(rèn)為,這個調(diào)用就是安全的。反之則是有風(fēng)險的。

不過,問題看著雖然多,但都是些細(xì)化的東西,也無需太在意。

基本上,我們通過線程池來控制進程的膨脹問題;通過讀取io流來解決異常信息問題;通過調(diào)用類型規(guī)劃內(nèi)存及用量問題;

3. 完整的shell調(diào)用參考

說了這么多理論,還不如來點實際。

don't bb, show me the code!

import com.my.mvc.app.common.exception.ShellProcessExecException;
import com.my.mvc.app.common.helper.NamedThreadFactory;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.io.FileUtils;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * 功能描述: Shell命令運行工具類封裝
 *
 */
@Log4j2
public class ShellCommandExecUtil {
 
 
    /**
     * @see #runShellCommandSync(String, String[], Charset, String)
     */
    public static int runShellCommandSync(String baseShellDir, String[] cmd,
                                          Charset outputCharset) throws IOException {
        return runShellCommandSync(baseShellDir, cmd, outputCharset, null);
    }
 
    /**
     * 真正運行shell命令
     *
     * @param baseShellDir 運行命令所在目錄(先切換到該目錄后再運行命令)
     * @param cmd 命令數(shù)組
     * @param outputCharset 日志輸出字符集,一般windows為GBK, linux為utf8
     * @param logFilePath 日志輸出文件路徑, 為空則直接輸出到當(dāng)前應(yīng)用日志中,否則寫入該文件
     * @return 進程退出碼, 0: 成功, 其他:失敗
     * @throws IOException 執(zhí)行異常時拋出
     */
    public static int runShellCommandSync(String baseShellDir, String[] cmd,
                                          Charset outputCharset, String logFilePath)
            throws IOException {
        long startTime = System.currentTimeMillis();
        boolean needReadProcessOutLogStreamByHand = true;
        log.info("【cli】receive new Command. baseDir: {}, cmd: {}, logFile:{}",
                baseShellDir, String.join(" ", cmd), logFilePath);
        ProcessBuilder pb = new ProcessBuilder(cmd);
        pb.directory(new File(baseShellDir));
        initErrorLogHolder(logFilePath, outputCharset);
        int exitCode = 0;
        try {
            if(logFilePath != null) {
                ensureFilePathExists(logFilePath);
//            String redirectLogInfoAndErrCmd = " > " + logFilePath + " 2>&1 ";
//            cmd = mergeTwoArr(cmd, redirectLogInfoAndErrCmd.split("\\s+"));
                pb.redirectErrorStream(true);
                pb.redirectOutput(new File(logFilePath));
                needReadProcessOutLogStreamByHand = false;
            }
            Process p = pb.start();
            if(needReadProcessOutLogStreamByHand) {
                readProcessOutLogStream(p, outputCharset);
            }
            try {
                p.waitFor();
            }
            catch (InterruptedException e) {
                log.error("進程被中斷", e);
                setProcessLastError("中斷異常:" + e.getMessage());
            }
            finally {
                exitCode = p.exitValue();
                log.info("【cli】process costTime:{}ms, exitCode:{}",
                        System.currentTimeMillis() - startTime, exitCode);
            }
            if(exitCode != 0) {
                throw new ShellProcessExecException(exitCode,
                        "進程返回異常信息, returnCode:" + exitCode
                                + ", lastError:" + getProcessLastError());
            }
            return exitCode;
        }
        finally {
            removeErrorLogHolder();
        }
    }
 
    /**
     * 使用 Runtime.exec() 運行shell
     */
    public static int runShellWithRuntime(String baseShellDir,
                                          String[] cmd,
                                          Charset outputCharset) throws IOException {
        long startTime = System.currentTimeMillis();
        initErrorLogHolder(null, outputCharset);
        Process p = Runtime.getRuntime().exec(cmd, null, new File(baseShellDir));
        readProcessOutLogStream(p, outputCharset);
        int exitCode;
        try {
            p.waitFor();
        }
        catch (InterruptedException e) {
            log.error("進程被中斷", e);
            setProcessLastError("中斷異常:" + e.getMessage());
        }
        catch (Throwable e) {
            log.error("其他異常", e);
            setProcessLastError(e.getMessage());
        }
        finally {
            exitCode = p.exitValue();
            log.info("【cli】process costTime:{}ms, exitCode:{}",
                    System.currentTimeMillis() - startTime, exitCode);
        }
        if(exitCode != 0) {
            throw new ShellProcessExecException(exitCode,
                    "進程返回異常信息, returnCode:" + exitCode
                            + ", lastError:" + getProcessLastError());
        }
        return exitCode;
    }
 
    /**
     * 確保文件夾存在
     *
     * @param filePath 文件路徑
     * @throws IOException 創(chuàng)建文件夾異常拋出
     */
    public static void ensureFilePathExists(String filePath) throws IOException {
        File path = new File(filePath);
        if(path.exists()) {
            return;
        }
        File p = path.getParentFile();
        if(p.mkdirs()) {
            log.info("為文件創(chuàng)建目錄: {} 成功", p.getPath());
            return;
        }
        log.warn("創(chuàng)建目錄:{} 失敗", p.getPath());
    }
 
    /**
     * 合并兩個數(shù)組數(shù)據(jù)
     *
     * @param arrFirst 左邊數(shù)組
     * @param arrAppend 要添加的數(shù)組
     * @return 合并后的數(shù)組
     */
    public static String[] mergeTwoArr(String[] arrFirst, String[] arrAppend) {
        String[] merged = new String[arrFirst.length + arrAppend.length];
        System.arraycopy(arrFirst, 0,
                merged, 0, arrFirst.length);
        System.arraycopy(arrAppend, 0,
                merged, arrFirst.length, arrAppend.length);
        return merged;
    }
 
    /**
     * 刪除以某字符結(jié)尾的字符
     *
     * @param originalStr 原始字符
     * @param toTrimChar 要檢測的字
     * @return 裁剪后的字符串
     */
    public static String trimEndsWith(String originalStr, char toTrimChar) {
        char[] value = originalStr.toCharArray();
        int i = value.length - 1;
        while (i > 0 && value[i] == toTrimChar) {
            i--;
        }
        return new String(value, 0, i + 1);
    }
 
    /**
     * 錯誤日志讀取線程池(不設(shè)上限)
     */
    private static final ExecutorService errReadThreadPool = Executors.newCachedThreadPool(
            new NamedThreadFactory("ReadProcessErrOut"));
 
    /**
     * 最后一次異常信息
     */
    private static final Map<Thread, ProcessErrorLogDescriptor>
            lastErrorHolder = new ConcurrentHashMap<>();
 
    /**
     * 主動讀取進程的標(biāo)準(zhǔn)輸出信息日志
     *
     * @param process 進程實體
     * @param outputCharset 日志字符集
     * @throws IOException 讀取異常時拋出
     */
    private static void readProcessOutLogStream(Process process,
                                                Charset outputCharset) throws IOException {
        try (BufferedReader stdInput = new BufferedReader(new InputStreamReader(
                process.getInputStream(), outputCharset))) {
            Thread parentThread = Thread.currentThread();
            // 另起一個線程讀取錯誤消息,必須先啟該線程
            errReadThreadPool.submit(() -> {
                try {
                    try (BufferedReader stdError = new BufferedReader(
                            new InputStreamReader(process.getErrorStream(), outputCharset))) {
                        String err;
                        while ((err = stdError.readLine()) != null) {
                            log.error("【cli】{}", err);
                            setProcessLastError(parentThread, err);
                        }
                    }
                }
                catch (IOException e) {
                    log.error("讀取進程錯誤日志輸出時發(fā)生了異常", e);
                    setProcessLastError(parentThread, e.getMessage());
                }
            });
            // 外部線程讀取標(biāo)準(zhǔn)輸出消息
            String stdOut;
            while ((stdOut = stdInput.readLine()) != null) {
                log.info("【cli】{}", stdOut);
            }
        }
    }
 
    /**
     * 新建一個進程錯誤信息容器
     *
     * @param logFilePath 日志文件路徑,如無則為 null
     */
    private static void initErrorLogHolder(String logFilePath, Charset outputCharset) {
        lastErrorHolder.put(Thread.currentThread(),
                new ProcessErrorLogDescriptor(logFilePath, outputCharset));
    }
 
    /**
     * 移除錯誤日志監(jiān)聽
     */
    private static void removeErrorLogHolder() {
        lastErrorHolder.remove(Thread.currentThread());
    }
 
    /**
     * 獲取進程的最后錯誤信息
     *
     *      注意: 該方法只會在父線程中調(diào)用
     */
    private static String getProcessLastError() {
        Thread thread = Thread.currentThread();
        return lastErrorHolder.get(thread).getLastError();
    }
 
    /**
     * 設(shè)置最后一個錯誤信息描述
     *
     *      使用當(dāng)前線程或自定義
     */
    private static void setProcessLastError(String lastError) {
        lastErrorHolder.get(Thread.currentThread()).setLastError(lastError);
    }
 
    private static void setProcessLastError(Thread thread, String lastError) {
        lastErrorHolder.get(thread).setLastError(lastError);
    }
 
    /**
     * 判斷當(dāng)前系統(tǒng)是否是 windows
     */
    public static boolean isWindowsSystemOs() {
        return System.getProperty("os.name").toLowerCase()
                .startsWith("win");
    }
 
    /**
     * 進程錯誤信息描述封裝類
     */
    private static class ProcessErrorLogDescriptor {
 
        /**
         * 錯誤信息記錄文件
         */
        private String logFile;
 
        /**
         * 最后一行錯誤信息
         */
        private String lastError;
        private Charset charset;
        ProcessErrorLogDescriptor(String logFile, Charset outputCharset) {
            this.logFile = logFile;
            charset = outputCharset;
        }
        String getLastError() {
            if(lastError != null) {
                return lastError;
            }
            try{
                if(logFile == null) {
                    return null;
                }
                List<String> lines = FileUtils.readLines(
                        new File(logFile), charset);
                StringBuilder sb = new StringBuilder();
                for (int i = lines.size() - 1; i >= 0; i--) {
                    sb.insert(0, lines.get(i) + "\n");
                    if(sb.length() > 200) {
                        break;
                    }
                }
                return sb.toString();
            }
            catch (Exception e) {
                log.error("【cli】讀取最后一次錯誤信息失敗", e);
            }
            return null;
        }
 
        void setLastError(String err) {
            if(lastError == null) {
                lastError = err;
                return;
            }
            lastError = lastError + "\n" + err;
            if(lastError.length() > 200) {
                lastError = lastError.substring(lastError.length() - 200);
            }
        }
    }
}

以上實現(xiàn),完成了我們在第2點中討論的幾個問題:

  • 1. 主要使用 ProcessBuilder 完成了shell的調(diào)用;
  • 2. 支持讀取進程的所有輸出信息,且在必要的時候,支持使用單獨的文件進行接收輸出日志;
  • 3. 在進程執(zhí)行異常時,支持拋出對應(yīng)異常,且給出一定的errMessage描述;
  • 4. 如果想控制調(diào)用進程的數(shù)量,則在外部調(diào)用時控制即可;
  • 5. 使用兩個線程接收兩個輸出流,避免出現(xiàn)應(yīng)用假死,使用newCachedThreadPool線程池避免過快創(chuàng)建線程;

接下來,我們進行下單元測試:

public class ShellCommandExecUtilTest {
 
    @Test
    public void testRuntimeShell() throws IOException {
        int errCode;
        errCode = ShellCommandExecUtil.runShellWithRuntime("E:\\tmp",
                new String[] {"cmd", "/c", "dir"}, Charset.forName("gbk"));
        Assert.assertEquals("進程返回碼不正確", 0, errCode);
 
    }
 
    @Test(expected = ShellProcessExecException.class)
    public void testRuntimeShellWithErr() throws IOException {
        int errCode;
        errCode = ShellCommandExecUtil.runShellWithRuntime("E:\\tmp",
                new String[] {"cmd", "/c", "dir2"}, Charset.forName("gbk"));
        Assert.fail("dir2 應(yīng)該要執(zhí)行失敗,但卻通過了,請查找原因");
    }
 
    @Test
    public void testProcessShell1() throws IOException {
        int errCode;
        errCode = ShellCommandExecUtil.runShellCommandSync("/tmp",
                new String[]{"cmd", "/c", "dir"}, Charset.forName("gbk"));
        Assert.assertEquals("進程返回碼不正確", 0, errCode);
 
        String logPath = "/tmp/cmd.log";
        errCode = ShellCommandExecUtil.runShellCommandSync("/tmp",
                new String[]{"cmd", "/c", "dir"}, Charset.forName("gbk"), logPath);
        Assert.assertTrue("結(jié)果日志文件不存在", new File(logPath).exists());
    }
 
    @Test(expected = ShellProcessExecException.class)
    public void testProcessShell1WithErr() throws IOException {
        int errCode;
        errCode = ShellCommandExecUtil.runShellCommandSync("/tmp",
                new String[]{"cmd", "/c", "dir2"}, Charset.forName("gbk"));
        Assert.fail("dir2 應(yīng)該要執(zhí)行失敗,但卻通過了,請查找原因");
    }
 
    @Test(expected = ShellProcessExecException.class)
    public void testProcessShell1WithErr2() throws IOException {
        int errCode;
        String logPath = "/tmp/cmd2.log";
        try {
            errCode = ShellCommandExecUtil.runShellCommandSync("/tmp",
                    new String[]{"cmd", "/c", "dir2"}, Charset.forName("gbk"), logPath);
        }
        catch (ShellProcessExecException e) {
            e.printStackTrace();
            throw e;
        }
        Assert.assertTrue("結(jié)果日志文件不存在", new File(logPath).exists());
    }
}

至此,我們的一個安全可靠的shell運行功能就搞定了。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java排序算法中的快速排序算法實現(xiàn)

    Java排序算法中的快速排序算法實現(xiàn)

    這篇文章主要介紹了Java排序算法中的快速排序算法實現(xiàn),通過一趟排序?qū)⒁判虻臄?shù)據(jù)分割成獨立的兩部分,其中一部分的所有數(shù)據(jù)都比另外一部分的所有數(shù)據(jù)都要小,然后再按此方法對這兩部分?jǐn)?shù)據(jù)分別進行快速排序,需要的朋友可以參考下
    2023-12-12
  • Java中request對象常用方法匯總

    Java中request對象常用方法匯總

    這篇文章主要為大家詳細(xì)匯總了Java中request對象的常用方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-02-02
  • 詳解Java的類加載機制及熱部署的原理

    詳解Java的類加載機制及熱部署的原理

    今天我要講的就是Java的熱部署的原理,由于熱部署的原理和類的加載機制有關(guān),所以打算講一下類加載的機制,文中介紹的非常詳細(xì),需要的朋友可以參考下
    2021-05-05
  • Java判斷對象是否為空的四種方法小結(jié)

    Java判斷對象是否為空的四種方法小結(jié)

    這篇文章主要介紹了Java判斷對象是否為空的四種方法,判斷對象是否為空有多種方法,包括使用==或!=運算符直接比較對象與null,使用Objects.isNull()方法,以及用instanceof運算符或Optional類進行更安全的空值處理,需要的朋友可以參考下
    2024-10-10
  • SpringBoot通過tractId操作日志鏈路跟蹤

    SpringBoot通過tractId操作日志鏈路跟蹤

    這篇文章給大家介紹SpringBoot通過tractId操作日志鏈路跟蹤,通過tractId,即可完成對從一個請求進入系統(tǒng)到請求結(jié)束的日志追蹤,本文給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2023-10-10
  • 項目依賴Springboot jar失敗解決方案

    項目依賴Springboot jar失敗解決方案

    這篇文章主要介紹了項目依賴Springboot jar失敗解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-08-08
  • SpringMVC如何接收參數(shù)各種場景

    SpringMVC如何接收參數(shù)各種場景

    這篇文章主要介紹了SpringMVC如何接收參數(shù)各種場景,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java三目運算符的實戰(zhàn)案例

    Java三目運算符的實戰(zhàn)案例

    三目運算符在java中運用可以說非常的廣泛,下面這篇文章主要給大家介紹了關(guān)于Java三目運算符的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-09-09
  • javaweb中mysql數(shù)據(jù)庫連接步驟方法及其實例

    javaweb中mysql數(shù)據(jù)庫連接步驟方法及其實例

    這篇文章主要介紹了使用java web 連接MySQL數(shù)據(jù)庫的驅(qū)動方法的相關(guān)知識,本文介紹的非常詳細(xì),具有參考借鑒價值,需要的朋友可以參考下
    2017-04-04
  • spring-data-jpa+Alibaba Druid多數(shù)據(jù)源案例實踐

    spring-data-jpa+Alibaba Druid多數(shù)據(jù)源案例實踐

    本文介紹基于Spring Data JPA與Alibaba Druid配置多數(shù)據(jù)源的方案,通過定義三個獨立數(shù)據(jù)源、分別設(shè)置JPA配置及實體/倉庫包,實現(xiàn)訂單、用戶、詳情表的分離管理
    2025-07-07

最新評論

新巴尔虎左旗| 邓州市| 商洛市| 启东市| 屏山县| 酒泉市| 沛县| 峨边| 根河市| 普陀区| 肥乡县| 罗城| 德钦县| 铜川市| 来安县| 绥滨县| 辽源市| 永平县| 射阳县| 略阳县| 来宾市| 抚宁县| 宣恩县| 花莲县| 绥滨县| 巨野县| 息烽县| 嵊州市| 宜黄县| 思茅市| 兰溪市| 辽阳县| 龙江县| 金门县| 丘北县| 临洮县| 汉中市| 英吉沙县| 塔城市| 新巴尔虎左旗| 广宁县|