MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。也可以说,副本集类似于有自动故障恢复功能的主从集群。
通俗的讲就是用多台机器进行同一数据的异步得进行同步,从而使多台机器拥有同一数据的多个副本,实现数据备份。并且当主库挂掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。
而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。
(1)数据冗余和数据可用性
复制提供冗余并提高数据可用性。 通过在不同数据库服务器上提供多个数据副本,复制可提供一定级别的容错功能,以防止丢失单个数据库服务器。
在某些情况下,复制可以提供增加的读取性能,因为客户端可以将读取操作发送到不同的服务上, 在不同数据中心维护数据副本可以增加分布式应用程序的数据位置和可用性。 您还可以为专用目的维护其他副本,例如灾难恢复,报告或备份。
(2)MongoDB中的复制
副本集是一组维护相同数据集的mongod实例。 副本集包含多个数据承载节点和可选的一个仲裁节点。在承载数据的节点中,一个且仅一个成员被视为主节点,而其他节点被视为次要(从)节点。
主节点接收所有写操作。 副本集只能有一个主要能够确认具有{w:“most”}写入关注的写入; 虽然在某些情况下,另一个mongod实例可能暂时认为自己也是主要的。主要记录其操作日志中的数据集的所有更改,即oplog。
辅助(副本)节点复制主节点的oplog并将操作应用于其数据集,以使辅助节点的数据集反映主节点的数据集。 如果主要人员不在,则符合条件的中将举行选举以选出新的主要人员。
(3)主从复制和副本集区别
主从集群和副本集最大的区别就是副本集没有固定的“主节点”;整个集群会选出一个“主节点”,当其挂
掉后,又在剩下的从节点中选中其他节点为“主节点”,副本集总有一个活跃点(主、primary)和一个或多
个备份节点(从、secondary)。
MongoDB副本集的特征:
副本集有两种类型和三种角色
两种类型:
三种角色:
您可以将额外的mongod实例添加到副本集作为仲裁者。 仲裁者不维护数据集。== 仲裁者的目的是通过
响应其他副本集成员的心跳和选举请求来维护副本集中的仲裁 ==。因为它们不存储数据集,所以仲裁器可以是提供副本集仲裁功能的好方法,其资源成本比具有数据集的全功能副本集成员更便宜。
如果您的副本集具有偶数个成员,请添加仲裁者以获得主要选举中的“大多数”投票。 仲裁者不需要专用
硬件。仲裁者将永远是仲裁者,而主要人员可能会退出并成为次要人员,而次要人员可能成为选举期间的主要人员。
如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
如果你的副本+主节点的个数是奇数,可以不加仲裁者。
Primary节点写入数据,Secondary通过读取Primary的oplog得到复制信息,开始复制数据并且将复制信息写入到自己的oplog。如果某个操作失败,则备份节点停止从当前数据源复制数据。如果某个备份节点由于某些原因挂掉了,当重新启动后,就会自动从oplog的最后一个操作开始同步,同步完成后,将信息写入自己的oplog,由于复制操作是先复制数据,复制完成后再写入oplog,有可能相同的操作会同步两份,不过MongoDB在设计之初就考虑到这个问题,将oplog的同一个操作执行多次,与执行一次的效果是一样的。简单的说就是:
当Primary节点完成数据操作后,Secondary会做出一系列的动作保证数据的同步:
1:检查自己local库的oplog.rs集合找出最近的时间戳。
2:检查Primary节点local库oplog.rs集合,找出大于此时间戳的记录。
3:将找到的记录插入到自己的oplog.rs集合中,并执行这些操作。
副本集的同步和主从同步一样,都是异步同步的过程,不同的是副本集有个自动故障转移的功能。其原理是:slave端从primary端获取日志,然后在自己身上完全顺序的执行日志所记录的各种操作(该日志是不记录查询操作的),这个日志就是local数据 库中的oplog.rs表,默认在64位机器上这个表是比较大的,占磁盘大小的5%,oplog.rs的大小可以在启动参数中设 定: --oplogSize 1000,单位是M。
注意:在副本集的环境中,要是所有的Secondary都宕机了,只剩下Primary。最后Primary会变成Secondary,不能提供服务。
节选自:https://blog.csdn.net/caiqiandu/article/details/89928151
首先安装MongoDB,官网下载地址:https://www.mongodb.com/try/download。
MongoDB所有历史版本下载:http://dl.mongodb.org/dl/linux/x86_64
上传压缩包到Linux中,解压到当前目录
tar -xvf mongodb-linux-x86_64-rhel70-5.0.4.tgz
移动解压后的文件夹到指定的目录中
mv mongodb-linux-x86_64-rhel70-5.0.4 /usr/local/mongodb
建立存放数据和日志的目录
mkdir -p /mongodb/replica_sets/myrs_27017/log \ &
mkdir -p /mongodb/replica_sets/myrs_27017/data/db
新建或修改配置文件:
vim /mongodb/replica_sets/myrs_27017/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/myrs_27017/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/mongodb/replica_sets/myrs_27017/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/myrs_27017/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,198.168.16.9
#bindIp
#绑定的端口
port: 27017
replication:
#副本集的名称
replSetName: myrs
注意如果是云服务器,198.168.16.9应该是内网ip
启动主节点服务:
/usr/local/mongodb/mongodb-linux-x86_64-rhel70-5.0.4/bin/mongod -f /mongodb/replica_sets/myrs_27017/mongod.conf
建立存放数据和日志的目录
mkdir -p /mongodb/replica_sets/myrs_27018/log \ &
mkdir -p /mongodb/replica_sets/myrs_27018/data/db
新建或修改配置文件:
vim /mongodb/replica_sets/myrs_27018/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/myrs_27018/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/mongodb/replica_sets/myrs_27018/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/myrs_27018/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,198.168.16.9
#bindIp
#绑定的端口
port: 27018
replication:
#副本集的名称
replSetName: myrs
启动副本节点服务:
/usr/local/mongodb/mongodb-linux-x86_64-rhel70-5.0.4/bin/mongod -f /mongodb/replica_sets/myrs_27018/mongod.conf
建立存放数据和日志的目录
mkdir -p /mongodb/replica_sets/myrs_27019/log \ &
mkdir -p /mongodb/replica_sets/myrs_27019/data/db
新建或修改配置文件:
vim /mongodb/replica_sets/myrs_27019/mongod.conf
systemLog:
#MongoDB发送所有日志输出的目标指定为文件
destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
path: "/mongodb/replica_sets/myrs_27019/log/mongod.log"
#当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
logAppend: true
storage:
#mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
dbPath: "/mongodb/replica_sets/myrs_27019/data/db"
journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
enabled: true
processManagement:
#启用在后台运行mongos或mongod进程的守护进程模式。
fork: true
#指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
pidFilePath: "/mongodb/replica_sets/myrs_27019/log/mongod.pid"
net:
#服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
#bindIpAll: true
#服务实例绑定的IP
bindIp: localhost,198.168.16.9
#bindIp
#绑定的端口
port: 27019
replication:
#副本集的名称
replSetName: myrs
启动副本节点服务:
/usr/local/mongodb/mongodb-linux-x86_64-rhel70-5.0.4/bin/mongod -f /mongodb/replica_sets/myrs_27019/mongod.conf
查看是否启动成功
ps aux|grep mongod
使用客户端命令连接主节点(27017节点):
/usr/local/mongodb/bin/mongo --host=10.0.16.9 --port=27017
连接上之后很多命令无法使用,比如show dbs,必须先初始化副本集。
初始化副本集的命令:
rs.initiate(configuration)
提示:
(1)“ok”的值为1,说明创建成功。
(2)命令行提示符发生变化,变成了一个从节点角色,此时默认不能读写。稍等片刻,回车,变成主节点。
查看副本集的配置内容的命令,返回包含当前副本集配置的文档:
rs.conf(configuration)
rs.config() 是该方法的别名。
configuration:可选,如果没有配置,则使用默认主节点配置。
说明:
(1) “_id” : “myrs” :副本集的配置数据存储的主键值,默认就是副本集的名字
(2) “members” :副本集成员数组,此时只有一个: “host” : “180.76.159.126:27017” ,该成员不
是仲裁节点: “arbiterOnly” : false ,优先级(权重值): “priority” : 1,
(3) “settings” :副本集的参数配置。
提示:副本集配置的查看命令,本质是查询的是 system.replset 的表中的数据
查看副本集状态的命令:
rs.status()
返回包含状态信息的文档。此输出使用从副本集的其他成员发送的心跳包中获得的数据反映副本集的当前状态。
myrs:PRIMARY> rs.status()
{
"set" : "myrs",
"date" : ISODate("2022-04-25T15:28:48.371Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"majorityVoteCount" : 1,
"writeMajorityCount" : 1,
"votingMembersCount" : 1,
"writableVotingMembersCount" : 1,
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1650900524, 1),
"t" : NumberLong(1)
},
"lastCommittedWallTime" : ISODate("2022-04-25T15:28:44.173Z"),
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1650900524, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1650900524, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1650900524, 1),
"t" : NumberLong(1)
},
"lastAppliedWallTime" : ISODate("2022-04-25T15:28:44.173Z"),
"lastDurableWallTime" : ISODate("2022-04-25T15:28:44.173Z")
},
"lastStableRecoveryTimestamp" : Timestamp(1650900504, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2022-04-25T15:25:34.074Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(1650900334, 1),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1650900334, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 1,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"newTermStartDate" : ISODate("2022-04-25T15:25:34.147Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2022-04-25T15:25:34.171Z")
},
"members" : [
{
"_id" : 0,
"name" : "10.0.16.9:27017",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 86280,
"optime" : {
"ts" : Timestamp(1650900524, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2022-04-25T15:28:44Z"),
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1650900334, 2),
"electionDate" : ISODate("2022-04-25T15:25:34Z"),
"configVersion" : 1,
"configTerm" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
}
],
"ok" : 1,
"$clusterTime" : {
"clusterTime" : Timestamp(1650900524, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
},
"operationTime" : Timestamp(1650900524, 1)
}
说明:
在主节点添加从节点,将其他成员加入到副本集,命令如下:
rs.add(host,arbiterOnly)
将27018的副本节点添加到副本集中,成功响应示例:
查看副本集状态rs.status()
说明: “name” : “180.76.159.126:27018” 是第二个节点的名字,其角色是 “stateStr” :
“SECONDARY”
rs.addArb("10.0.16.9:27019")
添加仲裁节点一直无响应,很长时间后返回错误信息
“errmsg” : “Reconfig attempted to install a config that would change the implicit default write concern. Use the setDefaultRWConcern command to set a cluster-wide write concern and try the reconfig again.”
需要 在主节点MongoDB中设置如下信息,具体命令如下:
db.adminCommand({
"setDefaultRWConcern" : 1,
"defaultWriteConcern" : {
"w" : 2
}
})
查看主节点状态的members项可以看到已经添加了仲裁从节点。其角色是 “stateStr” : “ARBITER”。
登录主节点27017,写入和读取数据:
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光 明媚","userid":"1001","nickname":"Rose","createdatetime":new Date()});
登录从节点27018
可以看到我们无法读取集合的数据。当前从节点只是一个备份,不是奴隶节点,无法读取和写入数据。因为在默认情况下,从节点是没有读写权限的,我们需要通过设置增加读权限。
设置读权限的命令(设置为奴隶节点,允许从成员上运行读的操作)
rs.slaveok()
或者
rs.slaveOk(true)
该命令是db.getMongo.setSlaveOk()的简化命令
可以看到往后版本支持rs.secondaryOk()
替代使用rs.slaveok()
。现在副本从节点已经可以执行查询命令,但是不允许插入。
现在已经实现读写分离,可以让主节点插入数据,使用从节点来读取数据。
取消奴隶节点的命令:rs.slaveOk(false)
仲裁从节点,不存放任何业务数据,只存放副本集配置等数据
MongoDB在副本集中,会自动进行主节点的选举,主节点选举的触发条件:
一旦触发选举,就要根据一定规则来选主节点。
选举规则是根据票数来决定谁获胜:
在获得票数的时候,优先级(priority)参数影响重大。可以通过设置优先级(priority)来设置额外票数。优先级即权重,取值为0-1000,相当于可额外增加0-1000的票数,优先级的值越大,就越可能获得多数成员的投票(votes)数。指定较高的值可使成员更有资格成为主要成员,更低的值可使成员更不符合条件。默认情况下,优先级的值是1
可以看出,主节点和副本节点的优先级各为1,即,默认可以认为都已经有了一票。但选举节点,优先
级是0,(选举节点的优先级必须是0,不能是别的值。即不具备选举权,但具有投票权)
(1) 先将配置导入cfg变量
cfg=rs.conf()
(2) 然后修改值(ID号默认从0开始)
cfg.members[1].priority=2
(3) 重新加载配置
rs.reconfig(cfg)
稍等片刻会重新开始选举。
关闭27018副本节点,主节点和仲裁节点对27018的心跳失败。因为主节点还在,因此,没有触发投票选举。
在主节点写入数据
db.comment.insert({"_id":"2","articleid":"100002","content":"我们不应该把清晨浪费在 手机上,健康很重要,一杯温水幸福你我他。","userid":"1002","nickname":"相忘于江 湖","createdatetime":new Date("2019-08- 05T22:08:15.522Z"),"likenum":NumberInt(1000),"state":"1"})
再启动从节点,会发现,主节点写入的数据,会自动同步给从节点。
如果此时,在主节点写入数据。
关闭27017节点后发现,从节点和仲裁节点对27017的心跳失败,当失败超过10秒,此时因为没有主节点了,会自动发起投票。
而副本节点只有27018,因此,候选人只有一个就是27018,开始投票。27019向27018投了一票,27018本身自带一票,因此共两票,超过了“大多数”27019是仲裁节点,没有选举权,27018不向其投票,其票数是0.最终结果,27018成为主节点。具备读写功能。在27018写入数据查看。
db.comment.insert({"_id":"2","articleid":"100001","content":"我夏天空腹喝凉开水,冬 天喝温开水","userid":"1005","nickname":"伊人憔悴","createdatetime":new Date("2019- 08-05T23:58:51.485Z"),"likenum":NumberInt(888),"state":"1"})
再启动27017节点,发现27017变成了从节点,27018仍保持主节点。登录27017节点,发现是从节点了,数据自动从27018同步。从而实现了高可用。
先关掉仲裁节点27019,关掉现在的主节点27017。
登录27018节点后发现,27018仍然是从节点,副本集中没有主节点了,导致此时副本集是只读状态,无法写入。
为啥不选举了?因为27018的票数,没有获得大多数,即没有大于等于2,它只有默认的一票(优先级是1)如果要触发选举,随便加入一个成员即可。
先关掉仲裁节点27019,关掉现在的副本节点27018。10秒后,27017主节点自动降级为副本节点。(服务降级)副本集不可写数据了,已经故障了。
如果使用云服务器需要修改配置中的节点ip为公网ip
var config = rs.config();
config.members[0].host="180.76.159.126:27017";
config.members[1].host="180.76.159.126:27018";
config.members[2].host="180.76.159.126:27019";
rs.reconfig(config)
MongoDB客户端连接语法:
mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]] [/[database][?options]]
连接MongoDB副本集数据源的语法:
mongodb://host1,host2,host3/articledb? connect=replicaSet&slaveOk=true&replicaSet=副本集名字
其中:
连接 replica set 三台服务器 (端口 27017, 27018, 和27019),直接连接第一个服务器,无论是replica
set一部分或者主服务器或者从服务器,写入操作应用在主服务器 并且分布查询到从服务器。
(1)配置环境
创建Spring Boot项目添加如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
修改配置文件application.properties
spring.data.mongodb.uri=mongodb://101.43.30.7:27017,101.43.30.7:27018,101.43.30.7:27019/artibledb?connect=replicaSet&slaveOk=true&replicaSet=myrs
主机必须是副本集中所有的主机,包括主节点、副本节点、仲裁节点。SpringDataMongoDB自动实现了读写分离。
(2)编写代码
创建实体类Comment
@Document(collection = "comment")
public class Comment implements Serializable {
//主键标识,自动与MongoDB的主键字段“_id”对应
@Id
private String id;
private String content;
private Date publishtime;
//添加一个单字段索引
@Indexed
private String userid;
private String nickname;//昵称
private LocalDateTime createdatetime;//评论的日期时间
private Integer likenum;//点赞数
private Integer replynum;//回复数
private String state;//状态
private String parentid;//上级ID
private String articleid;
public String getArticleid() {
return articleid;
}
public void setArticleid(String articleid) {
this.articleid = articleid;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Comment{" +
"id='" + id + '\'' +
", content='" + content + '\'' +
", publishtime=" + publishtime +
", userid='" + userid + '\'' +
", nickname='" + nickname + '\'' +
", createdatetime=" + createdatetime +
", likenum=" + likenum +
", replynum=" + replynum +
", state='" + state + '\'' +
", parentid='" + parentid + '\'' +
", articleid='" + articleid + '\'' +
'}';
}
public Date getPublishtime() {
return publishtime;
}
public void setPublishtime(Date publishtime) {
this.publishtime = publishtime;
}
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public LocalDateTime getCreatedatetime() {
return createdatetime;
}
public void setCreatedatetime(LocalDateTime createdatetime) {
this.createdatetime = createdatetime;
}
public Integer getLikenum() {
return likenum;
}
public void setLikenum(Integer likenum) {
this.likenum = likenum;
}
public Integer getReplynum() {
return replynum;
}
public void setReplynum(Integer replynum) {
this.replynum = replynum;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getParentid() {
return parentid;
}
public void setParentid(String parentid) {
this.parentid = parentid;
}
}
创建CommentRepository类
public interface CommentRepository extends MongoRepository<Comment,String> {
/**
* 根据parentid分页查询数据
* @param parentid
* @param pageable
* @return
*/
Page<Comment> findByParentid(String parentid, Pageable pageable);
}
创建CommentService类
@Service
public class CommentService {
@Autowired
CommentRepository commentRepository;
@Autowired
MongoTemplate mongoTemplate;
public void saveComment(Comment comment){
commentRepository.save(comment);
}
public void updateCOmment(Comment comment){
commentRepository.save(comment);
}
public void deleteCommentById(String id){
commentRepository.deleteById(id);
}
public List<Comment> findCommentList(){
return commentRepository.findAll();
}
public Comment findCommentById(String id){
return commentRepository.findById(id).get();
}
public Page<Comment> findCommentListPageByParentid(String parentid,int page,int size){
return commentRepository.findByParentid(parentid, PageRequest.of(page-1,size));
}
public void updateCommentLikenum(String id){
Query query = Query.query(Criteria.where("userid").is(id));
Update update = new Update();
update.inc("likenum");
mongoTemplate.updateFirst(query,update,Comment.class);
}
}
编写测试类CommentServiceTest
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MongodbCommentApplication.class)
public class CommentServiceTest {
@Autowired
CommentService commentService;
@Test
public void testFindAll(){
System.out.println(commentService.findCommentList());
}
@Test
public void testFindCommentById(){
System.out.println(commentService.findCommentById("1"));
};
@Test
public void testSaveComment(){
Comment comment = new Comment();
comment.setContent("测试添加数");
comment.setArticleid("12");
comment.setCreatedatetime(LocalDateTime.now());
comment.setUserid("1003");
comment.setNickname("老色批");
comment.setState("1");
comment.setLikenum(2);
comment.setReplynum(2);
comment.setId("007");
commentService.saveComment(comment);
}
}
(3)测试代码
写操作时,只打开主节点连接:
读操作是,打开从节点连接获取数据:
笔记总结自:https://www.bilibili.com/video/BV1bJ411x7mq