Spring-Data-MongoDB如何自定义id生成规则以及id自增示例

MongoDB默认的ObjectId确实有其积极意义,但是有时候需求却需要我们自定义id生成规则。
本文使用AOP注解的方式来实现id的自定义规则。
如果声明在id字段上,那它就是自定义的id。也可以声明在其他字段上,与ObjectId并存。


本文由作者三汪首发于。

原理分析

  • Spring Data MongoDB是有生命周期的。通过继承org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener类来实现监听。
    本文中产生id的动作在监听到onBeforeConvert时触发。
    7个生命周期列举如下:
    onBeforeConvert,onBeforeSave,onAfterSave,onAfterLoad,onAfterConvert,onAfterDelete,onBeforeDelete

  • 通过自定义注解标识根据自定义规则赋值的字段。通过org.springframework.util.ReflectionUtils提供的反射功能来获取被自定义注解标识的字段

  • 实现根据数据库中已存在的id生成新的id(自增),使用单例控制并发。

示例

注意:

  • 本例中自定义的id规则为:年月日+序号。如:2017112801,2017112899
    示例中没有对自定义id的长度做控制。因此会出现20171128100的情况。
    如果需要做控制,请自行在保存方法中做校验。
  • 本文中自定义id的类型为String。
    根据所参考的博文作者的说法,自增ID的类型不能定义成Long这种包装类。
    Mongotemplate的源码里面对主键ID的类型有限制。只能定义为原生类型。

代码:

ProjectDomain.java(实体类)
@ProjectCode即本例中自定义的注解。)
@Getter @Setter @NoArgsConstructor是Lombok提供的注解。如有疑问请自行查阅Lombok相关知识)
@ApiModelProperty是Swagger提供的注解。如果未使用Swagger请忽略)

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

    @Id 
    @ProjectCode
    @ApiModelProperty(value = "项目编号")
    private String code;//code字段在数据库中还是会以_id的名字存在
    
    @ApiModelProperty(value = "项目名称")
    @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( 生成规则定义和实际生成在此类中)

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 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(事件监听类)

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{
    
    @Autowired
    private MongoTemplate template;
    
    @Override
    public void onBeforeConvert(BeforeConvertEvent 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)) {//判断字段是否被自定义注解标识
                        field.set(source,CreateProjectCode.getInstance().createProjectCode(template));//设置id
                    }
                }
            });
        }
    }
    

}

参考内容

  • MongoDB进阶(九)Java中实现MongoDB自增主键ID

以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,
但我能保证它们都是我的理解和感悟以及拒绝直接复制黏贴(确实需要引用的部分我会附上源地址)。
有什么意见、见解或疑惑,欢迎留言讨论。

你可能感兴趣的:(Spring-Data-MongoDB如何自定义id生成规则以及id自增示例)