從頭到尾說一次 Spring 事務管理(器)

来源:https://www.cnblogs.com/Jcloud/archive/2023/08/23/17650499.html
-Advertisement-
Play Games

事務管理,一個被說爛的也被看爛的話題,還是八股文中的基礎股之一。​本文會從設計角度,一步步的剖析 Spring 事務管理的設計思路(都會設計事務管理器了,還能玩不轉?) ...


事務管理,一個被說爛的也被看爛的話題,還是八股文中的基礎股之一。​

本文會從設計角度,一步步的剖析 Spring 事務管理的設計思路(都會設計事務管理器了,還能玩不轉?)

為什麼需要事務管理?

先看看如果沒有事務管理器的話,如果想讓多個操作(方法/類)處在一個事務里應該怎麼做:

// MethodA:
public void methodA(){
	Connection connection = acquireConnection();
    try{
        int updated = connection.prepareStatement().executeUpdate();
        methodB(connection);
        connection.commit();
    }catch (Exception e){
        rollback(connection);
    }finally {
        releaseConnection(connection);
    }
}

// MethodB:
public void methodB(Connection connection){
	int updated = connection.prepareStatement().executeUpdate();
}





或者用 ThreadLocal 存儲 Connection?

static ThreadLocal<Connection> connHolder = new ThreadLocal<>();

// MethodA:
public void methodA(){
	Connection connection = acquireConnection();
	connHolder.set(connection);
    try{
        int updated = connection.prepareStatement().executeUpdate();
        methodB();
        connection.commit();
    }catch (Exception e){
        rollback(connection);
    }finally {
        releaseConnection(connection);
        connHolder.remove();
    }
}

// MethodB:
public void methodB(){
    Connection connection = connHolder.get();
	int updated = connection.prepareStatement().executeUpdate();
}





還是有點噁心,再抽象一下?將綁定 Connection 的操作提取為公共方法:

static ThreadLocal<Connection> connHolder = new ThreadLocal<>();

private void bindConnection(){
	Connection connection = acquireConnection();
    connHolder.set(connection);
}

private void unbindConnection(){
	releaseConnection(connection);
    connHolder.remove();
}

// MethodA:
public void methodA(){
    try{
        bindConnection();
        int updated = connection.prepareStatement().executeUpdate();
        methoB();
        connection.commit();
    }catch (Exception e){
        rollback(connection);
    }finally {
        unbindConnection();
    }
}

// MethodB:
public void methodB(){
    Connection connection = connHolder.get();
	int updated = connection.prepareStatement().executeUpdate();
}





現在看起來好點了,不過我有一個新的需求:想讓 methodB 獨立一個新事務,單獨提交和回滾,不影響 methodA

這……可就有點難搞了,ThreadLocal 中已經綁定了一個 Connection,再新事務的話就不好辦了

那如果再複雜點呢,methodB 中需要調用 methodC,methodC 也需要一個獨立事務……

而且,每次 bind/unbind 的操作也有點太傻了,萬一哪個方法忘了寫 unbind ,最後來一個連接泄露那不是完蛋了!

好在 Spring 提供了事務管理器,幫我們解決了這一系列痛點。

Spring 事務管理解決了什麼問題?

Spring 提供的事務管理可以幫我們管理事務相關的資源,比如 JDBC 的 Connection、Hibernate 的 Session、Mybatis 的 SqlSession。如說上面的 Connection 綁定到 ThreadLocal 來解決共用一個事務的這種方式,Spring 事務管理就已經幫我們做好了。

還可以幫我們處理複雜場景下的嵌套事務,比如前面說到的 methodB/methodC 獨立事務。

什麼是嵌套事務?

還是拿上面的例子來說, methodA 中調用了 methodB,兩個方法都有對資料庫的操作,而且都需要事務:

// MethodA:
public void methodA(){
    int updated = connection.prepareStatement().executeUpdate();
    methodB();
    // ...
}

// MethodB:
public void methodB(){
    // ...
}





這種多個方法調用鏈中都有事務的場景,就是嵌套事務。不過要註意的是,並不是說多個方法使用一個事務才叫嵌套,哪怕是不同的事務,只要在這個方法的調用鏈中,都是嵌套事務。

什麼是事務傳播行為?

那調用鏈中的子方法,是用一個新事務,還是使用當前事務呢?這個子方法決定使用新事務還是當前事務(或不使用事務)的策略,就叫事務傳播。

在 Spring 的事務管理中,這個子方法的事務處理策略叫做事務傳播行為(Propogation Behavior)

有哪些事務傳播行為?

Spring 的事務管理支持多種傳播行為,這裡就不貼了,八股文里啥都有。

但給這些傳播行為分類之後,無非是以下三種:

  1. 優先使用當前事務

  2. 不使用當前事務,新建事務

  3. 不使用任何事務

比如上面的例子中,methodB/methodC 獨立事務,就屬於第 2 種傳播行為 - 不使用當前事務,新建事務

看個慄子

以 Spring JDBC + Spring註解版的事務舉例。在預設的事務傳播行為下,methodA 和 methodB 會使用同一個 Connection,在一個事務中

@Transactional
public void methodA(){
    jdbcTemplate.batchUpdate(updateSql, params);
    methodB();
}

@Transactional
public void methodB(){
    jdbcTemplate.batchUpdate(updateSql, params);
}





如果我想讓 methodB 不使用 methodA 的事務,自己新建一個連接/事務呢?只需要簡單的配置一下 @Transactional 註解:

@Transactional
public void methodA(){
    jdbcTemplate.batchUpdate(updateSql, params);
    methodB();
}

// 傳播行為配置為 - 方式2,不使用當前事務,獨立一個新事務
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB(){
    jdbcTemplate.batchUpdate(updateSql, params);
}





就是這麼簡單,獲取 Connection/多方法共用 Connection/多方法共用+獨享 Connection/提交/釋放連接之類的操作,完全不需要我們操心,Spring 都替我們做好了。

怎麼回滾?

在註解版的事務管理中,預設的的回滾策略是:拋出異常就回滾。這個預設策略挺好,連回滾都幫我們解決了,再也不用手動回滾。

但是如果在嵌套事務中,子方法獨立新事務呢?這個時候哪怕拋出異常,也只能回滾子事務,不能直接影響前一個事務

可如果這個拋出的異常不是 sql 導致的,比如校驗不通過或者其他的異常,此時應該將當前的事務回滾嗎?

這個還真不一定,誰說拋異常就要回滾,異常也不回滾行不行?

當然可以!拋異常和回滾事務本來就是兩個問題,可以連在一起,也可以分開處理

// 傳播行為配置為 - 方式2,不使用當前事務,獨立一個新事務

// 指定 Exception 也不會滾
@Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = Exception.class)
public void methodB(){
    jdbcTemplate.batchUpdate(updateSql, params);
}





每個事務/連接使用不同配置

除了傳播和回滾之外,還可以給每個事務/連接使用不同的配置,比如不同的隔離級別:

@Transactional
public void methodA(){
    jdbcTemplate.batchUpdate(updateSql, params);
    methodB();
}

// 傳播行為配置為 - 方式2,不使用當前事務,獨立一個新事務
// 這個事務/連接中使用 RC 隔離級別,而不是預設的 RR
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
public void methodB(){
    jdbcTemplate.batchUpdate(updateSql, params);
}





除了隔離級別之外,其他的 JDBC Connection 配置當然也是支持的,比如 readOnly。這樣一來,雖然我們不用顯示的獲取 connection/session,但還是可以給嵌套中的每一個事務配置不同的參數,非常靈活。

功能總結

好了,現在已經瞭解了 Spring 事務管理的所有核心功能,來總結一下這些核心功能點:

  1. 連接/資源管理 - 無需手動獲取資源、共用資源、釋放資源

  2. 嵌套事務的支持 - 支持嵌套事務中使用不同的資源策略、回滾策略

  3. 每個事務/連接使用不同的配置

事務管理器(TransactionManager)模型

其實仔細想想,事務管理的核心操作只有兩個:提交和回滾。前面所謂的傳播、嵌套、回滾之類的,都是基於這兩個操作。

所以 Spring 將事務管理的核心功能抽象為一個事務管理器(Transaction Manager),基於這個事務管理器核心,可以實現多種事務管理的方式。

這個核心的事務管理器只有三個功能介面:

  1. 獲取事務資源,資源可以是任意的,比如jdbc connection/hibernate mybatis session之類,然後綁定並存儲

  2. 提交事務- 提交指定的事務資源

  3. 回滾事務- 回滾指定的事務資源

interface PlatformTransactionManager{
    // 獲取事務資源,資源可以是任意的,比如jdbc connection/hibernate mybatis session之類
	TransactionStatus getTransaction(TransactionDefinition definition)
			throws TransactionException;
    
    // 提交事務
    void commit(TransactionStatus status) throws TransactionException;
    
    // 回滾事務
    void rollback(TransactionStatus status) throws TransactionException;
}





事務定義 - TransactionDefinition

還記得上面的 @Transactional 註解嗎,裡面定義了傳播行為、隔離級別、回滾策略、只讀之類的屬性,這個就是一次事務操作的定義。

在獲取事務資源時,需要根據這個事務的定義來進行不同的配置:

  1. 比如配置了使用新事務,那麼在獲取事務資源時就需要創建一個新的,而不是已有的

  2. 比如配置了隔離級別,那麼在首次創建資源(Connection)時,就需要給 Connection 設置 propagation

  3. 比如配置了只讀屬性,那麼在首次創建資源(Connection)時,就需要給 Connection 設置 readOnly

為什麼要單獨用一個 TransactionDefinition 來存儲事務定義,直接用註解的屬性不行嗎?

當然可以,但註解的事務管理只是 Spring 提供的自動擋,還有適合老司機的手動擋事務管理(後面會介紹);手動擋可用不了註解,所以單獨建一個事務定義的模型,這樣就可以實現通用。

事務狀態 - TransactionStatus

那既然嵌套事務下,每個子方法的事務可能不同,所以還得有一個子方法事務的狀態 - TransactionStatus,用來存儲當前事務的一些數據和狀態,比如事務資源(Connection)、回滾狀態等。

獲取事務資源

事務管理器的第一步,就是根據事務定義來獲取/創建資源了,這一步最麻煩的是要區分傳播行為,不同傳播行為下的邏輯不太一樣。

“預設的傳播行為下,使用當前事務”,怎麼算有當前事務呢?

把事務資源存起來嘛,只要已經存在那就是有當前事務,直接獲取已存儲的事務資源就行。文中開頭的例子也演示了,如果想讓多個方法無感的使用同一個事務,可以用 ThreadLocal 存儲起來,簡單粗暴。

Spring 也是這麼做的,不過它實現的更複雜一些,抽象了一層事務資源同步管理器 - TransactionSynchronizationManager(本文後面會簡稱 TxSyncMgr),在這個同步管理器里使用 ThreadLocal 存儲了事務資源(本文為了方便理解,儘可能的不貼非關鍵源碼)。

剩下的就是根據不同傳播行為,執行不同的策略了,分類之後只有 3 個條件分支:

  1. 當前有事務 - 根據不同傳播行為處理不同

  2. 當前沒事務,但需要開啟新事務

  3. 徹底不用事務 - 這個很少用

public final TransactionStatus getTransaction(TransactionDefinition definition) {
    //創建事務資源 - 比如 Connection
    Object transaction = doGetTransaction();
    
    if (isExistingTransaction(transaction)) {
        // 處理當前已有事務的場景
        return handleExistingTransaction(def, transaction, debugEnabled);
    }else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED){
        
        // 開啟新事務
    	return startTransaction(def, transaction, debugEnabled, suspendedResources);
    }else {
    	// 徹底不用事務
    }
    
    // ...
}





先介紹一下分支 2 - 當前沒事務,但需要開啟新事務,這個邏輯相對簡單一些。只需要新建事務資源,然後綁定到 ThreadLocal 即可:

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
			boolean debugEnabled, SuspendedResourcesHolder suspendedResources) {
		
    	// 創建事務
		DefaultTransactionStatus status = newTransactionStatus(
				definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    	
    	// 開啟事務(beginTx或者setAutoCommit之類的操作)
    	// 然後將事務資源綁定到事務資源管理器 TransactionSynchronizationManager
		doBegin(transaction, definition);





現在回到分支1 - 當前有事務 - 根據不同傳播行為處理不同,這個就稍微有點麻煩了。因為有子方法獨立事務的需求,可是 TransactionSynchronizationManager 卻只能存一個事務資源。

掛起(Suspend)和恢復(Resume)

Spring 採用了一種掛起(Suspend) - 恢復(Resume)的設計來解決這個嵌套資源處理的問題。當子方法需要獨立事務時,就將當前事務掛起,從 TxSyncMgr 中移除當前事務資源,創建新事務的狀態時,將掛起的事務資源保存至新的事務狀態 TransactionStatus 中;在子方法結束時,只需要再從子方法的事務狀態中,再次拿出掛起的事務資源,重新綁定至 TxSyncMgr 即可完成恢復的操作。

整個掛起 - 恢復的流程,如下圖所示:

spring_tx_suspend_resume.png

註意:掛起操作是在獲取事務資源這一步做的,而恢復的操作是在子方法結束時(提交或者回滾)中進行的。

這樣一來,每個 TransactionStatus 都會保存掛起的前置事務資源,如果方法調用鏈很長,每次都是新事務的話,那這個 TransactionStatus 看起來就會像一個鏈表:
image.png

提交事務

獲取資源、操作完畢後來到了提交事務這一步,這個提交操作比較簡單,只有兩步:

  1. 當前是新事務才提交

  2. 處理掛起資源

怎麼知道是新事務?

每經過一次事務嵌套,都會創建一個新的 TransactionStatus,這個事務狀態里會記錄當前是否是新事務。如果多個子方法都使用一個事務資源,那麼除了第一個創建事務資源的 TransactionStatus 之外,其他都不是新事務。

如下圖所示,A -> B -> C 時,由於 BC 都使用當前事務,那麼雖然 ABC 所使用的事務資源是一樣的,但是只有 A 的 TransactionStatus 是新事務,BC 並不是;那麼在 BC 提交事務時,就不會真正的調用提交,只有回到 A 執行 commit 操作時,才會真正的調用提交操作。
image.png
這裡再解釋下,為什麼新事務才需要提交,而已經有事務卻什麼都不用做:

因為對於新事務來說,這裡的提交操作已經是事務完成了;而對於非新事務的場景,前置事務(即當前事務)還沒有執行完,可能後面還有其他資料庫操作,所以這個提交的操作得讓當前事務創建方去做,這裡並不能提交。

回滾事務

除了提交,還有回滾呢,回滾事務的邏輯和提交事務類似:

  1. 如果是新事務才回滾,原因上面已經介紹過了

  2. 如果不是新事務則只設置回滾標記

  3. 處理掛起資源

註意:事務管理器是不包含回滾策略這個東西的,回滾策略是 AOP 版的事務管理增強的功能,但這個功能並不屬於核心的事務管理器

自動擋與手動擋

Spring 的事務管理功能都是圍繞著上面這個事務管理器運行的,提供了三種管理事務的方式,分別是:

  1. XML AOP 的事務管理 - 比較古老現在用的不多

  2. 註解版本的事務管理 - @Transactional

  3. TransactionTemplate - 手動擋的事務管理,也稱編程式事務管理

自動擋

XML/@Transactional 兩種基於 AOP 的註解管理,其入口類是 TransactionInterceptor,是一個 AOP 的 Interceptor,負責調用事務管理器來實現事務管理。

因為核心功能都在事務管理器里實現,所以這個 AOP Interceptor 很簡單,只是調用一下事務管理器,核心(偽)代碼如下:

public Object invoke(MethodInvocation invocation) throws Throwable {
    
    // 獲取事務資源
	Object transaction = transactionManager.getTransaction(txAttr);    
    Object retVal;
    
    try {
        // 執行業務代碼
    	retVal = invocation.proceedWithInvocation();
        
        // 提交事務
        transactionManager.commit(txStatus);
    } catch (Throwable ex){
        // 先判斷異常回滾策略,然後調用事務管理器的 rollback
    	rollbackOn(ex, txStatus);
    } 
}





並且 AOP 這種自動擋的事務管理還增加了一個回滾策略的玩法,這個是手動擋 TransactionTemplate 所沒有的,但這個功能並不在事務管理器中,只是 AOP 版事務的一個增強。

手動擋

TransactionTemplate這個是手動擋的事務管理,雖然沒有註解的方便,但是好在靈活,異常/回滾啥的都可以自己控制。

所以這個實現更簡單,連異常回滾策略都沒有,特殊的回滾方式還要自己設置(預設是任何異常都會回滾),核心(偽)代碼如下:

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
	
    // 獲取事務資源
    TransactionStatus status = this.transactionManager.getTransaction(this);
    T result;
    try {
        
        // 執行 callback 業務代碼
        result = action.doInTransaction(status);
    }
    catch (Throwable ex) {
        
        // 調用事務管理器的 rollback
        rollbackOnException(status, ex);
    }
    
    提交事務
    this.transactionManager.commit(status);
	}
}





為什麼有這麼方便的自動擋,還要手動擋?

因為手動擋更靈活啊,想怎麼玩就怎麼玩,比如我可以在一個方法中,執行多個資料庫操作,但使用不同的事務資源:

Integer rows = new TransactionTemplate((PlatformTransactionManager) transactionManager,
                                       new DefaultTransactionDefinition(TransactionDefinition.ISOLATION_READ_UNCOMMITTED))
    .execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus status) {
			// update 0
            int rows0 = jdbcTemplate.update(...);
            
            // update 1
            int rows1 = jdbcTemplate.update(...);
            return rows0 + rows1;
        }
    });

Integer rows2 = new TransactionTemplate((PlatformTransactionManager) transactionManager,
                                        new DefaultTransactionDefinition(TransactionDefinition.ISOLATION_READ_UNCOMMITTED))
    .execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus status) {
            
            // update 2
            int rows2 = jdbcTemplate.update(...);
            return rows2;
        }
    });





在上面這個例子里,通過 TransactionTemplate 我們可以精確的控制 update0/update1 使用同一個事務資源和隔離級別,而 update2 單獨使用一個事務資源,並且不需要新建類加註解的方式。

手自一體可以嗎?

當然可以,只要我們使用的是同一個事務管理器的實例,因為綁定資源到同步資源管理器這個操作是在事務管理器中進行的。

AOP 版本的事務管理里,同樣可以使用手動擋的事務管理繼續操作,而且還可以使用同一個事務資源 。

比如下麵這段代碼,update1/update2 仍然在一個事務內,並且 update2 的 callback 結束後並不會提交事務,事務最終會在 methodA 結束時,TransactionInterceptor 中才會提交

@Transactional
public void methodA(){
    
    // update 1
	jdbcTemplate.update(...);
    new TransactionTemplate((PlatformTransactionManager) transactionManager,
                                        new DefaultTransactionDefinition(TransactionDefinition.ISOLATION_READ_UNCOMMITTED))
    .execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus status) {
            
            // update 2
            int rows2 = jdbcTemplate.update(...);
            return rows2;
        }
    });
   
}





總結

Spring 的事務管理,其核心是一個抽象的事務管理器,XML/@Transactional/TransactionTemplate 幾種方式都是基於這個事務管理器的,三中方式的核心實現區別並不大,只是入口不同而已。

image.png

本文為了方便理解,省略了大量的非關鍵實現細節,可能會導致有部分描述不嚴謹的地方,如有問題歡迎評論區留言。

作者:京東保險 蔣信

來源:京東雲開發者社區 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這兩天給我們開發的Chrome插件:[Youtube中文配音](https://youtube-dubbing.com/)增加了賬戶註冊和登錄功能,其中有一步是郵箱驗證,所以這邊會在Spring Boot後臺給用戶的郵箱發個驗證信息。如果發郵件,之前的文章教程里就有,這裡就不說了,著重說說這兩天發現 ...
  • # Java將MySQL建表語句轉換為SQLite的建表語句 **源代碼**: ```java package com.fxsen.platform.core.util; import java.util.HashMap; import java.util.Map; import java.util ...
  • 本文主要講述通過MyBatis、JDBC等做大數據量數據插入的案例和結果。 ## 30萬條數據插入插入資料庫驗證 - 實體類、mapper和配置文件定義 - - User實體 - mapper介面 - mapper.xml文件 - jdbc.properties - sqlMapConfig.xml ...
  • 本文已收錄至GitHub,推薦閱讀 👉 [Java隨想錄](https://github.com/ZhengShuHai/JavaRecord) 微信公眾號:Java隨想錄 > 原創不易,註重版權。轉載請註明原作者和原文鏈接 [TOC] 某天,爪哇星球上,一個普通的房間,正在舉行一場秘密的面試: ...
  • ITGeeker技術奇客發佈的開源Word文字替換小工具更新到v1.0.1.0版本啦,現已支持Office Word文檔頁眉和頁腳的替換。 同時ITGeeker技術奇客修複了v1.0.0.0版本因替換數字引起的in ‘ requires string as left operand, not int ...
  • 本文已收錄至GitHub,推薦閱讀 👉 [Java隨想錄](https://github.com/ZhengShuHai/JavaRecord) 微信公眾號:Java隨想錄 > 原創不易,註重版權。轉載請註明原作者和原文鏈接 [TOC] 前面我們講了可達性分析和根節點枚舉,介紹完了GC的前置工作, ...
  • LibCurl是一個開源的免費的多協議數據傳輸開源庫,該框架具備跨平臺性,開源免費,並提供了包括`HTTP`、`FTP`、`SMTP`、`POP3`等協議的功能,使用`libcurl`可以方便地進行網路數據傳輸操作,如發送`HTTP`請求、下載文件、發送電子郵件等。它被廣泛應用於各種網路應用開發中,... ...
  • 我們在`jupyter notebook`中使用`pandas`顯示`DataFrame`的數據時,由於屏幕大小,或者數據量大小的原因,常常會覺得顯示出來的表格不是特別符合預期。 這時,就需要調整`pandas`顯示`DataFrame`的方式。`pandas`為我們提供了很多調整顯示方式的參數,具 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...