ObjectId的選擇
創建MongoDB文檔時,如果沒有賦值ID,系統會自動幫你創建一個,通常會在客戶端由驅動程序完成。得到的ObjectId類似於這種

ObjectId使用12字節的存儲空間,每個字節兩位十六進制數字,是一個24位的字符串。其含義分別代表時間戳、機器碼、PID、計數器。時間戳是文檔創建時的時間,只是從十進制轉化成了十六進制。機器碼是生成文檔主機的ID,為了區分多主機而生成的。PID則是區分同主機下不同mongoDB進程產生的,同樣防止沖突。前面的9個字節是保證了一秒內不同機器不同進程生成ObjectId不沖突,最后的3個字節是一個自動增加的計數器,用來確保在同一秒內產生的ObjectId也不會沖突,允許256的3次方等於16777216條記錄的唯一性。

顯然系統生成的ObjectID已經很嚴謹了,但是在選擇系統創建還是程序創建id上,經過網上查找的一些資料,得到的結論是盡量采用程序創建的方式,速度、可讀性、可維護性都要強於系統創建。
雖然ObjectId 設計成輕量型的,易於生成,但是畢竟生成的時候還是產生開銷。在客戶端生成體現了MongoDB 的設計理念:能從服務器端轉移到驅動程序來做的事,就盡量轉移。這種理念背后的原因是,即便是像MongoDB 這樣的可擴展數據庫,擴展應用層也要比擴展數據庫層容易得多。將事務交由客戶端來處理,就減輕了數據庫擴展的負擔。
在客戶端生成ObjectId,驅動程序能夠提供更加豐富的API。例如,驅動程序可以有自己的insert 方法,可以返回生成的ObjectId,也可以直接將其插入文檔。如果驅動程序允許服務器生成ObjectId,那么將需要單獨的查詢,以確定插入的文檔中的"_id" 值。
設計思路
創建一個序列計數的文檔,記錄所有文檔的名稱和序列值,序列值設置默認0,每次進行插入操作的時候,序列值+1,作為本次操作的id。
程序實現
開發環境:IntelliJ IDEA+JAVA8+SpringBoot
1 創建序列計數類,用於存儲各文檔以及文檔序列值。

2 自定義注解

3 定義實體類,自己要實現自增的實體類(get、set),與文檔一一對應

4 定義監聽類SaveEventListener。重寫save方法。在每次存儲時候進行主鍵自增

5.然后測試(中間的111113刪除掉了)
注意
如果根據主鍵查詢的話要進行轉化:
到此,MongoDB的主鍵自增就完成了。
總結
經過測試,以上流程沒有問題,會得到期望的結果,但是有以下幾點需要注意:
(1)為什么我在Student類中為主鍵賦了一個默認值0L?
答:我在此自增方式原作者文章中發現這么一句,“注意自增ID的類型不要定義成Long這種包裝類,mongotemplate的源碼里面對主鍵ID的類型有限制”。測試后發現,如果ID定義為原生類型確實是沒有問題的。當ID定義為包裝類的情況下,如果在onBeforeConvert方法之前沒有給ID設置值,是會報錯的,我猜測可能是因為內部轉換類型時如果ID是空值而無法轉換引起的,因此,我賦了一個默認值,這樣就不會報錯了,包裝類也可以使用(不過這樣好像跟原生類型就沒什么區別了,沒什么意義)。
(2)這個監聽器會不會影響修改操作?
答:測試發現,不會影響,水平有限,本人也不知作何解釋,不要打我……
(3)這種方式會有並發問題嗎?
答:不會的!根據官方文檔說明,findAndModify一個原子性操作,不過有這么一句“When the findAndModify command includes the upsert: true option and the query field(s) is not uniquely indexed, the command could insert a document multiple times in certain circumstances.”,大概意思是說當查詢和更新兩個操作都存在時,如果查詢的字段沒有唯一索引的話,該命令可能會在某些情況下更新/插入 文檔多次,參考鏈接:戳我戳我。以上演示的是只存儲了集合所對應的實體類的短名稱,短名稱是會重復的,所以這種方法不妥,還是記錄長名稱吧
菜鳥程序員,若有需修正之處,望指正~