亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

SpringBoot配置主從數據庫實現讀寫分離

 更新時間:2023年11月06日 11:36:45   作者:Doker 多克 技術人的數碼品牌  
現在的 Web 應用大都是讀多寫少,本文主要介紹了SpringBoot配置主從數據庫實現讀寫分離,具有一定的參考價值,感興趣的可以了解一下

一、前言

現在的 Web 應用大都是讀多寫少。除了緩存以外還可以通過數據庫 “主從復制” 架構,把讀請求路由到從數據庫節(jié)點上,實現讀寫分離,從而大大提高應用的吞吐量。

通常,我們在 Spring Boot 中只會用到一個數據源,即通過 spring.datasource 進行配置。前文 《在 Spring Boot 中配置和使用多個數據源》 介紹了一種在 Spring Boot 中定義、使用多個數據源的方式。但是這種方式對于實現 “讀寫分離” 的場景不太適合。首先,多個數據源都是通過 @Bean 定義的,當需要新增額外的從數據庫時需要改動代碼,非常不夠靈活。其次,在業(yè)務層中,如果需要根據讀、寫場景切換不同數據源的話只能手動進行。

對于 Spring Boot “讀寫分離” 架構下的的多數據源,我們需要實現如下需求:

  • 可以通過配置文件新增數據庫(從庫),而不不需要修改代碼。
  • 自動根據場景切換讀、寫數據源,對業(yè)務層是透明的。

幸運的是,Spring Jdbc 模塊類提供了一個 AbstractRoutingDataSource 抽象類可以實現我們的需求。

它本身也實現了 DataSource 接口,表示一個 “可路由” 的數據源。

核心的代碼如下:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    // 維護的所有數據源
    @Nullable
    private Map<Object, DataSource> resolvedDataSources;

    // 默認的數據源
    @Nullable
    private DataSource resolvedDefaultDataSource;

    // 獲取 Jdbc 連接
    @Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return determineTargetDataSource().getConnection(username, password);
    }

    // 獲取目標數據源
    protected DataSource determineTargetDataSource() {
        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
        // 調用  determineCurrentLookupKey() 抽象方法,獲取 resolvedDataSources 中定義的 key。
        Object lookupKey = determineCurrentLookupKey();
        DataSource dataSource = this.resolvedDataSources.get(lookupKey);
        if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
            dataSource = this.resolvedDefaultDataSource;
        }
        if (dataSource == null) {
            throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
        }
        return dataSource;
    }

    // 抽象方法,返回 resolvedDataSources 中定義的 key。需要自己實現
    @Nullable
    protected abstract Object determineCurrentLookupKey();
}

核心代碼如上,它的工作原理一目了然。它在內部維護了一個 Map<Object, DataSource> 屬性,維護了多個數據源。

當嘗試從 AbstractRoutingDataSource 數據源獲取數據源連接對象 Connection 時,會調用 determineCurrentLookupKey() 方法得到一個 Key,然后從數據源 Map<Object, DataSource> 中獲取到真正的目標數據源,如果 Key 或者是目標數據源為 null 則使用默認的數據源。

得到目標數據數據源后,返回真正的 Jdbc 連接。這一切對于使用到 Jdbc 的組件(Repository、JdbcTemplate 等)來說都是透明的。

了解了 AbstractRoutingDataSource 后,我們來看看如何使用它來實現 “讀寫分離”。

二、實現思路

首先,創(chuàng)建自己的 AbstractRoutingDataSource 實現類。把它的默認數據源 resolvedDefaultDataSource 設置為主庫,從庫則保存到 Map<Object, DataSource> resolvedDataSources 中。

在 Spring Boot 應用中通常使用 @Transactional 注解來開啟聲明式事務,它的默認傳播級別為 REQUIRED,也就是保證多個事務方法之間的相互調用都是在同一個事務中,使用的是同一個 Jdbc 連接。它還有一個 readOnly 屬性表示是否是只讀事務。

于是,我們可以通過 AOP 技術,在事務方法執(zhí)行之前,先獲取到方法上的 @Transactional 注解從而判斷是讀、還是寫業(yè)務。并且把 “讀寫狀態(tài)” 存儲到線程上下文(ThreadLocal)中!

在 AbstractRoutingDataSource 的 determineCurrentLookupKey 方法中,我們就可以根據當前線程上下文中的 “讀寫狀態(tài)” 判斷當前是否是只讀業(yè)務,如果是,則返回從庫 resolvedDataSources 中的 Key,反之則返回 null 表示使用默認數據源也就是主庫。

三、初始化數據庫

首先,在本地創(chuàng)建 4 個不同名稱的數據庫,用于模擬 “MYSQL 主從” 架構。

-- 主庫
CREATE DATABASE `demo_master` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫
CREATE DATABASE `demo_slave1` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫
CREATE DATABASE `demo_slave2` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';
-- 從庫
CREATE DATABASE `demo_slave3` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

如上,創(chuàng)建了 4 個數據庫。1 個主庫,3 個從庫。它們本質上毫無關系,并不是真正意義上的主從架構,這里只是為了方便演示。

接著,在這 4 個數據庫下依次執(zhí)行如下 SQL 創(chuàng)建一張名為 test 的表。

該表只有 2 個字段,1 個是 id 表示主鍵,一個是 name 表示名稱。

CREATE TABLE `test` (
  `id` int NOT NULL COMMENT 'ID',
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名稱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

最后,初始化數據。往不同的數據庫插入對應的記錄。

INSERT INTO `demo_master`.`test` (`id`, `name`) VALUES (1, 'master');
INSERT INTO `demo_slave1`.`test` (`id`, `name`) VALUES (1, 'slave1');
INSERT INTO `demo_slave2`.`test` (`id`, `name`) VALUES (1, 'slave2');
INSERT INTO `demo_slave3`.`test` (`id`, `name`) VALUES (1, 'slave3');

不同數據庫節(jié)點下 test 表中的 name 字段不同,用于區(qū)別不同的數據庫節(jié)點。

四、創(chuàng)建應用

創(chuàng)建 Spring Boot 應用,添加 spring-boot-starter-jdbc 和 mysql-connector-j (MYSQL 驅動)依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
</dependency>

五、配置定義

我們需要在 application.yaml 中定義上面創(chuàng)建好的所有主、從數據庫。

app:
  datasource:
    master: # 唯一主庫
      jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_master?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
      username: root
      password: root

    slave: # 多個從庫
      slave1:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root
      
      slave2:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root
      
      slave3:
        jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo_slave3?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&allowMultiQueries=true
        username: root
        password: root

在 app.datasource.master 下配置了唯一的一個主庫,也就是寫庫。然后在 app.datasource.slave 下以 Map 形式配置了多個從庫(也就是讀庫),每個從庫使用自定義的名稱作為 Key。

數據源的實現使用的是默認的 HikariDataSource,并且數據源的配置是按照 HikariConfig 類定義的。也就是說,你可以根據 HikariConfig 的屬性在配置中添加額外的設置。

有了配置后,還需要定義對應的配置類,如下:

package cn.springdoc.demo.db;

import java.util.Map;
import java.util.Objects;
import java.util.Properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

@ConfigurationProperties(prefix = "app.datasource")  //  配置前綴
public class MasterSlaveDataSourceProperties {

    // 主庫
    private final Properties master;

    // 從庫
    private final Map<String, Properties> slave;

    @ConstructorBinding // 通過構造函數注入配置文件中的值
    public MasterSlaveDataSourceProperties(Properties master, Map<String, Properties> slave) {
        super();
        
        Objects.requireNonNull(master);
        Objects.requireNonNull(slave);
        
        this.master = master;
        this.slave = slave;
    }

    public Properties master() {
        return master;
    }

    public Map<String, Properties> slave() {
        return slave;
    }
}

還需要在 main 類上使用 @EnableConfigurationProperties 注解來加載我們的配置類:

package cn.springdoc.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import cn.springdoc.demo.db.MasterSlaveDataSourceProperties;

@SpringBootApplication
@EnableAspectJAutoProxy
@EnableConfigurationProperties(value = {MasterSlaveDataSourceProperties.class}) // 指定要加載的配置類
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

這里還使用 @EnableAspectJAutoProxy 開啟了 AOP 的支持,后面會用到。

六、創(chuàng)建 MasterSlaveDataSourceMarker

創(chuàng)建一個 MasterSlaveDataSourceMarker 類,用于維護當前業(yè)務的 “讀寫狀態(tài)”。

package cn.springdoc.demo.db;

public class MasterSlaveDataSourceMarker {

    private static final ThreadLocal<Boolean> flag = new ThreadLocal<Boolean>();

    // 返回標記
    public static Boolean get() {
        return flag.get();
    }

    // 寫狀態(tài),標記為主庫
    public static void master() {
        flag.set(Boolean.TRUE);
    }

    // 讀狀態(tài),標記為從庫
    public static void slave() {
        flag.set(Boolean.FALSE);
    }

    // 清空標記
    public static void clean() {
        flag.remove();
    }
}

通過 ThreadLocal<Boolean> 在當前線程中保存當前業(yè)務的讀寫狀態(tài)。

如果 get() 返回 null 或者 true 則表示非只讀,需要使用主庫。反之則表示只讀業(yè)務,使用從庫。

七、創(chuàng)建 MasterSlaveDataSourceAop

創(chuàng)建 MasterSlaveDataSourceAop 切面類,在事務方法開始之前執(zhí)行。

package cn.springdoc.demo.db;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)  // 在事務開始之前執(zhí)行
public class MasterSlaveDataSourceAop {

    static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSourceAop.class);

    @Pointcut(value = "@annotation(org.springframework.transaction.annotation.Transactional)")
    public void txMethod () {}

    @Around("txMethod()")
    public Object handle (ProceedingJoinPoint joinPoint) throws Throwable {
        
        // 獲取當前請求的主從標識
        try {
                
            // 獲取事務方法上的注解
            Transactional transactional = ((MethodSignature) joinPoint.getSignature()).getMethod().getAnnotation(Transactional.class);
            
            if (transactional != null && transactional.readOnly()) {
                log.info("標記為從庫");
                MasterSlaveDataSourceMarker.slave();    // 只讀,從庫
            } else {
                log.info("標記為主庫");
                MasterSlaveDataSourceMarker.master(); // 可寫,主庫
            }
            
            // 執(zhí)行業(yè)務方法
            Object ret = joinPoint.proceed();
            
            return ret;
            
        } catch (Throwable e) {
            throw e;
        } finally {
            MasterSlaveDataSourceMarker.clean();
        }
    }
}

首先,通過 @Order(Ordered.HIGHEST_PRECEDENCE) 注解保證它必須比聲明式事務 AOP 更先執(zhí)行。

該 AOP 會攔截所有聲明了 @Transactional 的方法,在執(zhí)行前從該注解獲取 readOnly 屬性從而判斷是否是只讀業(yè)務,并且在 MasterSlaveDataSourceMarker 標記。

八、創(chuàng)建 MasterSlaveDataSource

現在,創(chuàng)建 AbstractRoutingDataSource 的實現類 MasterSlaveDataSource:

package cn.springdoc.demo.db;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class MasterSlaveDataSource extends AbstractRoutingDataSource {

    static final Logger log = LoggerFactory.getLogger(MasterSlaveDataSource.class);

    // 從庫的 Key 列表
    private List<Object> slaveKeys;

    // 從庫 key 列表的索引
    private AtomicInteger index = new AtomicInteger(0);

    @Override
    protected Object determineCurrentLookupKey() {
        
        // 當前線程的主從標識
        Boolean master = MasterSlaveDataSourceMarker.get();
        
        if (master == null || master || this.slaveKeys.isEmpty()) {
            // 主庫,返回 null,使用默認數據源
            log.info("數據庫路由:主庫");
            return null;
        }
        
        // 從庫,從 slaveKeys 中選擇一個 Key
        int index = this.index.getAndIncrement() % this.slaveKeys.size();

        if (this.index.get() > 9999999) {
            this.index.set(0); 
        }
        
        Object key = slaveKeys.get(index);
        
        log.info("數據庫路由:從庫 = {}", key);
        
        return key;
    }


    public List<Object> getSlaveKeys() {
        return slaveKeys;
    }
    public void setSlaveKeys(List<Object> slaveKeys) {
        this.slaveKeys = slaveKeys;
    }
}

其中,定義了一個 List<Object> slaveKeys 字段,用于存儲在配置文件中定義的所有從庫的 Key。

在 determineCurrentLookupKey 方法中,判斷當前業(yè)務的 “讀寫狀態(tài)”,如果是只讀則通過 AtomicInteger 原子類自增后從 slaveKeys 輪詢出一個從庫的 Key。反之則返回 null 使用主庫。

九、創(chuàng)建 MasterSlaveDataSourceConfiguration 配置類

最后,需要在 @Configuration 配置類中,創(chuàng)建 MasterSlaveDataSource 數據源 Bean。

package cn.springdoc.demo.db;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

@Configuration
public class MasterSlaveDataSourceConfiguration {

    @Bean
    public DataSource dataSource(MasterSlaveDataSourceProperties properties) {

        MasterSlaveDataSource dataSource = new MasterSlaveDataSource();

        // 主數據庫
        dataSource.setDefaultTargetDataSource(new HikariDataSource(new HikariConfig(properties.master())));

        // 從數據庫
        Map<Object, Object> slaveDataSource = new HashMap<>();
        
        // 從數據庫 Key
        dataSource.setSlaveKeys(new ArrayList<>());
        
        for (Map.Entry<String,Properties> entry : properties.slave().entrySet()) {
            
            if (slaveDataSource.containsKey(entry.getKey())) {
                throw new IllegalArgumentException("存在同名的從數據庫定義:" + entry.getKey());
            }
            
            slaveDataSource.put(entry.getKey(), new HikariDataSource(new HikariConfig(entry.getValue())));
            
            dataSource.getSlaveKeys().add(entry.getKey());
        }
        
        // 設置從庫
        dataSource.setTargetDataSources(slaveDataSource);

        return dataSource;
    }
}

首先,通過配置方法注入配置類,該類定義了配置文件中的主庫、從庫屬性。

使用 HikariDataSource 實例化唯一主庫數據源、和多個從庫數據源,并且設置到 MasterSlaveDataSource 對應的屬性中。

同時還存儲每個從庫的 Key,且該 Key 不允許重復。

十、測試

1、創(chuàng)建 TestService

創(chuàng)建用于測試的業(yè)務類。

package cn.springdoc.demo.service;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TestService {
    final JdbcTemplate jdbcTemplate;
    public TestService(JdbcTemplate jdbcTemplate) {
        super();
        this.jdbcTemplate = jdbcTemplate;
    }

    // 只讀
    @Transactional(readOnly = true)
    public String read () {
        return this.jdbcTemplate.queryForObject("SELECT `name` FROM `test` WHERE id = 1;", String.class);
    } 


    // 先讀,再寫
    @Transactional
    public String write () {
        this.jdbcTemplate.update("UPDATE `test` SET `name` = ? WHERE id = 1;", "new name");
        return this.read();
    }
}

通過構造函數注入 JdbcTemplate(spring jdbc 模塊自動配置的)。

Service 類中定義了 2 個方法。

  • read():只讀業(yè)務,從表中檢索 name 字段返回。
  • write:可寫業(yè)務,先修改表中的 name 字段值為: new name,然后再調用 read() 方法讀取修改后的結果、返回。

2、創(chuàng)建測試類

創(chuàng)建測試類,如下:

package cn.springdoc.demo.test;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

import cn.springdoc.demo.service.TestService;


@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class DemoApplicationTests {

    static final Logger log = LoggerFactory.getLogger(DemoApplicationTests.class);

    @Autowired
    TestService testService;

    @Test
    public void test() throws Exception {

        // 連續(xù)4次讀
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());
        log.info("read={}", this.testService.read());

        // 寫
        log.info("write={}", this.testService.write());
    }
}

在測試類方法中,連續(xù)調用 4 次 TestService 的 read() 方法。由于這是一個只讀方法,按照我們的設定,它會在 3 個從庫之間輪詢使用。由于我們故意把三個從庫 test 表中 name 的字段值設置得不一樣,所以這里可以通過返回的結果看出來是否符合我們的預期。

最后調用了一次 write() 方法,按照設定會路由到主庫。先 UPDATE 修改數據,再調用 read() 讀取數據,雖然 read() 設置了 @Transactional(readOnly = true),但因為入口方法是 write(),所以 read() 還是會從主庫讀取數據(默認的事務傳播級別)。

執(zhí)行測試,輸出的日志如下:

[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標記為從庫
[           main] c.s.demo.db.MasterSlaveDataSource        : 數據庫路由:從庫 = slave1
[           main] c.s.demo.test.DemoApplicationTests       : read=slave1
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標記為從庫
[           main] c.s.demo.db.MasterSlaveDataSource        : 數據庫路由:從庫 = slave2
[           main] c.s.demo.test.DemoApplicationTests       : read=slave2
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標記為從庫
[           main] c.s.demo.db.MasterSlaveDataSource        : 數據庫路由:從庫 = slave3
[           main] c.s.demo.test.DemoApplicationTests       : read=slave3
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標記為從庫
[           main] c.s.demo.db.MasterSlaveDataSource        : 數據庫路由:從庫 = slave1
[           main] c.s.demo.test.DemoApplicationTests       : read=slave1
[           main] c.s.demo.db.MasterSlaveDataSourceAop     : 標記為主庫
[           main] c.s.demo.db.MasterSlaveDataSource        : 數據庫路由:主庫
[           main] c.s.demo.test.DemoApplicationTests       : write=new name

你可以看到,對于只讀業(yè)務。確實輪詢了三個不同的從庫,符合預期。最后的 write() 方法也成功地路由到了主庫,執(zhí)行了修改并且返回了修改后的結果。

十一總結

通過 AbstractRoutingDataSource 可以不使用任何第三方中間件就可以在 Spring Boot 中實現數據源 “讀寫分離”,這種方式需要在每個業(yè)務方法上通過 @Transactional 注解明確定義是讀還是寫。

到此這篇關于SpringBoot配置主從數據庫實現讀寫分離的文章就介紹到這了,更多相關SpringBoot 讀寫分離內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java基礎題新手練習(二)

    Java基礎題新手練習(二)

    下面小編就為大家?guī)硪黄狫ava基礎的幾道練習題(分享)。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧,希望可以幫到你
    2021-07-07
  • MyBatis?Xml映射文件之字符串替換方式

    MyBatis?Xml映射文件之字符串替換方式

    這篇文章主要介紹了MyBatis?Xml映射文件之字符串替換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot實現單元測試示例詳解

    SpringBoot實現單元測試示例詳解

    單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。這篇文章主要為大家介紹了C語言實現單元測試的方法,需要的可以參考一下
    2022-11-11
  • SpringMVC文件上傳 多文件上傳實例

    SpringMVC文件上傳 多文件上傳實例

    這篇文章主要介紹了SpringMVC文件上傳 多文件上傳實例,有需要的朋友可以參考一下
    2014-01-01
  • Java的Synchronized關鍵字學習指南(全面 & 詳細)

    Java的Synchronized關鍵字學習指南(全面 & 詳細)

    這篇文章主要給大家介紹了關于Java的Synchronized關鍵字的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-03-03
  • Java中線程Thread的三種方式和對比

    Java中線程Thread的三種方式和對比

    這篇文章主要介紹了Java中線程Thread的三種方式和對比,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • Quarkus改造Pmml模型項目異常記錄及解決處理

    Quarkus改造Pmml模型項目異常記錄及解決處理

    這篇文章主要為大家介紹了Quarkus改造Pmml模型項目是遇到的異常記錄以及解決方法,有需要的同學可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2022-02-02
  • java集合框架詳解

    java集合框架詳解

    本文主要介紹了java集合框架的相關知識。具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • Java如何實現遠程文件下載到本地目錄

    Java如何實現遠程文件下載到本地目錄

    本文介紹了如何使用Java來實現遠程文件的下載功能,主要通過HTTPS路徑下載文件到本地目錄,詳細介紹了相關代碼和測試步驟,并提供了實際案例供參考,本文旨在幫助需要實現文件下載功能的開發(fā)者快速掌握核心技術
    2024-10-10
  • springboot項目打成war包部署到tomcat遇到的一些問題

    springboot項目打成war包部署到tomcat遇到的一些問題

    這篇文章主要介紹了springboot項目打成war包部署到tomcat遇到的一些問題,需要的朋友可以參考下
    2017-06-06

最新評論