项目实训7
1. 背景
由于本项目中除了使用了mysql数据库,还使用了mongo数据库,但是在一些情况下,mongo数据库无法实现自增的id,所以打算借助java的监听器实现表中字段的自增
由于MongoDB本身并不维护自增ID,因此只能利用MongoDB的原子性通过findAndModify自增1来实现,思路就是
2. 实现过程
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
首先看一下项目中mongo对应的实体类
package com.example.guke.entity;
import com.example.guke.annotation.AutoDec;
import com.example.guke.annotation.AutoInc;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
import java.util.List;
/**
* @program: GuKe
* @description: 社区的帖子类,内含多个回复,类似于百度贴吧的形式
* @author: NiuYiq
* @date: 2022-04-04 16:30
**/
@Data
@Document(value = "post")
public class Post {
@AutoInc
private long postId; // 帖子的id
private String owner; // 帖子的发起人的所有者 --- openid
private String title; // 帖子的标题
private Date time; // 发布时间
// private List<CommentToPost> comments; // 用户跟帖
@AutoDec
private int commentCount; // 评论数量
private List<String> likes; // 由于使用的是nosql,所以这里的like不需要再分一个表出去了
// private List<String> annexes; // 博客提供的附件
}
package com.example.guke.entity;
import com.example.guke.annotation.AutoInc;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
import java.util.List;
/**
* 一级评论类
*/
@Data
@Document(value = "commentToPost")
public class CommentToPost {
@AutoInc
private long C2Pid; // 评论的id
private String owner; // 评论的用户的openid User.openid
private String content; // 用户评论的正文内容
private long belong; // 属于哪一条博客的评论 Post.id
private List<CommentToComment> comments; // 评论下的评论列表
private Date time; // 发布时间
private List<String> likes; // 点赞列表,存的是点赞用户的openid
}
以上等实体类需要用到id,便于进行使用,所以有定义@AutoInc的注解标识,便于在监听器中对其进行处理。
我们先看一下springboot中集成mongo的生命周期
AbstractMappingEventListener
中提供了以下回调方法:
方法 | 描述 |
---|---|
onBeforeConvert | 调用 MongoTemplate 的 insert 、insertList 和 save 操作,在通过 MongoConverter 将对象转换为文档之前的处理。 |
onBeforeSave | 调用 MongoTemplate 的 insert 、insertList 和 save 操作,在数据库中插入或保存文档之前的处理。 |
onAfterSave | 调用 MongoTemplate 的 insert 、insertList 和 save 操作,在数据库中插入或保存文档之后的处理。 |
onAfterLoad | 调用 MongoTemplate 中的 find 、findAndRemove 、findOne 和 getCollection 方法,从数据库检索文档后的处理。 |
onAfterConvert | 调用 MongoTemplate 中的 find 、findAndRemove 、findOne 和 getCollection 方法,从数据库检索文档被转换为 POJO 后的处理。 |
也就是说,只需要我们在其保存到mongo之前,对其自增id进行处理,就可以达到一种自增id的效果,所以重写的方法是onBeforeSave方法,在数据库中插入或保存文档之前的处理。
下面是监听器的代码,其中用到了java原生中的一些工具类进行处理,包括ReflectionUtils等工具类
package com.example.guke.listener;
import com.example.guke.annotation.AutoInc;
import com.example.guke.entity.Incr;
import com.example.guke.entity.Post;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeSaveEvent;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import java.util.Objects;
/**
* @program: GuKe
* @description: 对mongodb保存过程进行监听,实现主键的递增替换
* @author: NiuYiq
* @date: 2022-04-05 09:56
**/
@Slf4j
@Component
public class SaveMongoEventListener extends AbstractMongoEventListener<Object> {
private final MongoTemplate mongoTemplate;
@Autowired
public SaveMongoEventListener(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
/**
* 在保存的时候进行监听,通过反射对ID进行自增
* 此时MongoDB data已经将要操作的实体类转为Document{@link Document}对象,
* 所以这时候应该对document对象进行换ID
*
* @param event 事件响应
*/
@Override
public void onBeforeSave(BeforeSaveEvent<Object> event) {
// 得到操作的实体类对象
Object source = event.getSource();
//spring-mongo-data与MongoDB交互的document对象
Document document = event.getDocument();
// log.info(event.getCollectionName());
if (Objects.nonNull(source)) {
//利用反射进行相关操作
ReflectionUtils.doWithFields(source.getClass(), field -> {
//使操作的成员可访问 取消私有字段权限
ReflectionUtils.makeAccessible(field);
// 该字段是否使用自增注解且是Number类型
if (field.isAnnotationPresent(AutoInc.class) && field.get(source) instanceof Number) {
String collectionName = source.getClass().getSimpleName().substring(0, 1).toLowerCase()
+ source.getClass().getSimpleName().substring(1);
//判断document不能为空
Assert.notNull(document, "event.document must not be null");
//获取自增主键
Long incrId = getIncrId(collectionName);
//对ID进行替换
if(collectionName.equals("post")){
document.put("postId", incrId);
field.set(source, incrId);
}else if(collectionName.equals("commentToPost")){
// log.info(document.toString());
document.put("C2Pid", incrId);
field.set(source, incrId);
incCommentCount((Long) document.get("belong"));
}
}
});
}
super.onBeforeSave(event);
}
/**
* 返回下一个自增ID
*
* @param collectionName 集合名
* @return
*/
private Long getIncrId(String collectionName) {
Query query = new Query(Criteria.where("collectionName").is(collectionName));
Update update = new Update();
update.inc("incrId");
FindAndModifyOptions options = FindAndModifyOptions.options();
options.upsert(true);//没有就新增
options.returnNew(true);//返回最新
Incr andModify = mongoTemplate.findAndModify(query, update, options, Incr.class);
Assert.notNull(andModify, "主键自增时返回参数异常");
return andModify.getIncrId();
}
/*todo: 找到对应的collection 并对相应的字段递增*/
/**
* @name: incCommentCount
* @description: TODO 使用listener在回帖的时候自动将帖子的回帖数量+1
* @param postId 帖子的id
* @return: boolean
* @date: 2022/4/5 14:40
* @author: NiuYiq
*
*/
public boolean incCommentCount(Long postId){
Query query = new Query(Criteria.where("postId").is(postId));
Update update = new Update();
update.inc("commentCount");
FindAndModifyOptions options = FindAndModifyOptions.options();
options.upsert(true);// 没有就新增
options.returnNew(true);// 返回最新
Post andModify = mongoTemplate.findAndModify(query, update, options, Post.class);
Assert.notNull(andModify, "post自增时返回参数异常");
return true;
}
}
如此,便实现了mongo中自增id的一个效果。