mongodb评论功能实现
对应的项目在我的下载里
一、mongodb 的优势与劣势
特点:
MongoDB目前3大核心优势:『灵活模式』+ 『高可用性』 + 『可扩展性』,通过json文档来实现灵活模式,通过复制集来保证高可用,通过Sharded cluster来保证可扩展性。
应用场景:
- 业务需要事务,使用mysql,因为mongodb不支持事务
- 数据量大,但是数据本身价值不大,使用mongodb
ps:加载大量低价值的业务数据 - 数据是非结构化的,且数据量大,使用mongodb
- 业务未来走向不明确,使用mongodb,方便扩展
优势:
- 不存在SQL注入
- 不需要提前创建表,可以直接写入数据
- 字段数据格式自由 比如mysql中id 字段是数字,输入字符串会出错,mongodb不会
- 可以处理json结构,并对其进行处理
比如 字段a的值为
{“a”:11,“b”:12,“c”:“abc”,“d”:[1,2,3]}
你可以直接去读取或设置a字段的b值 a.b,读取a字段d数组的第二个值:a.d.1,可以去删除a字段的a数据$unset:{“a.a”:1},就会变成:
{“b”:12,“c”:“abc”,“d”:[1,2,3]} - 可以使用upsert操作,修改的数据不存在的时候直接插入该数据
- 查询和插入效率在没有索引的情况下远大于mysql
缺点:
- mongodb是nosql数据库,关系能力薄弱,不能使用join,union来联合查找
- 事务能力薄弱
- 效率存在波动性,不是很稳定
MongoDB可以将模式设计划分为内嵌模式和 引用模式
内嵌模式
简单来讲,内嵌模式就是将关联数据,放在一个文档中。例如以下员工信息采用内嵌模式了而存储在了一个文档中:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OOSo6Eoo-1599922848721)(img\mongo01.png)]
根据上面的描述可以看出,内嵌模型可以给应用程序提供很好的数据查询性能,因为基于内嵌模型,可以通过一次数据库操作得到所有相关的数据。同时,内嵌模型可以使数据更新操作变成一个原子写操作。然而,内嵌模型也可能引入一些问题,比如说文档会越来越大,这样就可能会影响数据库写操作的性能,还可能会产生数据碎片
我们下面的demo就采用内嵌模型实现。
引用模式
引用模式是将数据存储在不同集合的文档中,而通过关系数据进行关联。例如,这里采用引用模式将员工信息存储在了3个文档中,基本信息一个文档,联系方式一个文档,登录权限放在了一个文档中。每个文档之前通过user_id来关联。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5P6Dw3Aj-1599922848724)(img\mongo02.png)]
使用内嵌模型往往会带来数据的冗余,却可以提升数据查询的效率。但是,当应用程序基本上不通过内嵌模型查询,或者说查询效率的提升不足以弥补数据冗余带来的问题时,我们就应该考虑引用模型了。
二、评论实现
1.数据库设计
(1)自由模式,无需提前声明、创建表结构,即不用先创建表、添加字段,然后才可以Insert数据。默认情况下MongoDB无需这样操作。
(2)键值类型自由,MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。字段值可以包含其他文档,数组及文档数组。
按照业务来慢慢添加,整个集合的存储文档格式就可以形成了。
一般现在最常见的和最实用的都是二级评论,所以这里用二级评论举例
一级评论, 文章id,用户id,用户评论内容,用户评论时间,评论回复
其中回复即二级评论,
包括 评论者id,评论者昵称,评论者评论内容,评论时间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vP9eYbX3-1599922848727)(C:\Users\hasee\Desktop\mongodb实现评论\img\mongo03.png)]
这里就遇到一个问题,二级评论可不可以被评论,如果不可以,显然不合理。
所以这里仿照微博,在遇到二级评论的评论时,通过@确定回复的用户,内容全部和二级评论在同一级显示。
因此我们设计出这样一个数据结构(此时二级评论和二级评论的回复是不同级的)
{
"_id" : ObjectId("597aa23add840cd4ce0681d1"),
"comment_blog_id" : "ObjectId("597aa23add840213432rdsfsed1")",
"comment_user_id" : "100001",
"comment_user_name": "刘德宝",
"comment_user_img": "图片地址",
"comment_content" : "1号用户的评论",
"create_time" : "2017-15-12 14:00",
"comment_responses" : [
{
"_id" : ObjectId("597aa23add840cd4ce0681d1"),
"response_user_id" : "1000002",
"response_user_name" : "朱秀秀",
"response_user_img" : "图片地址",
"response_content" : [
"这是2号用户给刘德宝的评论",
],
"create_time" : "2017-15-12 14:00",
},
{
"response_user_id" : "1000005",
"response_user_name" : "小火龙",
"response_user_img" : "图片地址",
"response_content" : [
"这是5号用户给刘德宝的回复",
],
"create_time" : "2017-15-12 14:00",
]
}
]
}
其中的评论和回复的id,昵称,和头像是方便查询显示使用,并且id可以用来做消息推送
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jVWQEmhf-1599922848730)(\img\mongo11.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mj8uDoGF-1599922848731)(\img\mongo10.png)]
评论全部不存在。
一级评论未存在,全空,最下方有一级评论回复框,可以进行评论产生comment
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RNRRrubS-1599922848735)(\img\mongo09.png)]
一级评论存在,没有回复,点击回复,出现回复框,完成回复,即二级评论,可以回复,产生comment.response
同理对二级评论的回复也是通过点击生成回复框,然后进行回复。然后判断是否是回复的一级评论来控制是否显示回复两个字即可。
所以和二级评论实现是同一个逻辑,前台即可完成。
2.spring整合mongodb
整合部分最难的就是配置和熟悉对mongodb数据库的操作,其他的前台传参和逻辑编写大家都很熟悉。这里主要介绍一下mongodb操作的坑
mongoRepository和mongoTemplate
①、Spring Data MongoDB 是 Spring 框架访问 MongoDB 数据库的分支,使用它可以非常方便地操作 MongoDB 数据库。
Spring Data MongoDB 是 Spring Source 的一个子项目,旨在为关系型数据库、非关系型数据、Map-Reduce框架、云数据服务等等提供统一的数据访问API。
Spring Data 提供了基于Repository的统一接口 MongoRepository 完成对象的 CRUD 操作以及查询方法、排序和分页方法等。
使用 Spring Data 可以帮助我们快速构建项目,非常方便,Spring Data 在数据持久层已经写好了常用功能,我们只需要定义一个接口去继承 Spring Data 提供的接口,就可以实现对数据库的操作,也可以自定义接口方法,甚至这些自定义方法都不需要我们手动去实现,Reposity 会自动完成实现。
实现方法:
1.创建自定义接口 xxxRepository ,继承 MongoRepository,这样xxxRepository 就直接拥有了MongoRepository 中定义好的基本CRUD操作,同时可以完成方法扩展,只需要在xxxRepository 中按照规则声明抽象方法即可,MongoRepository 会自动完成方法实现,同时在类定义处添加@Repository 注解完成IoC注入。
2.创建自定义接口 xxxService 以及实现类xxxServiceImpl,并通过自动装载将xxxRepository 注入xxxServiceImpl。
3.创建 xxxController 实现相关业务方法。
spring-data-mongodb其实有两种实现方式,一种是直接继承MongoRepository接口,dao层的实现,默认提供了CRUD的各种方法,几乎不用编写任何代码。另一种是通过MongoTemplate来操作数据库,这样需要自定义dao层接口,默认没有任何接口可以使用
Criteria类:它封装所有的语句,以方法的形式进行查询。
Query类:这是将语句进行封装或者添加排序之类的操作。(实现分页操作就在这里)
mongoTemplate操作简介
1.添加依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
添加spring和mongodb的依赖
网上都说要加一个mongo的依赖类似于这种
org.mongodb mongo-java-driver 2.13.02.mongodb配置文件
mongodb.properties
#数据库名称
mongo.dbname=sag
#用户名
mongo.username=
#密码
mongo.password=
#主机
mongo.host=127.0.0.1
#端口
mongo.port=27017
#线程最大阻塞数
mongo.connectionsPerHost=8
#线程队列数mongo.threadsAllowedToBlockForConnectionMultiplier=4
#连接超时时间,单位毫秒
mongo.connectTimeout=1500
#最大等待时间
mongo.maxWaitTime=1500
#自动连接
mongo.autoConnectRetry=true
#socket存活
mongo.socketKeepAlive=true
#socket超时时间
mongo.socketTimeout=1500
#读写分离
mongo.slaveOk=true
spring-mongodb.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
<!--引入MongoDB连接文件-->
<context:property-placeholder location="classpath:mongodb.properties" ignore-unresolvable="true"/>
<!--连接MongoDB服务器-->
<!-- 除此之外还有一个mongo连接,标签为<mongo-mongo> 版本较低时使用 -->
<mongo:mongo-client id="mongo" host="${mongo.host}" port="${mongo.port}" >
<mongo:client-options
connections-per-host="${mongo.connectionsPerHost}"
threads-allowed-to-block-for-connection-multiplier=
"${mongo.threadsAllowedToBlockForConnectionMultiplier}"
connect-timeout="${mongo.connectTimeout}"
max-wait-time="${mongo.maxWaitTime}"
socket-keep-alive="${mongo.socketKeepAlive}"
socket-timeout="${mongo.socketTimeout}"/>
</mongo:mongo-client>
<bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
<!-- 去掉默认的_class属性 -->
<bean id="customMongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper"> <constructor-arg name="typeKey"><null/></constructor-arg>
</bean>
<bean id="mappingMongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<constructor-arg name="mappingContext" ref="mappingContext" />
<property name="typeMapper" ref="customMongoTypeMapper" />
</bean>
<!-- mongo的工厂,通过它来取得mongo实例,dbname为mongodb的数据库名,没有的话会自动创建 -->
<mongo:db-factory id="mongoDbFactory" dbname="${mongo.dbname}" mongo-ref="mongo"/>
<!-- 配置mongoTemplate -->
<!-- mongodb的主要操作对象,所有对mongodb的增删改查的操作都是通过它完成-->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate"> <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mongoConverter" ref="customMongoTypeMapper"></constructor-arg>
</bean>
</beans>
在Spring框架中,对mongodb进行操作的是mongoTemplate对象。
MongoTemplate是数据库和代码之间的接口,对数据库的操作都在它里面。
注:MongoTemplate是线程安全的。
在实现评论demo之前,我们先插入两个例子,感受一下mongotemplate
这里我建了一个POJO, comment
@Document(collection = "commenttest")
public class Comment {
//@id
private String comment_id; //评论id
private String comment_blog_id; //文章id
private String comment_user_id; //评论用户id
private String comment_user_name; //评论用户名
private String comment_user_img; //评论用户头像
private String comment_content; //评论内容
private String comment_create_time; //评论时间
private String comment_responses; //评论回复
}
然后相应的创建了一个CommentTestRepository类
@Repository
@Document(collection = "commenttest")
public class CommentTestRepository {
@Autowired
MongoTemplate mongoTemplate;
public CommentTestRepository() {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring/spring-mongodb.xml");
mongoTemplate = (MongoTemplate) ac.getBean("mongoTemplate");
}
public void insertComment(Comment comment) {
List<Comment> list = new ArrayList<Comment>();
list.add(comment);
mongoTemplate.insert(list,Comment.class);
}
http 通过 controller 进来的 mongotemplate 对象才会注入 spring ,才能正常使用,如果是通过其他 controller 类来调用同层的 controller 里面的 mongotemplate ,则该对象是 null 值,不可使用 !而从 controller 调用 service 这种不同层的,则可以正常使用!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJoQEQ8O-1599922848736)(\img\mongo04.png)]
插入成功后,可以看到两个字段与我们预期的不太一样,_ id和_ class
_id:是系统自动生成的12字节唯一标识。满足分布式
mongodb采用了一个称之为ObjectId的类型来做主键。ObjectId是一个12字节的 BSON 类型字符串。按照字节顺序,一次代表:
4字节:UNIX时间戳
3字节:表示运行MongoDB的机器
2字节:表示生成此_id的进程
3字节:由一个随机数开始的计数器生成的值
https://blog.csdn.net/xiamizy/article/details/41521025
不使用ObjectId方法:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NoDJwLHu-1599922848738)(\img\mongo05.png)]
只需要把POJO实体类里的comment_id改名为id即可完成一种类似于覆盖的效果,达到了不使用objectId的目的。
或者在想要标识为id的属性上加上注解@id
_class:这个字段就是用来映射Pojo的,更具体的说,是为了方便处理Pojo中存在继承的情况,增加系统的扩展性的。去掉的方法就是配置Converter,默认映射为null
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nAT38MNK-1599922848739)(\img\mongo08.png)]
(
假如到时候设计不好,字段不确定怎么办。
个人建议是刚开始少一点,后面可以再加。
映射的时候,pojo里有的属性,数据库里没有,依然可以映射
数据库里有的字段,而pojo里没有对应的属性接受的话就无法完成映射了,
比如我用户头像刚开始没加,只需要在对应的pojo类里,插入和查询逻辑里添加用户头像的属性和逻辑,就可以完成拓展,并不用像传统数据库再创建一个列,或者费心思再去创建几个冗余列。这就体现了mongodb的高拓展性。
)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mC3dN0ro-1599922848740)(\img\mongo06.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Wj2EgQ2-1599922848740)(\img\mongo07.png)]
评论要点:
内嵌对象,比如comment对象里有一个response对象。那么mongotemplate映射到mongodb里的时候,对这个是怎么操作的呢。
根据实际情况,一级评论完毕,回复是在一级评论之后才有的操作,这个之前,一级评论文档中是没有任何回复的。所以这里检查了一下comment里如果没有这个键,或者这个键为空,可不可以插入到mongodb中。答案是可以的。
在测试的时候,尝试了两种方案。
1.在设置comment实例的时候,把对象设置为null
comment.setComment_responses(null);
2.不设置该属性,直接插入
结论:两者都不会插入该属性对应的数据。也不会存在这个属性对应的键值。不会自动映射进去
到时候要往comment里插入回复的时候,直接使用mongotemplate.upsert语句就可以完成。
如果查到一级评论没有任何回复,它就会创建response这个键,然后插入第一条记录
如果一级评论已经有回复,它就会插入到这个键里,成为这个键下的一个文档。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZU38yJlO-1599922848742)(\img\mongo12.png)]
mongoTemplate实现评论
通过手写插入一些假数据之后,我们开始查询,插入要和实际业务逻辑相对应,这里demo就不做了,大体思路是一级评论直接插入,二级评论,前台传入一级评论的id然后根据id插入,同时记录用户名和用户头像。
如果有,则前台显示有回复样式,没有的话,就是默认回复一级评论的二级评论。
查询实现
List<Comment> comments = mongoTemplate.find(query,Comment.class);
List<JSONObject> comments = mongoTemplate.find(query,JSONObject.class,"testMax2");
query:查询条件,
.class:需要查询的类型
最后的字符串是需要查询的Collection,
采用后面这个语句会遍历的把整个Collection查询出来,包括其下面的responses
也可以不写,默认的是.class上注解的,但是这里采用了JSONObject.class这个类进行查询,我并没有对这个类进行注解,所以后面这个集合要写。
整合springboot
配置
在spring官网添加mongodb即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x6xK6CGO-1599922848744)(C:\Users\hasee\Desktop\mongodb实现评论\img\mongo13.png)]
先入为主的导入了Mybatis但没有引入Mysql,所以这里要引入
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
其余的配置文件在打包上传的包里。
总结:
1.为什么评论要使用mongodb而不是mysql?
mongodb舍弃了事务管理,但是mongodb的功能是海量数据的查询很快,读写效率高,一般用于简单的sql查询或日志类数据的写入操作。
在评论的业务中,最多的就是查询评论和发表评论了。
BSON文档结构的存储形式,查询很方便,不需要关联查询。
其次,评论这种数据价值并不是特别大,但是量却很大,所以可以交给mongodb来存储
在这种不需要事务管理,也不需要关联查询的情况下,使用mongodb可以得到更快的响应速度,更快捷的开发,更少的数据库设计。
2.为什么使用了内嵌模型而不是引用模型?
一对很少 one-to-few 可以采用内嵌文档
优点:不需要单独执行一条语句去获取内嵌的内容
缺点:无法把这些内嵌文档当做单独的实体去访问
适用场合:一对很少且不需要单独访问内嵌内容