Spring-Data-MongoDB如何自定義id生成規(guī)則以及id自增示例

MongoDB默認(rèn)的ObjectId確實(shí)有其積極意義,但是有時(shí)候需求卻需要我們自定義id生成規(guī)則。
本文使用AOP注解的方式來實(shí)現(xiàn)id的自定義規(guī)則。
如果聲明在id字段上,那它就是自定義的id褒搔。也可以聲明在其他字段上阶牍,與ObjectId并存。


本文由作者三汪首發(fā)于簡(jiǎn)書星瘾。

原理分析

  • Spring Data MongoDB是有生命周期的走孽。通過繼承org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener類來實(shí)現(xiàn)監(jiān)聽。
    本文中產(chǎn)生id的動(dòng)作在監(jiān)聽到onBeforeConvert時(shí)觸發(fā)琳状。
    7個(gè)生命周期列舉如下:
    onBeforeConvert,onBeforeSave,onAfterSave,onAfterLoad,onAfterConvert,onAfterDelete,onBeforeDelete

  • 通過自定義注解標(biāo)識(shí)根據(jù)自定義規(guī)則賦值的字段磕瓷。通過org.springframework.util.ReflectionUtils提供的反射功能來獲取被自定義注解標(biāo)識(shí)的字段

  • 實(shí)現(xiàn)根據(jù)數(shù)據(jù)庫(kù)中已存在的id生成新的id(自增),使用單例控制并發(fā)念逞。

示例

注意:

  • 本例中自定義的id規(guī)則為:年月日+序號(hào)困食。如:2017112801,2017112899
    示例中沒有對(duì)自定義id的長(zhǎng)度做控制。因此會(huì)出現(xiàn)20171128100的情況翎承。
    如果需要做控制硕盹,請(qǐng)自行在保存方法中做校驗(yàn)。
  • 本文中自定義id的類型為String叨咖。
    根據(jù)所參考的博文作者的說法瘩例,自增ID的類型不能定義成Long這種包裝類。
    Mongotemplate的源碼里面對(duì)主鍵ID的類型有限制甸各。只能定義為原生類型垛贤。

代碼:

ProjectDomain.java(實(shí)體類)
@ProjectCode即本例中自定義的注解。)
@Getter @Setter @NoArgsConstructor是Lombok提供的注解趣倾。如有疑問請(qǐng)自行查閱Lombok相關(guān)知識(shí))
@ApiModelProperty是Swagger提供的注解聘惦。如果未使用Swagger請(qǐng)忽略)

@Document
@Getter
@Setter
@NoArgsConstructor
public class ProjectDomain {

    @Id 
    @ProjectCode
    @ApiModelProperty(value = "項(xiàng)目編號(hào)")
    private String code;//code字段在數(shù)據(jù)庫(kù)中還是會(huì)以_id的名字存在
    
    @ApiModelProperty(value = "項(xiàng)目名稱")
    @Field(value="name")
    private String name;

    //其余字段略
}

ProjectCode.java(自定義注解類)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)  
@Retention(RetentionPolicy.RUNTIME)  
public @interface ProjectCode {

}

CreateProjectCode.java( 生成規(guī)則定義和實(shí)際生成在此類中)

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import com.wolfgy.demo.domain.ProjectDomain;

@Component
class CreateProjectCode {
    
    private static CreateProjectCode createProjectCode = null;
    private CreateProjectCode(){}
    
    static synchronized CreateProjectCode getInstance(){
        if(createProjectCode == null){
            synchronized (CreateProjectCode.class) {
                if(createProjectCode == null){
                    createProjectCode = new CreateProjectCode();
                }
            }
        }
        return createProjectCode;
    }
    
    synchronized String createProjectCode(MongoTemplate template){
        LocalDate date = LocalDate.now();
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMdd");
        String dateStr = date.format(fmt);
        Query query = new Query(Criteria.where("code").regex(dateStr+"\\d*")).with(new Sort(Direction.DESC,"code"));
        List<ProjectDomain> list = template.find(query, ProjectDomain.class);
        if (list != null && !list.isEmpty()) {
            String numStr = list.get(0).getCode().substring(8);
            Integer numInteger = new Integer(numStr);
            int num = numInteger.intValue();
            return dateStr+( ++num<10 ? "0"+num : num ); 
        }else{
            return dateStr+"01";
        }
    }
}

SaveEventListener.java(事件監(jiān)聽類)

import java.lang.reflect.Field;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import com.wolfgy.demo.domain.ProjectDomain;

@Component
public class SaveEventListener extends AbstractMongoEventListener<ProjectDomain>{
    
    @Autowired
    private MongoTemplate template;
    
    @Override
    public void onBeforeConvert(BeforeConvertEvent<ProjectDomain> event) {
        final Object source = event.getSource();
        if (source != null) {
            ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {               
                @Override
                public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                    ReflectionUtils.makeAccessible(field);
                    if (field.isAnnotationPresent(ProjectCode.class)) {//判斷字段是否被自定義注解標(biāo)識(shí)
                        field.set(source,CreateProjectCode.getInstance().createProjectCode(template));//設(shè)置id
                    }
                }
            });
        }
    }
    

}

參考內(nèi)容


以上。
希望我的文章對(duì)你能有所幫助儒恋。
我不能保證文中所有說法的百分百正確善绎,
但我能保證它們都是我的理解和感悟以及拒絕直接復(fù)制黏貼(確實(shí)需要引用的部分我會(huì)附上源地址)。
有什么意見碧浊、見解或疑惑涂邀,歡迎留言討論瘟仿。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末箱锐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劳较,更是在濱河造成了極大的恐慌驹止,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件观蜗,死亡現(xiàn)場(chǎng)離奇詭異臊恋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)墓捻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門抖仅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事撤卢』吩洌” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵放吩,是天一觀的道長(zhǎng)智听。 經(jīng)常有香客問我,道長(zhǎng)渡紫,這世上最難降的妖魔是什么到推? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮惕澎,結(jié)果婚禮上莉测,老公的妹妹穿的比我還像新娘。我一直安慰自己唧喉,他們只是感情好悔雹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著欣喧,像睡著了一般腌零。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唆阿,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天益涧,我揣著相機(jī)與錄音,去河邊找鬼驯鳖。 笑死闲询,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的浅辙。 我是一名探鬼主播扭弧,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼记舆!你這毒婦竟也來了鸽捻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤泽腮,失蹤者是張志新(化名)和其女友劉穎御蒲,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诊赊,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厚满,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碧磅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碘箍。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遵馆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丰榴,到底是詐尸還是另有隱情团搞,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布多艇,位于F島的核電站逻恐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏峻黍。R本人自食惡果不足惜复隆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姆涩。 院中可真熱鬧挽拂,春花似錦、人聲如沸骨饿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宏赘。三九已至绒北,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間察署,已是汗流浹背闷游。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留贴汪,地道東北人脐往。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像扳埂,于是被迫代替她去往敵國(guó)和親业簿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容