深入講解基于Java開發(fā)的借據(jù)鎖
基于 Java 開發(fā)的借據(jù)鎖信息數(shù)據(jù)模型(DO)類,它映射了數(shù)據(jù)庫中的 asset_loan_invoice_lock_info 表
@Table(name = "asset_loan_invoice_lock_info")
@Getter
@Setter
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AssetLoanInvoiceLockInfoDo extends AssetLoanInvoiceLockInfoKeyDo implements Serializable {
private static final long serialVersionUID = 1101362594517114915L;
/**
* 借據(jù)鎖鎖狀態(tài) EnumBool 0-否 1-是
*/
@Column(name = "lock_status")
private String lockStatus;
/**
* 鎖定流水
*/
@Column(name = "lock_serial_no")
private String lockSerialNo;
/**
* 鎖定交易碼
*/
@Column(name = "lock_trade_code")
private String lockTradeCode;
/**
* 鎖定交易描述
*/
@Column(name = "lock_trade_description")
private String lockTradeDescription;
/**
* 樂觀鎖版本
*/
@Column(name = "lock_version")
private Integer lockVersion;
/**
* 創(chuàng)建時(shí)間
*/
@Column(name = "create_time")
private java.util.Date createTime;
/**
* 更新時(shí)間
*/
@Column(name = "update_time")
private java.util.Date updateTime;
/**
* 鎖定時(shí)間
*/
@Column(name = "lock_time")
private java.util.Date lockTime;
}| 字段名 | 類型 | 對(duì)應(yīng)數(shù)據(jù)庫列 | 含義 |
|---|---|---|---|
serialVersionUID | long | - | 序列化版本號(hào),保證序列化 / 反序列化的兼容性 |
lockStatus | String | lock_status | 借據(jù)鎖狀態(tài),枚舉值(0 - 未鎖定,1 - 已鎖定) |
lockSerialNo | String | lock_serial_no | 鎖定操作的流水號(hào),用于追蹤鎖定行為 |
lockTradeCode | String | lock_trade_code | 鎖定交易的編碼,標(biāo)識(shí)具體的鎖定業(yè)務(wù)類型 |
lockTradeDescription | String | lock_trade_description | 鎖定交易的描述信息,補(bǔ)充說明鎖定原因 / 場(chǎng)景 |
lockVersion | Integer | lock_version | 樂觀鎖版本號(hào),用于解決并發(fā)更新沖突 |
createTime | Date | create_time | 記錄創(chuàng)建時(shí)間 |
updateTime | Date | update_time | 記錄更新時(shí)間 |
lockTime | Date | lock_time | 借據(jù)鎖定的具體時(shí)間 |
借據(jù)鎖的核心本質(zhì)
這個(gè) “借據(jù)鎖”并非編程語言層面的鎖(如 Java 的 synchronized、ReentrantLock),也不是數(shù)據(jù)庫的行鎖 / 表鎖,而是一種業(yè)務(wù)層面的邏輯鎖(也叫狀態(tài)鎖 / 軟鎖),本質(zhì)是通過數(shù)據(jù)庫記錄的狀態(tài)標(biāo)識(shí)來控制借據(jù)的操作權(quán)限,屬于金融系統(tǒng)中典型的 “樂觀鎖 + 業(yè)務(wù)狀態(tài)” 組合設(shè)計(jì)。
簡(jiǎn)單來說:它是給每一筆借據(jù)(貸款憑證)加的 “業(yè)務(wù)標(biāo)記”,標(biāo)記該借據(jù)是否被占用,防止同一筆借據(jù)在不同業(yè)務(wù)流程中被重復(fù)操作(比如重復(fù)放款、重復(fù)核銷)。
借據(jù)鎖的核心作用場(chǎng)景
以金融資產(chǎn)系統(tǒng)為例,借據(jù)鎖主要用于這些高并發(fā)、高風(fēng)險(xiǎn)的場(chǎng)景:
- 放款操作:發(fā)起放款時(shí),先鎖定借據(jù),防止同一筆借據(jù)被多個(gè)放款請(qǐng)求同時(shí)處理,導(dǎo)致重復(fù)放款;放款完成后解鎖。
- 核銷 / 沖正操作:處理借據(jù)的核銷、沖正時(shí),鎖定借據(jù),避免和放款、還款等操作沖突。
- 資產(chǎn)處置 / 轉(zhuǎn)讓:借據(jù)涉及資產(chǎn)轉(zhuǎn)讓、抵債等操作時(shí),鎖定后防止其他流程修改借據(jù)狀態(tài)。
- 批量處理:系統(tǒng)夜間批量計(jì)算利息、罰息時(shí),鎖定相關(guān)借據(jù),避免批量處理和實(shí)時(shí)操作沖突。
關(guān)鍵字段的配合邏輯:
lockStatus:核心開關(guān),0 = 未鎖、1 = 已鎖,是判斷能否操作的核心依據(jù);lockSerialNo:鎖定流水號(hào),可追溯是誰(哪個(gè)業(yè)務(wù)請(qǐng)求)鎖定了借據(jù),便于問題排查;lockTradeCode/lockTradeDescription:記錄鎖定的業(yè)務(wù)類型(比如 “放款”“核銷”),明確鎖定原因;lockVersion:樂觀鎖,防止多線程同時(shí)更新 lockStatus,比如兩個(gè)請(qǐng)求同時(shí)查到 lockStatus=0,更新時(shí)通過版本號(hào)保證只有一個(gè)能成功;lockTime:記錄鎖定時(shí)間,可設(shè)置 “鎖超時(shí)” 邏輯(比如鎖定超過 30 分鐘自動(dòng)解鎖,防止死鎖)。
借據(jù)鎖 vs 技術(shù)鎖的區(qū)別
| 類型 | 借據(jù)鎖(業(yè)務(wù)邏輯鎖) | Java 鎖(編程語言鎖) | 數(shù)據(jù)庫行鎖 |
|---|---|---|---|
| 作用范圍 | 跨 JVM、跨服務(wù)(分布式) | 單個(gè) JVM 進(jìn)程內(nèi) | 數(shù)據(jù)庫事務(wù)內(nèi) |
| 持久化 | 持久化到數(shù)據(jù)庫,重啟不丟失 | 內(nèi)存中,進(jìn)程重啟消失 | 事務(wù)結(jié)束釋放 |
| 鎖超時(shí) | 可自定義(比如 30 分鐘) | 需手動(dòng)實(shí)現(xiàn) | 依賴數(shù)據(jù)庫事務(wù)超時(shí) |
| 適用場(chǎng)景 | 分布式業(yè)務(wù)流程控制 | 單進(jìn)程內(nèi)并發(fā)控制 | 數(shù)據(jù)庫操作的原子性 |
重復(fù)加鎖問題:
一、為什么會(huì)出現(xiàn)重復(fù)加鎖?
重復(fù)加鎖的本質(zhì)是并發(fā)場(chǎng)景下,“查狀態(tài) - 加鎖” 這兩步操作不是原子性的,常見成因有 3 類:
1. 最典型的并發(fā)競(jìng)態(tài)問題(核心原因)
線程A:查詢lockStatus=0 → 準(zhǔn)備更新為1 線程B:查詢lockStatus=0 → 準(zhǔn)備更新為1 線程A:更新lockStatus=1 ? 線程B:更新lockStatus=1 ?(重復(fù)加鎖)
這種情況在高并發(fā)場(chǎng)景下(比如批量放款)極易發(fā)生,只靠lockStatus字段完全無法防范。
2. 樂觀鎖使用不當(dāng)
如果lockVersion(樂觀鎖)的更新邏輯寫錯(cuò),比如:
-- 錯(cuò)誤寫法:只更新狀態(tài),沒帶版本號(hào)條件 UPDATE asset_loan_invoice_lock_info SET lock_status = '1', lock_time = NOW() WHERE loan_id = 'xxx';
-- 正確寫法:必須帶版本號(hào)條件 UPDATE asset_loan_invoice_lock_info SET lock_status = '1', lock_time = NOW(), lock_version = lock_version + 1 WHERE loan_id = 'xxx' AND lock_status = '0' AND lock_version = #{oldVersion};
一旦樂觀鎖沒生效,就會(huì)出現(xiàn)重復(fù)加鎖。
3. 鎖超時(shí) / 解鎖邏輯漏洞
- 業(yè)務(wù)操作超時(shí),但沒觸發(fā)解鎖,導(dǎo)致鎖一直占用;
- 解鎖時(shí)沒校驗(yàn) “鎖歸屬”(比如用
lockSerialNo判斷是誰加的鎖),A 加的鎖被 B 解鎖,解鎖后又被 C 重復(fù)加鎖; - 沒有鎖超時(shí)機(jī)制,某筆借據(jù)被異常鎖定后,一直無法釋放,業(yè)務(wù)方強(qiáng)行繞過鎖狀態(tài)加鎖。
二、如何徹底避免重復(fù)加鎖?
結(jié)合金融系統(tǒng)的最佳實(shí)踐,需要從原子性、唯一性、可追溯、超時(shí)控制四個(gè)維度設(shè)計(jì)加鎖邏輯:
1. 核心:保證 “查 - 加鎖” 的原子性(必做)
最可靠的方式是用數(shù)據(jù)庫的原子操作實(shí)現(xiàn)加鎖,而不是在代碼里分兩步查和更。
/**
* 加鎖方法:通過數(shù)據(jù)庫UPDATE的原子性保證不會(huì)重復(fù)加鎖
* @param loanId 借據(jù)ID
* @param tradeCode 交易碼
* @param serialNo 流水號(hào)
* @return true=加鎖成功,false=已被鎖定
*/
public boolean lockLoanInvoice(String loanId, String tradeCode, String serialNo) {
// 1. 先查詢當(dāng)前鎖信息(獲取舊版本號(hào))
AssetLoanInvoiceLockInfoDo lockInfo = lockInfoMapper.selectByLoanId(loanId);
if (lockInfo == null) {
// 首次加鎖,初始化記錄
lockInfo = new AssetLoanInvoiceLockInfoDo();
lockInfo.setLoanId(loanId);
lockInfo.setLockStatus("1");
lockInfo.setLockSerialNo(serialNo);
lockInfo.setLockTradeCode(tradeCode);
lockInfo.setLockVersion(1);
lockInfo.setLockTime(new Date());
lockInfoMapper.insert(lockInfo);
return true;
}
// 2. 原子更新:帶版本號(hào)+鎖狀態(tài)條件
int updateCount = lockInfoMapper.updateLockStatus(
loanId,
"0", // 原狀態(tài):未鎖定
lockInfo.getLockVersion(), // 原版本號(hào)
"1", // 新狀態(tài):鎖定
serialNo,
tradeCode,
new Date()
);
// 3. 只有更新成功(影響行數(shù)=1),才代表加鎖成功
return updateCount == 1;
}<update id="updateLockStatus">
UPDATE asset_loan_invoice_lock_info
SET lock_status = #{newLockStatus},
lock_serial_no = #{serialNo},
lock_trade_code = #{tradeCode},
lock_time = #{lockTime},
lock_version = lock_version + 1,
update_time = NOW()
WHERE loan_id = #{loanId}
AND lock_status = #{oldLockStatus}
AND lock_version = #{oldVersion}
</update>2. 輔助:增加 “鎖歸屬” 校驗(yàn)(防止誤解鎖 / 重復(fù)加鎖)
解鎖時(shí)必須校驗(yàn)lockSerialNo(只有加鎖的那個(gè)流水才能解鎖)
public boolean unlockLoanInvoice(String loanId, String serialNo) {
int updateCount = lockInfoMapper.updateUnlockStatus(
loanId,
"1", // 原狀態(tài):已鎖定
serialNo, // 必須是加鎖時(shí)的流水號(hào)
"0" // 新狀態(tài):未鎖定
);
return updateCount == 1;
}3. 兜底:設(shè)置鎖超時(shí)機(jī)制(防止死鎖)
定時(shí)任務(wù)掃描超時(shí)的鎖,自動(dòng)解鎖:
/**
* 定時(shí)任務(wù):解鎖超過30分鐘的借據(jù)鎖
*/
@Scheduled(fixedRate = 5 * 60 * 1000) // 每5分鐘執(zhí)行一次
public void unlockTimeoutLock() {
// 計(jì)算超時(shí)時(shí)間:當(dāng)前時(shí)間 - 30分鐘
Date timeoutTime = new Date(System.currentTimeMillis() - 30 * 60 * 1000);
// 只解鎖超時(shí)且未解鎖的鎖
lockInfoMapper.unlockTimeoutLock(timeoutTime);
}<update id="unlockTimeoutLock">
UPDATE asset_loan_invoice_lock_info
SET lock_status = '0',
lock_serial_no = '',
lock_trade_code = '',
lock_version = lock_version + 1,
update_time = NOW()
WHERE lock_status = '1'
AND lock_time < #{timeoutTime}
</update>只要保證 “加鎖操作是原子的、解鎖操作是可控的、異常情況有兜底的”,重復(fù)加鎖問題就能規(guī)避
總結(jié)
到此這篇關(guān)于基于Java開發(fā)借據(jù)鎖的文章就介紹到這了,更多相關(guān)Java借據(jù)鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java畢業(yè)設(shè)計(jì)實(shí)戰(zhàn)之線上水果超市商城的實(shí)現(xiàn)
這是一個(gè)使用了java+SSM+springboot+redis開發(fā)的網(wǎng)上水果超市商城,是一個(gè)畢業(yè)設(shè)計(jì)的實(shí)戰(zhàn)練習(xí),具有水果超市商城該有的所有功能,感興趣的朋友快來看看吧2022-01-01
解決idea爆紅 cant resolve symbol String的問題解析
連著出差幾個(gè)禮拜沒有使用idea開發(fā)工具,突然一天打開電腦發(fā)現(xiàn)idea里的代碼全部爆紅,懵逼不如所措,很多朋友建議我按住Alt+回車設(shè)置jdk就能解決,但是仍然報(bào)錯(cuò),經(jīng)過幾個(gè)小時(shí)的倒騰最終解決,遇到此問題的朋友參考下本文吧2021-06-06
Android/Java中創(chuàng)建類實(shí)例的各種模式實(shí)例代碼
這篇文章主要介紹了Android/Java中創(chuàng)建類實(shí)例各種模式的相關(guān)資料,包括New關(guān)鍵字、靜態(tài)工廠方法、建造者模式、單例模式、依賴注入、抽象工廠模式、原型模式和Reflection,每個(gè)模式有其特點(diǎn)和適用場(chǎng)景,需要的朋友可以參考下2025-09-09
Java?代碼本地設(shè)置Hadoop用戶名密碼的方法
在Hadoop環(huán)境中,通常使用Kerberos進(jìn)行身份驗(yàn)證,這篇文章主要介紹了Java?代碼本地設(shè)置Hadoop用戶名密碼的方法,需要的朋友可以參考下2024-08-08
Hibernate中實(shí)現(xiàn)增刪改查的步驟詳解
本篇文章主要介紹了Hibernate中實(shí)現(xiàn)增刪改查的步驟與方法,具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02
快速解決?IDEA?報(bào)錯(cuò):?“java?找不到符號(hào)“(“cannot?find?symbol“)
文章詳細(xì)講解了在IntelliJIDEA中解決“找不到符號(hào)”錯(cuò)誤的方法,包括檢查導(dǎo)入語句、拼寫錯(cuò)誤、類路徑設(shè)置、文件編譯狀態(tài)、JDK配置以及IDE配置問題,通過具體示例代碼,展示了如何從錯(cuò)誤代碼到解決步驟,感興趣的朋友一起看看吧2025-03-03
SpringBoot 單元測(cè)試實(shí)戰(zhàn)(Mockito,MockBean)
這篇文章主要介紹了SpringBoot 單元測(cè)試實(shí)戰(zhàn)(Mockito,MockBean),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09

