欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java開發(fā)中常遇到的各種難點(diǎn)以及解決思路方案

 更新時間:2023年07月20日 10:07:37   作者:孟秋與你  
Java項(xiàng)目是一個復(fù)雜的軟件開發(fā)過程,其中會涉及到很多技術(shù)難點(diǎn),這篇文章主要給大家介紹了關(guān)于java開發(fā)中常遇到的各種難點(diǎn)以及解決思路方案的相關(guān)資料,需要的朋友可以參考下

作為一個開發(fā)人員 總會遇到各種難題 本文列舉博主 遇見/想到 的例子 ,也希望同學(xué)們可以共同進(jìn)步~

邏輯刪除如何建立唯一索引

場景描述:

比如我們有project項(xiàng)目表

字段project_name 是唯一的,且有邏輯刪除字段is_delete 0表示未刪除 1表示已刪除

很顯然 不能直接將project_name設(shè)置為唯一索引,例如A用戶建立的project_name為 java工程,又把這個工程(邏輯)刪除了, 這時B用戶是允許建立 java工程的。

那將is_delete project_name 共同設(shè)置為唯一索引是否可行呢? 答案也是否定的,在B用戶刪除時,就會出現(xiàn)問題了。

解決方案:

is_delete 不用0和1表示,可改為數(shù)字遞增,或者時間戳(盡量小 例如納秒級別), 這時將is_delete project_name 共同設(shè)置為唯一索引 可以解決該問題。

唯一索引失效問題

場景描述:

人員姓名和電話 組成唯一索引 。

出現(xiàn)問題:

有兩個小孩 名字都叫小朋友 且他們都沒有手機(jī)號 此時數(shù)據(jù)重復(fù) 唯一索引失效。 我們換個場景,在高并發(fā)的電商活動中,用戶姓名和vip標(biāo)識碼 組成唯一索引,此時有兩位用戶 都不是vip用戶,vip標(biāo)識碼都為空,那可能出現(xiàn)的問題就比較嚴(yán)峻了

解決方案: 唯一索引的字段設(shè)置為非空,因?yàn)榭帐窃试S重復(fù)的

( 不管單獨(dú)將某一個字段設(shè)置為唯一索引 還是多個字段組合成唯一索引 都一樣的)

加密字段模糊查詢問題

場景描述: 用戶敏感信息,例如手機(jī)號 身份證 戶籍所在地 入庫時,我們通常會加密, 這時需要模糊查詢

解決方案:

數(shù)據(jù)量少時,例如只是一個公司內(nèi)部系統(tǒng)的人員表,可以全表查詢 并解密,在java代碼中過濾 (如果遇到要分頁,那得好好考慮怎么處理分頁問題了)

與業(yè)務(wù)/產(chǎn)品溝通,看搜索的字?jǐn)?shù)是否相對固定的,例如某用戶的戶籍所在地是廣東省廣州市 那么我們可以將廣東省、廣州市拆分加密。

假設(shè)廣東省加密后字符串為 pwd_gds 廣州市加密后字符串為pwd_gzs,此時我們前端傳入廣州市,后端加密后再進(jìn)行模糊查詢 sql語句變成 like %pwd_gzs%

當(dāng)然 前面兩種方式只是取巧,通常在中型規(guī)模的項(xiàng)目就已經(jīng)不適用了,既然提到拆分,那我們可以聯(lián)想到分詞,所以我們可以使用es,將各詞都拆分加密 存入es中 (題外話 es也好 其它存儲也罷 一定要設(shè)置密碼 )

maven依賴沖突問題(jar包版本沖突問題)

場景描述: classNotFound , 這是在項(xiàng)目中,引入版本不正確最經(jīng)常遇到的問題了。 我們跟進(jìn)報(bào)錯類,找到頂部import導(dǎo)包處,假設(shè)我們紅色涂抹部分報(bào)紅,我們可以找到前一級目錄(紅色劃線處) ,按住ctrl 鍵 再鼠標(biāo)左鍵點(diǎn)擊,找到所在jar包

解決方案: 將jar包升級(或降級)。

但很多時候,該jar包并不是我們直接通過maven依賴引入的,可能是通過其它組件內(nèi)部引用的,這個時候我們就可以通過mvn dependency:tree 命令,將控制臺打印信息復(fù)制到文本編輯器,在文本編輯器搜索 即可知道是哪個父包引入的

sql in條件查詢時 將結(jié)果按照傳入順序排序

場景描述: 例如我們調(diào)用外部接口獲取id, 再通過id去數(shù)據(jù)庫查詢,如果獲取一條id 查一次庫,是可以保證結(jié)果順序和id傳入順序一致的;那此時我們希望優(yōu)化一下下,等獲取一批id時,再通過in條件查詢的形式 :

select xx,xxx,xxxx from t where id in(5,1,4,2,3) 

此時如何保證返回結(jié)果順序與id傳入順序一致呢? 如上偽代碼 id=5 時,希望返回記錄在第一條

解決方案:

sql層面處理

orcale : order by decode

mysql : order by field

2. 如果條件允許 不是直接sql開發(fā),那么推薦是在java代碼中去二次處理數(shù)據(jù)的,循環(huán)idList 根據(jù)id對比去重新組裝結(jié)果即可。

數(shù)據(jù)庫主從復(fù)制 主從不同步問題

場景描述: 由于網(wǎng)絡(luò)延遲、負(fù)載、、自增主鍵不一致等等各種原因 導(dǎo)致主從數(shù)據(jù)不一致

解決方法: 線上真出現(xiàn)了問題,都到了需要集群數(shù)據(jù)庫級別的項(xiàng)目 博主覺得吧 大部分還是手動修復(fù)數(shù)據(jù)吧 出現(xiàn)問題 誰都擔(dān)不起…

言歸正傳:

  • 鎖主庫 鎖為只讀狀態(tài)
  • 數(shù)據(jù)導(dǎo)出
  • 停止從庫
  • 數(shù)據(jù)導(dǎo)入
  • 重新開始同步

但是鎖主庫 停從庫 這時候如果有數(shù)據(jù)來源 非常難處理,這時候最好的方式就是 業(yè)務(wù)對外公布維護(hù)了。

數(shù)據(jù)庫讀寫分離 讀寫不一致

場景描述: 讀寫分離時,讀從庫時 數(shù)據(jù)和主庫不一致

解決方法: 還是數(shù)據(jù)同步問題,看業(yè)務(wù)是否能容忍錯誤,能就不處理 不能容忍就手動修數(shù)據(jù)/重新同步。

臨時解決方案為:強(qiáng)制路由(強(qiáng)制讀取主庫) 但博主還是認(rèn)為,只要不是大面積出現(xiàn)問題,手動修數(shù)據(jù)都是比較穩(wěn)妥的方案。

雙寫不一致問題 并發(fā)下數(shù)據(jù)庫和緩存不一致

場景描述 : 在博主的 《從高并發(fā)場景下超賣問題到redis分布式鎖》博客中 有提到過具體案例

解決方法:

1.延遲雙刪

優(yōu)點(diǎn): 博主個人認(rèn)為優(yōu)點(diǎn)不明顯

缺點(diǎn):博主認(rèn)為在寫多讀少的場景下 沒有一點(diǎn)用

寫多讀少場景下,在寫入時刪除緩存,讀時更新緩存,此時延遲雙刪 不能解決任何問題 反而降低性能

2.使用隊(duì)列 串行化

優(yōu)點(diǎn):避免不一致問題

缺點(diǎn):效率低

3.分布式鎖 串行化 如redislock 提供了讀寫鎖

優(yōu)缺點(diǎn)與第2點(diǎn)一致

4.使用canal中間件

博主未接觸過 只是知道該中間件可以解決

java服務(wù)如何作為websocket客戶端

場景描述: 有的時候 我們對接供應(yīng)商/甲方接口,可能會遇到對方給的websocket接口,我們避免在前后端傳輸之間出現(xiàn)數(shù)據(jù)丟失問題 可能想在后端自己搭建websocket客戶端。 注意是客戶端,網(wǎng)上搜java websocket客戶端,千篇一律都是搜出作為服務(wù)端的教程。

解決方法: 可以使用netty實(shí)現(xiàn),博主目前在寫自動重連和發(fā)送心跳時 遇到了問題 找了大佬寫的比較好的代碼 并經(jīng)過測試 是可用的 具體的代碼會單獨(dú)發(fā)博客教程

spring事務(wù)失效問題

場景描述: 事務(wù)失效 出現(xiàn)異常不回滾 ,首先 @Transactional需要加上(rollbackFor = Exception.class),博主之前有單獨(dú)文章介紹過為什么阿里規(guī)范要求加上

解決方法: 博主私認(rèn)為 所有失效問題都是因?yàn)閷pring代理對象機(jī)制理解不深導(dǎo)致的,失效只是自己沒用對,歡迎在博主博客搜索事務(wù) 查看相應(yīng)文章

數(shù)據(jù)庫死鎖問題

場景描述: 數(shù)據(jù)庫死鎖 導(dǎo)致系統(tǒng)卡爆

解決方法: 博主曾切身體會過,在老舊項(xiàng)目中,使用的是oracle 存儲過程開發(fā),由于大量的sql代碼,且使用for update悲觀鎖,各處sql實(shí)在太多了,且未及時commit,引發(fā)了死鎖,出現(xiàn)死鎖我們需要在 v$session 中找到死鎖進(jìn)程 并殺死進(jìn)程,并及時優(yōu)化sql,簡化或拆分邏輯。

在mysql中,使用replace into語句 也會引發(fā)死鎖,建議使用select + insert方式替代,(據(jù)說mysql8.0已修復(fù)該bug 博主未親測)

跨庫分頁問題

場景描述:
數(shù)據(jù)源來自不同的庫,甚至不同類型的數(shù)據(jù)庫(例如一部分來自mysql,部分來自于時序數(shù)據(jù)庫)

大多數(shù)時候,只需要單獨(dú)查不同的庫就能滿足業(yè)務(wù),各司其職;但有一個頁面 需要查看這兩個庫的數(shù)據(jù) 并實(shí)現(xiàn)分頁功能。

解決方法:

首先能不跨庫分頁就不跨庫分頁,看業(yè)務(wù)是否真的不能妥協(xié),數(shù)據(jù)源是否真的不能合并。

如果都不能,那只能考慮分頁方案,下面是博主想到的方法:

將兩個庫的數(shù)據(jù),同步至同一張大表中,記錄好每次同步的最新那條數(shù)據(jù)的時間戳,下次同步時,同步這個時間戳以后的數(shù)據(jù)即可,大表只負(fù)責(zé)分頁查詢。

這時大表數(shù)據(jù)量雖然大些,但有分頁在,效率不會過低。

(如果數(shù)據(jù)量過大 根據(jù)實(shí)際情況,考慮同步至es 、clickhouse等)

博主看到有人提過 canal可以同步mysql數(shù)據(jù)到es,還是要提醒:生產(chǎn)環(huán)境中不是我們demo寫著玩,使用這種中間件 必須熟悉原理 否則重要數(shù)據(jù)丟失或出現(xiàn)問題 得不償失!

分布式事務(wù)問題

場景描述:在分布式中 需要事務(wù)回滾

解決方法:可以引入seata中間件,seata中間件本身就是個事務(wù)調(diào)度器,基于mysql的undo日志;
如果不引入seata,也可以手動回滾,但這得嚴(yán)格要求代碼及時調(diào)用,且不適用高并發(fā)場景,
僅適用于中小型項(xiàng)目, 偽代碼如下:

// service A
public GoodsDO delete(Long id){
	GoodsDO gs = database.getOne(id);
	database.deleteById(id);
	return gs;
}
public void insert(GoodsDO  gs){
	database.insert(gs);
}
// service B
@Autowired 
private ServiceA serviceA;
public void handle(Long id){
	try{
		GoodsDO  gs = serviceA.deleteById(id);
		// do other things  serviceB.xx();
	}
	 catch(E e){
	 	// 這里可以換成aop方式,也可以通過mq實(shí)現(xiàn)異步
	 	serviceA.insert(gs);
	 }
}

如何避免多人同時修改問題

場景描述:例如管理系統(tǒng)中,管理人員可以修改員工的基本信息,員工自己也可以修改。員工在修改過程中,如果管理員已經(jīng)修改并提交,員工隨后提交,這就會將管理人員修改的內(nèi)容覆蓋。

解決方法:詳情接口 加上樂觀鎖版本號,在點(diǎn)擊編輯按鈕時,調(diào)用一次詳情接口,獲取到當(dāng)前的樂觀鎖版本號,例如員工點(diǎn)編輯時 version = 1,接下來管理員也點(diǎn)擊了編輯,管理員得到的版本號也為1 (此時員工還沒保存),接著管理員點(diǎn)擊保存,前端將版本號傳回后端,保存接口中去判斷前端傳入的版本號和當(dāng)前數(shù)據(jù)庫版本號是否一致(這個時候是一致的 都是1),管理員保存成功 修改樂觀鎖版本號。員工點(diǎn)擊保存時,傳入的版本號也為1,但此時數(shù)據(jù)庫獲取的版本號,已經(jīng)變成2了,提示前端信息已被他人修改 刷新頁面再進(jìn)入。

netty中 發(fā)送多條指令 如何與回復(fù)內(nèi)容進(jìn)行對應(yīng)

場景描述:netty中,向服務(wù)端發(fā)送多條指令,接收到回復(fù)時,如何確定哪條內(nèi)容對應(yīng)是哪條指令發(fā)送的

解決方法:可以在發(fā)送時,在數(shù)據(jù)頭部添加一個請求ID字段,或者在尾部添加一個ack應(yīng)答機(jī)制, 但這前提都是需要服務(wù)端進(jìn)行配合。
參考代碼如下:

// 客戶端代碼
public class ClientHandler extends ChannelInboundHandlerAdapter {
    // 記錄每個請求的請求ID
    private final Map<Integer, String> requestMap = new ConcurrentHashMap<>();
    // 記錄每個請求對應(yīng)的響應(yīng)結(jié)果
    private final Map<String, String> responseMap = new ConcurrentHashMap<>();
    // 請求ID生成器
    private final AtomicInteger requestIdGenerator = new AtomicInteger(0);
    public void sendRequest(byte[] data) {
        int requestId = requestIdGenerator.incrementAndGet();
        ByteBuf buf = Unpooled.buffer(data.length + 4);
        buf.writeInt(requestId);
        buf.writeBytes(data);
        channel.writeAndFlush(buf);
        // 將請求ID和請求數(shù)據(jù)保存到請求映射表
        requestMap.put(requestId, Arrays.toString(data));
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            int requestId = buf.readInt();
            byte[] data = new byte[buf.readableBytes()];
            buf.getBytes(buf.readerIndex(), data);
            String request = requestMap.get(requestId);
            if (request != null) {
                // 將請求ID和響應(yīng)數(shù)據(jù)保存到響應(yīng)映射表
                String response = Arrays.toString(data);
                responseMap.put(request, response);
                // 從請求映射表中刪除請求ID
                requestMap.remove(requestId);
            }
        }
    }
}

ack:

public class MyClientHandler extends ChannelInboundHandlerAdapter {
    // 記錄上一次請求的ACK字段的值
    private int lastAck = 1;
    public void sendRequest(byte[] data) {
        // 在請求數(shù)據(jù)末尾添加一個預(yù)留的ACK字段
        byte[] requestData = Arrays.copyOf(data, data.length + 1);
        requestData[data.length] = (byte) lastAck;
        channel.writeAndFlush(requestData);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof ByteBuf) {
            ByteBuf buf = (ByteBuf) msg;
            byte[] data = new byte[buf.readableBytes()];
            buf.readBytes(data);
            int ack = data[data.length - 1];
            // 修改ACK字段的值為1
            data[data.length - 1] = 1;
            lastAck = 1;
            // 處理服務(wù)端的響應(yīng)
            handleResponse(data);
        }
    }
    public void handleResponse(byte[] data) {
        // 處理服務(wù)端的響應(yīng)
        // ...
    }
}

那如果服務(wù)端拒絕配合呢? 那我們只能在等接收到響應(yīng)后,再發(fā)送下一條指令,思路如下
(但是注意 并發(fā)下會出現(xiàn)問題 如果有并發(fā)場景,必須得服務(wù)端配合做應(yīng)答機(jī)制):

1.定義一個 指令下標(biāo) (我們以要發(fā)送10條指令為例) :

public static AtomicInteger index = new AtomicInteger(0);

2.提供一個修改下標(biāo)的方法

 	public static void setOtherIndex() {
          // 如果下標(biāo)到了10 則清0 進(jìn)行下一次的輪詢
      if (Objects.equals(cabinIndex.get(), 10)) {
          cabinIndex.set(0);
      } else {
          cabinIndex.getAndAdd(1);
      }
  }

3.發(fā)送指令

 	if(index.get() == 0){
 	  new byte[]{0x01}
 	}else if (index.get() == 1){
 	  new byte[]{0x02}
 	}
 	// .....

4.channelRead 方法中處理數(shù)據(jù)

 // dosomething
 // 處理完畢后 下標(biāo)偏移
 setOtherIndex();

總結(jié) 

到此這篇關(guān)于java開發(fā)中常遇到的各種難點(diǎn)以及解決思路方案的文章就介紹到這了,更多相關(guān)java開發(fā)難點(diǎn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Java關(guān)于JDK中時間日期的API

    詳解Java關(guān)于JDK中時間日期的API

    這篇文章主要介紹了詳解Java關(guān)于JDK中時間日期的API,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • 如何使用Docker部署Java項(xiàng)目

    如何使用Docker部署Java項(xiàng)目

    在接觸了docker后,干什么都想用docker來弄,這篇文章主要給大家介紹了關(guān)于如何使用Docker部署Java項(xiàng)目的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-07-07
  • Java小程序賽馬游戲?qū)崿F(xiàn)過程詳解

    Java小程序賽馬游戲?qū)崿F(xiàn)過程詳解

    這篇文章主要介紹了Java小程序賽馬游戲?qū)崿F(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • IntelliJ IDEA引入第三方j(luò)ar包或查看Java源碼的時候報(bào)decompiled.class file bytecode version:52.0(java 8)錯誤的解決辦法

    IntelliJ IDEA引入第三方j(luò)ar包或查看Java源碼的時候報(bào)decompiled.class file byt

    今天小編就為大家分享一篇關(guān)于IntelliJ IDEA引入第三方j(luò)ar包或查看Java源碼的時候報(bào)decompiled.class file bytecode version:52.0(java 8)錯誤的解決辦法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • springboot中RabbitMQ死信隊(duì)列的實(shí)現(xiàn)示例

    springboot中RabbitMQ死信隊(duì)列的實(shí)現(xiàn)示例

    死信隊(duì)列是一種特殊的消息隊(duì)列,用來存儲無法被正常消費(fèi)的消息,常被用來實(shí)現(xiàn)延遲處理,異常消息處理等,本文主要介紹了springboot中RabbitMQ死信隊(duì)列的實(shí)現(xiàn)示例,感興趣的可以了解一下
    2024-01-01
  • spring中EventListener的使用方式

    spring中EventListener的使用方式

    這篇文章主要介紹了spring中EventListener的使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • shiro攔截認(rèn)證的全過程記錄

    shiro攔截認(rèn)證的全過程記錄

    Apache?Shiro是一個強(qiáng)大且易用的Java安全框架,執(zhí)行身份驗(yàn)證、授權(quán)、密碼和會話管理,下面這篇文章主要給大家介紹了關(guān)于shiro攔截認(rèn)證的相關(guān)資料,需要的朋友可以參考下
    2021-11-11
  • Java在線打開word文檔并強(qiáng)制留痕的方法

    Java在線打開word文檔并強(qiáng)制留痕的方法

    在本篇文章里小編給讀者們分享的是關(guān)于Java在線打開word文檔并強(qiáng)制留痕的方法,對此有需要的朋友們可以學(xué)習(xí)下。
    2020-01-01
  • SpringBoot為啥不用配置啟動類的實(shí)現(xiàn)

    SpringBoot為啥不用配置啟動類的實(shí)現(xiàn)

    這篇文章主要介紹了SpringBoot為啥不用配置啟動類的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • SpringBoot中使用Redis作為全局鎖示例過程

    SpringBoot中使用Redis作為全局鎖示例過程

    這篇文章主要為大家介紹了SpringBoot中使用Redis作為全局鎖示例過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-03-03

最新評論