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ì)附上源地址)。
有什么意見碧浊、見解或疑惑涂邀,歡迎留言討論瘟仿。