java UUID&雪花算法生成和使用場(chǎng)景詳解
1. 引言
簡(jiǎn)介:為什么需要唯一標(biāo)識(shí)符
在現(xiàn)代軟件架構(gòu)和數(shù)據(jù)管理中,能夠唯一標(biāo)識(shí)信息資源是至關(guān)重要的。唯一標(biāo)識(shí)符(Unique Identifier,簡(jiǎn)稱UID)允許系統(tǒng)在全局范圍內(nèi)區(qū)分每一個(gè)獨(dú)立的元素,無(wú)論是用戶、訂單、消息還是任何數(shù)據(jù)記錄。這種標(biāo)識(shí)的唯一性保證了數(shù)據(jù)的一致性和完整性,避免了數(shù)據(jù)處理過(guò)程中的混淆和錯(cuò)誤。
在分布式系統(tǒng)中,例如互聯(lián)網(wǎng)服務(wù)、云基礎(chǔ)設(shè)施和大規(guī)模計(jì)算環(huán)境,需要跨多個(gè)節(jié)點(diǎn)、位置和時(shí)間區(qū)間追蹤和管理數(shù)據(jù)。在這些環(huán)境中,生成全局唯一的標(biāo)識(shí)符尤為重要,因?yàn)閭鹘y(tǒng)的基于單一數(shù)據(jù)庫(kù)的自增ID生成策略在這些環(huán)境中可能會(huì)導(dǎo)致ID沖突。
UUID和雪花算法的重要性
UUID(Universally Unique Identifier)和雪花算法(Snowflake Algorithm)是生成唯一標(biāo)識(shí)符的兩種流行方法,它們各有優(yōu)勢(shì),并適用于不同的應(yīng)用場(chǎng)景。
- UUID:UUID是一個(gè)16字節(jié)的數(shù)字,通常以32個(gè)十六進(jìn)制數(shù)字表示,并通過(guò)特定的版本算法生成。它的主要優(yōu)點(diǎn)是簡(jiǎn)單易用,能夠在本地生成,無(wú)需通過(guò)網(wǎng)絡(luò)交互,從而避免了網(wǎng)絡(luò)延遲和中斷的問(wèn)題。UUID的生成不依賴于中心數(shù)據(jù)庫(kù)或其他外部系統(tǒng),這使得它非常適合需要高度解耦的系統(tǒng)架構(gòu)。
- 雪花算法:雪花算法是由Twitter開(kāi)發(fā)的,用于生成64位的長(zhǎng)整型數(shù)字作為唯一ID。它結(jié)合了機(jī)器ID、數(shù)據(jù)中心ID和時(shí)間戳信息,可以在分布式系統(tǒng)中快速生成具有時(shí)間順序性的ID。雪花算法的主要優(yōu)點(diǎn)是生成ID時(shí)考慮了時(shí)間順序,這對(duì)于需要維護(hù)記錄順序的系統(tǒng)特別有用。
這兩種算法的存在和發(fā)展,顯著提升了現(xiàn)代系統(tǒng)中數(shù)據(jù)處理的效率和可靠性,使得開(kāi)發(fā)者可以更專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而不必?fù)?dān)心數(shù)據(jù)標(biāo)識(shí)和沖突的問(wèn)題。
2. UUID生成方案
UUID的定義和標(biāo)準(zhǔn)
UUID(Universally Unique Identifier)是一種軟件建構(gòu)的標(biāo)準(zhǔn),也被稱為GUID(Globally Unique Identifier)。UUID的主要目的是讓分布式系統(tǒng)中的所有元素,都能有唯一的識(shí)別信息,而不需要通過(guò)中央控制端來(lái)做標(biāo)識(shí)符的指定。如此一來(lái),每個(gè)人都可以創(chuàng)建不與其它人沖突的UUID。在這個(gè)方面,UUID的目標(biāo)與主鍵的目標(biāo)是相符合的。
UUID是由一個(gè)十六位的數(shù)字組成,通過(guò)特定的算法進(jìn)行生成,形如:550e8400-e29b-41d4-a716-446655440000
。
UUID的標(biāo)準(zhǔn)型式包含32個(gè)16進(jìn)制數(shù)字,以連字號(hào)分為五段,形式為8-4-4-4-12的32個(gè)字符。
Java中生成UUID的方法
在Java中,可以使用java.util.UUID
類來(lái)生成UUID。以下是一個(gè)簡(jiǎn)單的示例:
import java.util.UUID; public class Main { public static void main(String[] args) { UUID uuid = UUID.randomUUID(); System.out.println(uuid.toString()); } }
在這個(gè)示例中,UUID.randomUUID()
方法被用來(lái)生成一個(gè)隨機(jī)UUID。
UUID的版本差異
UUID標(biāo)準(zhǔn)定義了五種不同的生成方法,或者說(shuō)五個(gè)版本。每個(gè)版本的UUID都包含一個(gè)4位的版本號(hào),以便我們可以區(qū)分生成的UUID使用了哪種方法。
- 版本1:基于時(shí)間的UUID:這種UUID使用了發(fā)起UUID生成請(qǐng)求的計(jì)算機(jī)的MAC地址和當(dāng)前的時(shí)間戳(精確到100納秒)來(lái)生成UUID。由于MAC地址是全球唯一的,所以生成的UUID也是全球唯一的。不過(guò),這種方法會(huì)因暴露MAC地址而帶來(lái)一定的安全風(fēng)險(xiǎn)。
- 版本4:隨機(jī)生成的UUID:這種UUID完全由隨機(jī)數(shù)生成,沒(méi)有時(shí)間和硬件的限制,也沒(méi)有安全性問(wèn)題。Java的
UUID.randomUUID()
方法就是生成這種UUID。不過(guò),由于是隨機(jī)生成,所以理論上存在生成的UUID重復(fù)的可能,但實(shí)際上這種可能性非常非常小。
3. UUID的使用場(chǎng)景
UUIDs 提供了一種高度可靠的方式來(lái)生成唯一標(biāo)識(shí)符,這在許多不同的技術(shù)場(chǎng)景中都非常有用。
以下是一些典型的使用UUID的場(chǎng)景:
網(wǎng)絡(luò)系統(tǒng)中的唯一性需求
在網(wǎng)絡(luò)環(huán)境中,尤其是在互聯(lián)網(wǎng)應(yīng)用和服務(wù)中,需要追蹤和區(qū)分成千上萬(wàn)的請(qǐng)求和事務(wù)。UUID可以為每一個(gè)請(qǐng)求或事務(wù)生成一個(gè)唯一的標(biāo)識(shí)符,確保即使在高并發(fā)的情況下也不會(huì)產(chǎn)生沖突。例如,Web API可以為每個(gè)請(qǐng)求生成一個(gè)UUID,用于日志記錄、監(jiān)控和追蹤問(wèn)題,從而提高服務(wù)的可靠性和可追溯性。
數(shù)據(jù)庫(kù)主鍵
在數(shù)據(jù)庫(kù)設(shè)計(jì)中,使用UUID作為主鍵是一種常見(jiàn)的做法,尤其是在分布式數(shù)據(jù)庫(kù)系統(tǒng)中。與傳統(tǒng)的遞增整數(shù)主鍵相比,UUID可以避免跨數(shù)據(jù)庫(kù)的同步和沖突問(wèn)題,使得數(shù)據(jù)庫(kù)的擴(kuò)展更為靈活和可靠。此外,使用UUID作為主鍵可以減少數(shù)據(jù)庫(kù)遷移和合并時(shí)的復(fù)雜性,因?yàn)樗WC了即使在不同的數(shù)據(jù)庫(kù)間也不會(huì)出現(xiàn)主鍵的重復(fù)。
分布式系統(tǒng)中的身份標(biāo)識(shí)
在分布式系統(tǒng)中,尤其是那些涉及多個(gè)服務(wù)和組件的大型系統(tǒng)中,需要一種機(jī)制來(lái)唯一標(biāo)識(shí)每個(gè)組件或節(jié)點(diǎn)。UUID為這些系統(tǒng)提供了一種簡(jiǎn)單而有效的方式來(lái)生成這種唯一標(biāo)識(shí)符。例如,微服務(wù)架構(gòu)中的每個(gè)服務(wù)實(shí)例可以使用UUID來(lái)標(biāo)識(shí),這有助于在服務(wù)發(fā)現(xiàn)和負(fù)載均衡等過(guò)程中確保正確的資源分配和管理。
總的來(lái)說(shuō),UUID的使用可以極大地增強(qiáng)系統(tǒng)的健壯性、可擴(kuò)展性和安全性。其能夠在不依賴中心化控制的情況下生成全局唯一的標(biāo)識(shí)符,使得它成為現(xiàn)代軟件和系統(tǒng)設(shè)計(jì)中不可或缺的一個(gè)工具。
4. 雪花算法(Snowflake Algorithm)
雪花算法的介紹
雪花算法(Snowflake Algorithm)是由Twitter開(kāi)發(fā)的一種用于生成唯一ID的算法,特別適用于分布式系統(tǒng)中。
這種算法可以在不需要中央數(shù)據(jù)庫(kù)的情況下生成全局唯一的ID,非常適合需要處理大量數(shù)據(jù)和高并發(fā)請(qǐng)求的應(yīng)用。
結(jié)構(gòu)解析
雪花算法生成的ID是一個(gè)64位的整數(shù),這64位中包含了以下幾個(gè)部分:
- 時(shí)間戳 - 占用41位,精確到毫秒級(jí),可以使用約69年。
- 數(shù)據(jù)中心ID - 占用5位,可以有最多32個(gè)數(shù)據(jù)中心。
- 機(jī)器ID - 占用5位,每個(gè)數(shù)據(jù)中心可以有最多32臺(tái)機(jī)器。
- 序列號(hào) - 占用12位,每個(gè)節(jié)點(diǎn)每毫秒可以生成最多4096個(gè)ID。
這種結(jié)構(gòu)確保了即使在同一時(shí)間同一數(shù)據(jù)中心的同一機(jī)器上發(fā)生多個(gè)請(qǐng)求,生成的ID也是唯一的。
Java實(shí)現(xiàn)雪花算法
要在Java中實(shí)現(xiàn)雪花算法,我們需要定義一個(gè)類來(lái)處理ID生成的邏輯。
下面是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)示例:
import cn.hutool.core.net.NetUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.Random; /** * Twitter_Snowflake<br> * SnowFlake的結(jié)構(gòu)如下(每部分用-分開(kāi)):<br> * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - * 000000000000 <br> * 1位標(biāo)識(shí),由于long基本類型在Java中是帶符號(hào)的,最高位是符號(hào)位,正數(shù)是0,負(fù)數(shù)是1,所以id一般是正數(shù),最高位是0<br> * 41位時(shí)間截(毫秒級(jí)),注意,41位時(shí)間截不是存儲(chǔ)當(dāng)前時(shí)間的時(shí)間截,而是存儲(chǔ)時(shí)間截的差值(當(dāng)前時(shí)間截 - 開(kāi)始時(shí)間截) * 得到的值),這里的的開(kāi)始時(shí)間截,一般是我們的id生成器開(kāi)始使用的時(shí)間,由我們程序來(lái)指定的(如下下面程序IdWorker類的startTime屬性)。41位的時(shí)間截,可以使用69年,年T * = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br> * 10位的數(shù)據(jù)機(jī)器位,可以部署在1024個(gè)節(jié)點(diǎn),包括5位datacenterId和5位workerId<br> * 12位序列,毫秒內(nèi)的計(jì)數(shù),12位的計(jì)數(shù)順序號(hào)支持每個(gè)節(jié)點(diǎn)每毫秒(同一機(jī)器,同一時(shí)間截)產(chǎn)生4096個(gè)ID序號(hào)<br> * 加起來(lái)剛好64位,為一個(gè)Long型。<br> * SnowFlake的優(yōu)點(diǎn)是,整體上按照時(shí)間自增排序,并且整個(gè)分布式系統(tǒng)內(nèi)不會(huì)產(chǎn)生ID碰撞(由數(shù)據(jù)中心ID和機(jī)器ID作區(qū)分),并且效率較高,經(jīng)測(cè)試,SnowFlake每秒能夠產(chǎn)生26萬(wàn)ID左右。 */ @Component public class SnowflakeIdWorker { /** * 開(kāi)始時(shí)間截 (2019-06-21) */ private final long twepoch = 1561104939733L; /** * 機(jī)器id所占的位數(shù) */ private final long workerIdBits = 5L; /** * 數(shù)據(jù)標(biāo)識(shí)id所占的位數(shù) */ private final long datacenterIdBits = 5L; /** * 支持的最大機(jī)器id,結(jié)果是31 (這個(gè)移位算法可以很快的計(jì)算出幾位二進(jìn)制數(shù)所能表示的最大十進(jìn)制數(shù)) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** * 支持的最大數(shù)據(jù)標(biāo)識(shí)id,結(jié)果是31 */ private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); /** * 序列在id中占的位數(shù) */ private final long sequenceBits = 12L; /** * 機(jī)器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** * 數(shù)據(jù)標(biāo)識(shí)id向左移17位(12+5) */ private final long datacenterIdShift = sequenceBits + workerIdBits; /** * 時(shí)間截向左移22位(5+5+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; /** * 生成序列的掩碼,這里為4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** * 工作機(jī)器ID(0~31) */ private long workerId = getWorkId(); /** * 數(shù)據(jù)中心ID(0~31) */ private long datacenterId = getDataId(); /** * 毫秒內(nèi)序列(0~4095) */ private long sequence = 0L; /** * 上次生成ID的時(shí)間截 */ private long lastTimestamp = -1L; /** * 機(jī)器隨機(jī)獲取數(shù)據(jù)中中心id的參數(shù) 32 */ private final long DATA_RANDOM = maxDatacenterId + 1; /** * 隨機(jī)獲取的機(jī)器id的參數(shù) */ private final long WORK_RANDOM = maxWorkerId + 1; private static final Logger logger = LoggerFactory.getLogger(SnowflakeIdWorker.class); @PostConstruct public void init() { logger.debug("snowflake-work-id:{}", getWorkId()); logger.debug("snowflake-data-id:{}", getDataId()); } public SnowflakeIdWorker() { // this(0, 0); } /** * 構(gòu)造函數(shù) * * @param workerId 工作ID (0~31) * @param datacenterId 數(shù)據(jù)中心ID (0~31) */ public SnowflakeIdWorker(long workerId, long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException( String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException( String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); } this.workerId = workerId; this.datacenterId = datacenterId; } /** * 獲得下一個(gè)ID (該方法是線程安全的) * * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); // 如果當(dāng)前時(shí)間小于上一次ID生成的時(shí)間戳,說(shuō)明系統(tǒng)時(shí)鐘回退過(guò)這個(gè)時(shí)候應(yīng)當(dāng)拋出異常 if (timestamp < lastTimestamp) { if (lastTimestamp - timestamp < 2000) { // 容忍2秒內(nèi)的回?fù)埽苊釴TP校時(shí)造成的異常 timestamp = lastTimestamp; } else { // 如果服務(wù)器時(shí)間有問(wèn)題(時(shí)鐘后退) 報(bào)錯(cuò)。 throw new RuntimeException(String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } } // 如果是同一時(shí)間生成的,則進(jìn)行毫秒內(nèi)序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; // 毫秒內(nèi)序列溢出 if (sequence == 0) { // 阻塞到下一個(gè)毫秒,獲得新的時(shí)間戳 timestamp = tilNextMillis(lastTimestamp); } } // 時(shí)間戳改變,毫秒內(nèi)序列重置 else { sequence = 0L; } // 上次生成ID的時(shí)間截 lastTimestamp = timestamp; // 移位并通過(guò)或運(yùn)算拼到一起組成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (datacenterId << datacenterIdShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一個(gè)毫秒,直到獲得新的時(shí)間戳 * * @param lastTimestamp 上次生成ID的時(shí)間截 * @return 當(dāng)前時(shí)間戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒為單位的當(dāng)前時(shí)間 * * @return 當(dāng)前時(shí)間(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } /** * 根據(jù)host name 取余,發(fā)生異常就獲取0到31之間的隨機(jī)數(shù) * * @return */ public long getWorkId() { try { String ip = NetUtil.getLocalhost().getHostAddress(); logger.info("服務(wù)器IP:{}", ip); return getHostId(ip, maxWorkerId); } catch (Exception e) { return new Random().nextInt((int) WORK_RANDOM); } } /** * 根據(jù)host name 取余,發(fā)生異常就獲取0到31之間的隨機(jī)數(shù) * * @return */ public long getDataId() { try { String ip = NetUtil.getLocalhost().getHostAddress(); logger.info("服務(wù)器IP:{}", ip); return getHostId(ip, maxDatacenterId); } catch (Exception e) { return new Random().nextInt((int) DATA_RANDOM); } } /** * 根據(jù)host name 取余 * * @return */ private long getHostId(String s, long max) { byte[] bytes = s.getBytes(); long sums = 0; for (byte b : bytes) { sums += b; } return sums % (max + 1); } /** * 測(cè)試 */ public static void main(String[] args) { SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); System.out.println(idWorker.nextId()); } }
在這個(gè)實(shí)現(xiàn)中,我們創(chuàng)建了一個(gè)SnowflakeIdWorker
類,該類可以在構(gòu)造時(shí)接受數(shù)據(jù)中心ID和機(jī)器ID,并提供了一個(gè)nextId()
方法來(lái)生成新的ID。
這個(gè)方法確保生成的ID是按時(shí)間順序遞增的,并且在多線程環(huán)境中是安全的。
5. 雪花算法的使用場(chǎng)景
雪花算法由于其獨(dú)特的結(jié)構(gòu)和高效的性能特點(diǎn),非常適用于特定的技術(shù)場(chǎng)景。以下是雪花算法的一些主要使用場(chǎng)景:
大規(guī)模分布式系統(tǒng)中的ID生成
在大規(guī)模分布式系統(tǒng)中,需要確保在多個(gè)節(jié)點(diǎn)和服務(wù)之間生成的ID是唯一的,同時(shí)又不能依賴于中央數(shù)據(jù)庫(kù)或服務(wù)來(lái)維護(hù)ID的唯一性。雪花算法通過(guò)結(jié)合時(shí)間戳、數(shù)據(jù)中心ID、機(jī)器ID和序列號(hào)生成唯一的ID,從而無(wú)需進(jìn)行網(wǎng)絡(luò)交互即可在各個(gè)節(jié)點(diǎn)獨(dú)立生成ID。這種方法非常適合用于電商平臺(tái)、社交網(wǎng)絡(luò)、在線游戲等業(yè)務(wù),其中需要處理大量數(shù)據(jù)并且對(duì)ID生成的性能要求極高。
性能考量和優(yōu)勢(shì)
雪花算法的一個(gè)顯著優(yōu)勢(shì)是其生成ID的速度非??欤梢栽诤撩爰?jí)別生成數(shù)百萬(wàn)個(gè)ID,這對(duì)于高并發(fā)的應(yīng)用場(chǎng)景尤為重要。此外,由于ID是基于時(shí)間戳生成的,這自然地保證了生成的ID的順序性(在同一毫秒內(nèi)通過(guò)序列號(hào)保證順序),這對(duì)于需要保持事件順序的應(yīng)用場(chǎng)景(如日志記錄、消息隊(duì)列等)非常有用。
雪花算法的另一個(gè)優(yōu)勢(shì)是其擴(kuò)展性好。通過(guò)調(diào)整數(shù)據(jù)中心ID和機(jī)器ID的位數(shù),可以靈活適應(yīng)不同規(guī)模的系統(tǒng)擴(kuò)展需要。這使得雪花算法不僅適用于大型系統(tǒng),也適用于中小型系統(tǒng),甚至是動(dòng)態(tài)擴(kuò)展的云環(huán)境。
總之,雪花算法是解決分布式系統(tǒng)中ID生成問(wèn)題的一個(gè)高效、可靠的解決方案,它通過(guò)獨(dú)特的設(shè)計(jì)滿足了高性能、高可用性和可擴(kuò)展性的需求。
6. UUID與雪花算法的比較
UUID和雪花算法都是在特定場(chǎng)景下生成唯一ID的有效工具。然而,它們?cè)谛阅?、?yīng)用場(chǎng)景和選擇依據(jù)方面有著顯著的差異。
性能比較
- UUID:UUID的生成過(guò)程非常簡(jiǎn)單,只需要調(diào)用函數(shù)即可立即生成。因此,UUID的生成性能很高。然而,UUID的長(zhǎng)度較長(zhǎng)(32位十六進(jìn)制),在存儲(chǔ)和傳輸上會(huì)占用更多的資源。此外,如果在數(shù)據(jù)庫(kù)中使用UUID作為主鍵,可能會(huì)導(dǎo)致索引性能下降。
- 雪花算法:雪花算法生成的ID長(zhǎng)度較短(64位整數(shù)),在存儲(chǔ)和傳輸上更加高效。而且,由于雪花算法生成的ID是有序的,因此在數(shù)據(jù)庫(kù)中使用雪花算法生成的ID作為主鍵可以提高索引性能。然而,雪花算法的生成過(guò)程比UUID更復(fù)雜,需要維護(hù)時(shí)間戳、數(shù)據(jù)中心ID、機(jī)器ID和序列號(hào)等信息。
應(yīng)用場(chǎng)景差異
- UUID:UUID最大的優(yōu)點(diǎn)是可以在任何地方生成,不需要考慮系統(tǒng)的分布式架構(gòu)。因此,UUID非常適合在分布式系統(tǒng)中作為全局唯一標(biāo)識(shí)符使用。
- 雪花算法:雪花算法最大的優(yōu)點(diǎn)是生成的ID是有序的,非常適合在需要保證順序的場(chǎng)景中使用。例如,如果需要按照ID的生成順序進(jìn)行數(shù)據(jù)處理,那么雪花算法會(huì)是一個(gè)更好的選擇。
選擇依據(jù)
在選擇UUID和雪花算法時(shí),需要考慮以下幾個(gè)因素:
- 系統(tǒng)架構(gòu):如果系統(tǒng)是分布式的,并且需要在多個(gè)節(jié)點(diǎn)上生成唯一ID,那么雪花算法可能是一個(gè)更好的選擇。如果系統(tǒng)架構(gòu)較簡(jiǎn)單,或者不需要在多個(gè)節(jié)點(diǎn)上生成唯一ID,那么UUID可能是一個(gè)更好的選擇。
- 性能需求:如果系統(tǒng)對(duì)存儲(chǔ)和傳輸效率有較高的要求,那么應(yīng)該選擇生成長(zhǎng)度較短的雪花算法ID。如果系統(tǒng)對(duì)生成ID的速度有較高的要求,那么應(yīng)該選擇生成速度較快的UUID。
- 順序需求:如果系統(tǒng)需要按照ID的生成順序進(jìn)行操作,那么應(yīng)該選擇雪花算法。如果系統(tǒng)不需要保證ID的順序,那么可以選擇UUID。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
javaweb圖書商城設(shè)計(jì)之圖書模塊(4)
這篇文章主要介紹了javaweb圖書商城設(shè)計(jì)之圖書模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)加載權(quán)限信息的方法
這篇文章主要介紹了SpringSecurity實(shí)現(xiàn)動(dòng)態(tài)加載權(quán)限信息,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定需要的朋友可以參考下2022-01-01Java求一個(gè)分?jǐn)?shù)數(shù)列的前20項(xiàng)之和的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java求一個(gè)分?jǐn)?shù)數(shù)列的前20項(xiàng)之和的實(shí)現(xiàn)代碼,需要的朋友可以參考下2017-02-02Spring Boot 配置文件(application.yml、application-dev.y
本文主要介紹了Spring Boot 配置文件,主要包含application.yml、application-dev.yml、application-test.yml,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03Java POI實(shí)現(xiàn)將導(dǎo)入Excel文件的示例代碼
這篇文章主要介紹了Java POI實(shí)現(xiàn)將導(dǎo)入Excel文件的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02Mybatis操作多數(shù)據(jù)源的實(shí)現(xiàn)
本文主要介紹了Mybatis操作多數(shù)據(jù)源,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05基于spring如何實(shí)現(xiàn)事件驅(qū)動(dòng)實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于基于spring如何實(shí)現(xiàn)事件驅(qū)動(dòng)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)
這篇文章主要介紹了Spring中事務(wù)的兩種實(shí)現(xiàn)方式:聲明式事務(wù)、編程式事務(wù)以及他們的本質(zhì)。文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-04-04