多级留言/评论的功能实现——SpringBoot3后端篇

目录

  • 功能描述
  • 数据库表设计
  • 后端接口设计
    • 实体类
      • entity 完整实体类
      • dto 封装请求数据
      • dto 封装分页请求数据
      • vo 请求返回数据
    • Controller控制层
    • Service层
      • 接口
      • 实现类
    • Mapper层
    • Mybatis 操作数据库
  • 补充:
    • 返回的数据结构
    • 自动创建实体类

最近毕设做完了,开始来梳理一下功能点,毕竟温故知新嘛

今天先写一下我的多级留言/评论功能数据库和后端是怎么设计的(前端的设计会再写一篇单独的文章)
删除评论还没做,这个功能还没有思路,目前也没有太多时间来设计了,搜了一下感觉挺复杂的,后面有时间了会补上

接下来文章里说的留言和评论指的都是同一个东西!!!因为我需求一开始就是叫留言功能,但是评论这俩字儿可能比较顺口🤣

如果你能耐心看完这篇文章,应该会有自己的整体思路和大概方向了,接下来就是根据思路去完善你的需求,用代码实现即可!

若有错漏,欢迎指出!

功能描述

想要实现类似bilibili的评论区那样,在我的药材、方剂、文章详情页下都实现多级留言功能,但是不以递进的方式来展现层级关系

解释:
顶级留言 = 一级留言 = 根留言,子留言从二级留言开始,三级,四级…
二级留言直接挂在一级留言的下面,三级及以上留言显示的位置与二级留言是保持 "同级" ,用 @nickname来区分。我的实现图如下:
在这里插入图片描述

数据库表设计

在这里插入图片描述
补充:
我这里的 root_comment_id 字段是参考了博主 黄金贼贼 的这篇文章,觉得后面会用上,就也加上了;
reply_commentimage_urls 字段我目前没用到,设计的时候想到啥就写上了,可以先不用关注这两个字段;
status 字段我用上了,但是感觉其实可以不设置,有点冗余,可以看你自己的具体实现决定是否需要该字段;
还有就是,我整个系统除了收藏、认证方剂功能外,用的都是逻辑删除,所以会设置一个is_deleted字段。

附上sql建表语句,根据自己的需求更改:

CREATE TABLE `comments` (`id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录id',`user_id` bigint NOT NULL COMMENT '用户ID',`comment` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '留言内容',`moment_id` bigint DEFAULT NULL COMMENT '关联主体ID(药材/方剂/文章ID)',`comment_type` int DEFAULT NULL COMMENT '评论类型(1药材;2方剂;3文章)',`parent_id` bigint DEFAULT NULL COMMENT '直接父级ID(顶级留言ID;子级留言ID)',`root_comment_id` bigint DEFAULT NULL COMMENT '顶级评论ID(区分顶级留言和子留言)',`status` int DEFAULT NULL COMMENT '业务状态:1 评论 2 回复',`reply_comment` varchar(3000) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '回复详情',`image_urls` text CHARACTER SET utf8mb4 COLLATE utf8mb4_bin COMMENT '留言图片',`created_by` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',`created_at` datetime DEFAULT NULL COMMENT '创建时间',`updated_by` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '修改人',`updated_at` datetime DEFAULT NULL COMMENT '更新时间',`is_deleted` tinyint DEFAULT '0' COMMENT '是否删除(0未删除;1已删除)',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=20240451 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=COMPACT COMMENT='评论留言表';

前提:
用户表:需要有用户ID(必须),用户名/昵称/角色身份等字段(根据你需要显示的需求来定)
被关联的主体的表:关联主体ID(我这里的moment_id存放的是三张被关联主体表的ID,可以存药材ID方剂ID文章ID,因为我这三个主体都需要有留言功能)简单点说就是,你要在哪个内容下面用这个留言功能,这个关联的主体就是它

解释: 简单讲一下几个字段
comment_type字段:因为我要将药材、方剂、文章的留言都存在同一张表,需要这个字段进行区分(但是不推荐这么做,因为我数据量小,这样能快速实现功能且省事)
parent_id 字段:每一条评论的直接父留言ID,存放的可能为顶级留言ID,也可能是子留言ID(如,子留言也会有子留言[或者说成回复],那它本身就是自己子留言的直接父亲;这个字段主要用于实现那个 @nickname功能点,被艾特的nickname是被留言[或回复]的二级、三级、四级…等留言的发表人昵称
root_comment_id字段:用于区别顶级留言与子留言。这个字段会用于查出全部顶级留言返回一个列表,根据结果列表可以进一步进行子留言查询(这里我还利用 PageHelper 做了分页)

后端接口设计

实体类

entity 完整实体类

项目使用了lombok工具

package com.ykl.springboot_tcmi.pojo.entity;  // 这是你自己的包名import java.io.Serializable;
import lombok.Data;
import java.time.LocalDateTime;/*** @Author: YKL* @ClassName: * @Date: 2024/04/28 10:48 * @Description: */@Data
public class Comments  implements Serializable {/*** 记录id*/private long id;/*** 用户ID*/private long userId;/*** 评论内容*/private String comment;/*** 关联药材/方剂ID*/private long momentId;/*** 评论类型(1药材;2方剂;3文章)*/private long commentType;/*** 直接父级ID(顶级评论ID;子级评论ID)*/private long parentId;/*** 顶级评论ID(区分顶级评论和子评论)*/private long rootCommentId;/*** 回复详情*/private String replyComment;/*** 业务状态:1 评论 2 回复*/private long status;/*** 评论图片*/private String imageUrls;/*** 创建人*/private String createdBy;/*** 创建时间*/private LocalDateTime createdAt;/*** 修改人*/private String updatedBy;/*** 更新时间*/private LocalDateTime updatedAt;/*** 是否删除(0未删除 1已删除)*/private Integer isDeleted;}

dto 封装请求数据

在接口开发时,一般不直接使用完整实体类,而是使用 dto 类进行开发进行;
项目中使用了validation进行参数校验

为什么刚刚我的数据库表里定义为bigint类型的字段,这里都改用了long类型呢?
这里解释一下:
在数据库中,bigint类型用于存储较大的整数值,而在某些编程环境中,如Java的IDE(例如IntelliJ IDEA),这个类型通常会被映射为long类型。这是因为long类型在Java中用于表示64位的整数,与数据库中的bigint类型相对应;
long类型在Java中是标准的整数类型之一,用于表示较大的整数值,与数据库中的bigint类型兼容;
IDE为了简化开发过程,会自动(为啥说自动呢,因为我的实体类是在idea中连接了mysql后,直接使用"脚本扩展 groovy"进行创建的,并非我手动创建) 将数据库中的bigint类型映射为Java中最常用的对应类型long,以便开发者可以直接使用而不需要额外的类型转换;
另外,Java提供了BigInteger类,但这通常会增加代码的复杂性。在对性能不是特别敏感的场景下,可以使用BigInteger来处理更大的数值。

package com.ykl.springboot_tcmi.pojo.dto;   // 这是你自己的包名import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;
import java.time.LocalDateTime;/*** @Author: YKL* @ClassName:* @Date: 2024/04/28 10:48* @Description:*/@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommentsDTO implements Serializable {/*** 用户ID*/private long userId;/*** 评论内容*/@NotEmpty(message = "评论内容不能为空")private String comment;/*** 关联药材/方剂/文章ID*/@NotNullprivate long momentId;/*** 评论类型(1药材;2方剂;3文章)*/@NotNullprivate long commentType;/*** 直接父级ID(顶级评论ID;子级评论ID)*/private long parentId;/*** 顶级评论ID(区分顶级评论和子评论)*/private long rootCommentId;/*** 回复详情*/private String replyComment;/*** 业务状态:1 评论 2 回复*/private long status;/*** 评论图片*/private String imageUrls;/*** 创建人*/private String createdBy;/*** 创建时间*/private LocalDateTime createdAt;
}

dto 封装分页请求数据

如果你没有分页需求,可以不用管这个

package com.ykl.springboot_tcmi.pojo.dto;   // 这是你自己的包名import jakarta.validation.constraints.NotNull;
import lombok.Data;import java.io.Serializable;/*** @Author YKL* @ClassName* @Date: 2024/4/25 0:26* @Description:*/@Data
public class CollectionsPageQueryDTO implements Serializable {// 页码@NotNullInteger pageNum;// 每页显示的记录数@NotNullInteger pageSize;/*** 关联的用户ID*/private long userId;/*** 收藏类型(1药材;2方剂;3文章)*/private long collectType;
}

vo 请求返回数据

封装响应给客户端的数据

package com.ykl.springboot_tcmi.pojo.vo;  // 这是你自己的包名import lombok.Data;import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;/*** @Author: YKL* @ClassName: * @Date: 2024/04/28 10:48 * @Description: */@Data
public class CommentsVO implements Serializable {/*** 记录id*/private long id;/*** 用户ID*/private long userId;/*** 评论内容*/private String comment;/*** 关联药材/方剂/文章ID*/private long momentId;/*** 评论类型(1药材;2方剂;3文章)*/private long commentType;/*** 直接父级ID(顶级评论ID;子级评论ID)*/private long parentId;/*** 顶级评论ID(区分顶级评论和子评论)*/private long rootCommentId;/*** 回复详情*/private String replyComment;/*** 业务状态:1 评论 2 回复*/private long status;/*** 评论图片*/private String imageUrls;/*** 创建人:这里,sql查询时,直接把用户名放在这个字段了*/private String createdBy;/*** 创建时间*/private LocalDateTime createdAt;// 用户头像private String userImg;// 用户身份private String roleName;// 子评论列表private List<CommentsVO> children;
}

重点提一下最后的三个字段,数据库表中是没有这三个字段的,这是在做sql多表查询时额外返回的字段,前端需要这些数据;若你没有显示用户身份需求的话,可以省略roleName字段。
子评论列表很重要,这里存放了顶级评论下所有的子评论,一层层嵌套的,后面会说怎么用

Controller控制层

package com.ykl.springboot_tcmi.controller;  // 这是你自己的包名import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultCodeEnum;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import com.ykl.springboot_tcmi.service.CommentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;/*** @Author YKL* @ClassName* @Date: 2024/4/28 10:51* @Description:*/@RestController
@RequestMapping("/comment")
@CrossOrigin(origins = "http://localhost:5173")
public class CommentController {@Autowiredprivate CommentService commentService;/*** 添加评论** @param commentsDTO* @return*/@PostMapping("/add")public ResultOBJ addComment(@RequestBody @Validated CommentsDTO commentsDTO) {return commentService.addComment(commentsDTO);}/*** 根据关联的主题ID查评论列表** @param commentsPageQueryDTO* @return 树形结构的列表*/@GetMapping("/page")public ResultOBJ<PageBean<CommentsVO>> getCommentList(@Validated CommentsPageQueryDTO commentsPageQueryDTO) {PageBean<CommentsVO> pb = commentService.getCommentListByMomentId(commentsPageQueryDTO);return ResultOBJ.SUCCESS(ResultCodeEnum.SUCCESS, pb);}
}

解释一哈:

添加评论

  • 需要提交的数据封装在CommentsDTO中传给后端,后端使用@RequestBody接收数据;
  • 其中,评论内容、关联主体ID、评论类型均不能为空

获取评论列表

  • 请求数据时需要提交 CommentsPageQueryDTO 中的数据,并使用 @Validated 进行参数的校验;
  • 这里使用了 PageHelper 进行分页处理(不做展示,网上教程也很多其实,或者私聊我获取这部分代码),所以返回的 CommentsVO 数据需要用 PageBean 包裹返回;
  • ResultOBJ 是我自己封装的通用的结果返回类,你也可以直接写成 return commentService.getCommentListByMomentId(commentsPageQueryDTO); 但是在你的接口实现类(Impl) 处需要返回对应的列表数据给前端

Service层

接口

package com.ykl.springboot_tcmi.service;  // 这是你自己的包名import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;/*** @Author YKL* @ClassName* @Date: 2024/4/28 10:52* @Description:*/public interface CommentService {ResultOBJ addComment(CommentsDTO commentsDTO);PageBean<CommentsVO> getCommentListByMomentId(CommentsPageQueryDTO commentsPageQueryDTO);
}

实现类

package com.ykl.springboot_tcmi.service;  // 这是你自己的包名import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.ykl.springboot_tcmi.common.bean.PageBean;
import com.ykl.springboot_tcmi.common.result.ResultCodeEnum;
import com.ykl.springboot_tcmi.common.result.ResultOBJ;
import com.ykl.springboot_tcmi.dao.CommentMapper;
import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import com.ykl.springboot_tcmi.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import java.util.List;
import java.util.Map;/*** @Author YKL* @ClassName* @Date: 2024/4/28 10:52* @Description:*/@Service
@Transactional
public class CommentServiceImpl implements CommentService {@Autowiredprivate CommentMapper commentMapper;/*** 添加评论** @param commentsDTO* @return*/@Overridepublic ResultOBJ addComment(CommentsDTO commentsDTO) {// 本项目使用了 ThreadLocal 进行用户的信息管理,所以这里直接取了登录用户的idMap<String, Object> map = ThreadLocalUtil.get();Integer id = (Integer) map.get("id");commentsDTO.setUserId(id);  // 补充属性,所以前端请求的时候不需要携带这个数据// 判断添加的是顶级评论还是子评论(这里其实没太大必要,我是想着后面有功能点可能要用到这个状态,就设置了)// 【注意】这里解释一下为什么是等于0不是等于null。因为前面说过bigint自动映射为long了,long类型没有值则是为0if (commentsDTO.getRootCommentId() == 0 && commentsDTO.getParentId() == 0) {    // 顶级评论// 设置业务状态 1 评论 2 回复commentsDTO.setStatus(1);} else {  // 子评论/回复commentsDTO.setStatus(2);}commentMapper.addComment(commentsDTO);return ResultOBJ.SUCCESS(ResultCodeEnum.COMMENT_SUCCESS);}/*** 根据关联的主题ID查评论列表** @param commentsPageQueryDTO* @return 树形结构的列表*/@Overridepublic PageBean<CommentsVO> getCommentListByMomentId(CommentsPageQueryDTO commentsPageQueryDTO) {// 1.创建PageBean对象PageBean<CommentsVO> pb = new PageBean<>();// 2.开启分页查询 PageHelperPageHelper.startPage(commentsPageQueryDTO.getPageNum(), commentsPageQueryDTO.getPageSize());// 3.查所有的根评论List<CommentsVO> commentsVOList = commentMapper.getAllRoot(commentsPageQueryDTO);// 4.遍历commentsVOList列表,添加对应的子评论(二级评论在一级评论的children字段中,三级评论在二级评论的children字段中,以此类推)for (CommentsVO comment : commentsVOList) {// 调用查询子评论的方法,需要该顶级评论自己的 id 与 关联主体 id// 【注意】这里就用到了vo中最后一个子评论列表 private List<CommentsVO> children 字段,设置子孩子的时候也是按照CommentsVO类型来返回数据的comment.setChildren(getChildrenComments(comment.getId(), commentsPageQueryDTO.getMomentId()));}// 强转Page<CommentsVO> page = (Page<CommentsVO>) commentsVOList;// 5.把数据填充到PageBean对象中,getTotal、getResult这两个方法是 pagehelper 提供的pb.setTotal(page.getTotal()); // 总数pb.setItems(page.getResult()); // 具体内容// 如果你在controller层用的我第二种写法,那么这里就还需要返回结果列表 + 处理状态,而不是单纯的一个 pb 对象return pb;}/*** 获取子评论的方法** @param parentId* @param momentId* @return*/private List<CommentsVO> getChildrenComments(long parentId, long momentId) {// 查所有的子评论(需要的是该子评论的直接父评论ID,一开始从二级评论开始查,也就是调用此方法时传进来的顶级评论id[这就是二级评论的直接父评论ID];还有关联主体id)List<CommentsVO> commentsVOList = commentMapper.getChildren(parentId, momentId);// 遍历名为commentsVOList的CommentsVO类型的集合for (CommentsVO comment : commentsVOList) {// 此处用到了递归,递归查询每一级评论,每次调用本层的id去查子一层// 【注意】每一个子孩子还有子孩子的话,也是按照CommentsVO类型来存放comment.setChildren(getChildrenComments(comment.getId(), momentId));}return commentsVOList;}// 子评论分页【未实现】// 【问题】只能分出二级评论,某条可展示的二级评论的子级评论们仍然可以被查到;需要的是所有子评论只展示3条,再进行分页
}

若这部分有疑问可以移步文章末尾看看返回的数据结构,结合起来再看看这段代码,可能会更清楚,若还有疑问,可以在评论区留言😁

Mapper层

package com.ykl.springboot_tcmi.dao;  // 这是你自己的包名import com.ykl.springboot_tcmi.pojo.dto.CommentsDTO;
import com.ykl.springboot_tcmi.pojo.dto.CommentsPageQueryDTO;
import com.ykl.springboot_tcmi.pojo.vo.CommentsVO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;/*** @Author YKL* @ClassName* @Date: 2024/4/28 10:52* @Description:*/@Mapper
public interface CommentMapper {@Insert("insert into comments (user_id, comment, moment_id, comment_type, parent_id, root_comment_id, reply_comment, status, image_urls, created_at) values (#{userId}, #{comment}, #{momentId}, #{commentType}, #{parentId}, #{rootCommentId}, #{replyComment}, #{status}, #{imageUrls}, now())")void addComment(CommentsDTO commentsDTO);// 不分页的话,可以在这里进行简单的查询返回;但是我使用了分页,需要动态sql,所以不使用此种方法,注释处仅供参考// @Select("select * from comments where moment_id = #{momentId} and parent_id = 0 and root_comment_id = 0 and status = 1 and is_deleted = 0")List<CommentsVO> getAllRoot(CommentsPageQueryDTO commentsPageQueryDTO);// @Select("select * from comments where parent_id = #{parentId} and moment_id = #{momentId} and status = 2 and is_deleted = 0")List<CommentsVO> getChildren(long parentId, long momentId);
}

Mybatis 操作数据库

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><!-- @author: ykl --><mapper namespace="com.ykl.springboot_tcmi.dao.CommentMapper"><select id="getAllRoot" resultType="com.ykl.springboot_tcmi.pojo.vo.CommentsVO">select cs.*, u.nickname as createdBy, u.avatar as userImg, r.role_name as roleNamefrom comments csLEFT JOIN users u on u.id = cs.user_idLEFT JOIN roles r on u.user_role = r.role_typewhere cs.moment_id = #{momentId}and cs.status = 1and cs.parent_id = 0and cs.root_comment_id = 0and cs.is_deleted = 0order by cs.created_at desc</select><select id="getChildren" resultType="com.ykl.springboot_tcmi.pojo.vo.CommentsVO">select cs.*, u.nickname as createdBy, u.avatar as userImg, r.role_name as roleNamefrom comments csLEFT JOIN users u on u.id = cs.user_idLEFT JOIN roles r on u.user_role = r.role_typewhere cs.parent_id = #{parentId}and cs.moment_id = #{momentId}and cs.status = 2and cs.is_deleted = 0order by cs.created_at desc</select></mapper>

这里使用到了左连接进行多表查询,只是看起来复杂,仔细看看语句应该都能看懂。
主要提一下,这里将查到的昵称、头像、角色名使用as别名对应了前面 vo 那几个字段,返回给前端使用:
u.nickname as createdBy, u.avatar as userImg, r.role_name as roleName

后端的设计到这里就结束了,删除评论的接口还没有设计,后面有时间补上😁

补充:

返回的数据结构

展示一下返回的评论列表是怎么样的,便于理解递归和 children字段。
示例中总共有7条顶级留言,但我是分页查询,本页我只查了3条:

  • 第一条id为 20240437 的顶级留言,没有子留言,故children字段为空。
  • 第二条id为 20240418 的顶级留言,只有1条二级子留言。
  • 第三条id为 20240402 的顶级留言,有3条二级留言。其中第一条二级留言下有一条三级留言(也可以有多条,我这里只是示例数据的结构),该三级留言下有一条四级留言,该四级留言下没有子留言了(后面还有五级、六级也是这个结构),children字段为空

若看着不方便,你可以cv到一些可以展示 json 格式数据的平台或编辑器上(如浏览器插件FeHelper),折叠着看,会更清楚,记得把我的注释去掉

{"code": 200,"msg": "操作成功","data": {"total": 7,		// 这就是查出的顶级数据总条数,通过pb.setTotal(page.getTotal());返回的"items": [		// 这里就是整个数据体,通过pb.setItems(page.getResult());返回的{	// 第一条顶级评论"id": 20240437,"userId": 1509187009,"comment": "载刷一条","momentId": 1000,"commentType": 1,"parentId": 0,"rootCommentId": 0,"replyComment": "","status": 1,"imageUrls": null,"createdBy": "温壶酒",	// u.nickname as createdBy 返回的"createdAt": "2024-04-29 18:53","userImg": "https://pic4.zhimg.com/v2-224f33627212bd952185ab882c377d3b_r.jpg",	//  u.avatar as userImg"roleName": "专业用户",	// r.role_name as roleName"children": []},{	// 第二条顶级评论"id": 20240418,"userId": 1509187011,"comment": "李白沉舟将欲行","momentId": 1000,"commentType": 1,"parentId": 0,"rootCommentId": 0,"replyComment": "","status": 1,"imageUrls": null,"createdBy": "诗仙","createdAt": "2024-04-29 13:27","userImg": "https://tse4-mm.cn.bing.net/th/id/OIP-C.cPnuoqXK3Jvbb4E8Y-mANQHaKM?rs=1&pid=ImgDetMain","roleName": "普通用户","children": [	// 子留言的结构还是 vo 结构,这就应用了private List<CommentsVO> children;这个字段{"id": 20240426,"userId": 1509187013,"comment": "破釜沉舟","momentId": 1000,"commentType": 1,"parentId": 20240418,"rootCommentId": 20240418,"replyComment": "","status": 2,"imageUrls": null,"createdBy": "诗鬼","createdAt": "2024-04-29 18:17","userImg": "https://www.renwuji.com/wp-content/uploads/images/2023/01/11/14bd6725cbe34af98bb2ed12df20c253~noop_dmtcvm25wza.jpg","roleName": "普通用户","children": []}]},{	// 第三条顶级评论"id": 20240402,"userId": 1509187005,"comment": "我是测试的给方剂的父级评论","momentId": 1000,"commentType": 1,"parentId": 0,"rootCommentId": 0,"replyComment": null,"status": 1,"imageUrls": null,"createdBy": "北上","createdAt": "2024-04-28 13:03","userImg": "","roleName": "普通用户","children": [{	// 第三条顶级评论的第一条二级评论"id": 20240403,"userId": 1509187005,"comment": "我是给自己的二级评论","momentId": 1000,"commentType": 1,"parentId": 20240402,"rootCommentId": 20240402,"replyComment": null,"status": 2,"imageUrls": null,"createdBy": "北上","createdAt": "2024-04-28 13:07","userImg": "","roleName": "普通用户","children": [{"id": 20240404,"userId": 1509187005,"comment": "我是给自己的三级评论","momentId": 1000,"commentType": 1,"parentId": 20240403,"rootCommentId": 20240402,"replyComment": null,"status": 2,"imageUrls": null,"createdBy": "北上","createdAt": "2024-04-28 13:08","userImg": "","roleName": "普通用户","children": [{"id": 20240405,"userId": 1509187005,"comment": "我是给自己的四级评论","momentId": 1000,"commentType": 1,"parentId": 20240404,"rootCommentId": 20240402,"replyComment": null,"status": 2,"imageUrls": null,"createdBy": "北上","createdAt": "2024-04-28 13:10","userImg": "","roleName": "普通用户","children": []}]}]},{	// 第三条顶级评论的第二条二级评论"id": 20240407,"userId": 1509187006,"comment": "gao的二级评论","momentId": 1000,"commentType": 1,"parentId": 20240402,"rootCommentId": 20240402,"replyComment": null,"status": 2,"imageUrls": null,"createdBy": "gao","createdAt": "2024-04-28 13:07","userImg": "https://ts1.cn.mm.bing.net/th/id/R-C.66d7b796377883a92aad65b283ef1f84?rik=sQ%2fKoYAcr%2bOwsw&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140305%2f1-140305131415.jpg&ehk=Hxl%2fQ9pbEiuuybrGWTEPJOhvrFK9C3vyCcWicooXfNE%3d&risl=&pid=ImgRaw&r=0","roleName": "超级管理员","children": []},{	// 第三条顶级评论的第三条二级评论"id": 20240408,"userId": 1509187007,"comment": "小脏的二级评论","momentId": 1000,"commentType": 1,"parentId": 20240402,"rootCommentId": 20240402,"replyComment": null,"status": 2,"imageUrls": null,"createdBy": "zangzang","createdAt": "2024-04-28 13:07","userImg": "https://tcmi.oss-cn-beijing.aliyuncs.com/0f4c453f-40dd-43e4-bee9-03ab995ca0de.png","roleName": "平台管理员","children": []}]}]}
}

FeHelper插件里,整体展示在这里插入图片描述

自动创建实体类

这里简单说一下怎么自动将数据库表导出为我们开发需要的实体类文件,详细过程网上很多,这里就提一下:
在idea中连接上数据库后,就可以使用这个功能
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF应用程序XAML

当WPF应用程序创建好后&#xff0c;系统会自动添加一个Grid控件到窗体上&#xff0c;通过Grid控件能够方便地对界面进行布局.下面代码中为Grid控件添加了两行两列&#xff0c;分别用RowDefinitions属性ColumnDefinitions属性表示行的集合和列的集合&#xff0c;集合中有RowDefi…

【区块链】共识算法简介

共识算法简介 区块链三要素&#xff1a; 去中心化共识算法智能合约 共识算法作为区块链三大核心技术之一&#xff0c;其重要性不言而喻。今天就来简单介绍共识算法的基本知识。 最简单的解释&#xff0c;共识算法就是要让所有节点达成共识&#xff0c;保证少数服从多数&#x…

噪声嵌入提升语言模型微调性能

在自然语言处理&#xff08;NLP&#xff09;的快速发展中&#xff0c;大模型&#xff08;LLMs&#xff09;的微调技术一直是研究的热点。最近&#xff0c;一篇名为《NEFTUNE: NOISY EMBEDDINGS IMPROVE INSTRUCTION FINETUNING》的论文提出了一种新颖的方法&#xff0c;通过在训…

【数据结构】--- 深入剖析二叉树(上篇)--- 初识树和二叉树

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 数据结构之旅 &#x1f3e0; 初识树 &#x1f4d2; 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点…

分布式与一致性协议之一致哈希算法(三)

一致哈希算法 如何使用一致哈希算法实现哈希寻址 我们一起来看一个例子&#xff0c;对于1000万个key的3节点KV存储&#xff0c;如果我们使用一致哈希算法增加1个节点&#xff0c;即3节点集群变为4节点集群&#xff0c;则只需要迁移24.3%的数据,如代码所示 package mainimpor…

微搭低代码入门03页面管理

目录 1 创建页面2 页面布局3 页面跳转总结 上一篇我们介绍了应用的基本操作&#xff0c;掌握了应用的概念后接着我们需要掌握页面的常见操作。 1 创建页面 打开应用的编辑器&#xff0c;在顶部导航条点击创建页面图标 在创建页面的时候可以从空白新建&#xff0c;也可以使用模…

【原件】软件需求分析报告

第1章 序言 第2章 引言 2.1 项目概述 2.2 编写目的 2.3 文档约定 2.4 预期读者及阅读建议 第3章 技术要求 3.1 软件开发要求 第4章 项目建设内容 第5章 系统安全需求 5.1 物理设计安全 5.2 系统安全设计 5.3 网络安全设计 5.4 应用安全设计 5.5 对用户安全管理 …

Mysql复习笔记: 基础概念(待补充)

一. 基础概念 通用概念: 网络连接必须得分配给一个线程去进行处理&#xff0c;由一个线程来监听请求以及读取请求数据&#xff0c;比如从网络连接中读取和解析出来一条我们的系统发送过去的SQL语句 在数据库中&#xff0c;哪怕执行一条SQL语句&#xff0c;其实也可以是一个独立…

2024牛客五一集训派对day2 Groundhog Looking Dowdy 个人解题思路

前言&#xff1a; 被实验室教练要求要打的这次五一牛客的训练赛&#xff0c;这些区域赛难度的题对于大一的我来说难度实在是太高了&#xff0c;我和我的队友只写了一些非常简单的签到题&#xff0c;其他题目都没怎么看&#xff08;我们太弱了&#xff09;&#xff0c;但我可以分…

线上线下交友社区系统,支持打包小程序/公众号/H5,源码交付!

上网交友的好处有很多&#xff0c;以下是一些主要的好处&#xff1a; 1. 拓展人际关系&#xff1a;通过上网交友可以认识更多的人&#xff0c;拓展自己的社交圈。这有助于扩大自己的视野、增加人生经验和开阔心胸。 2. 找到志同道合的朋友&#xff1a;在网络上&#xff0c;我们…

JavaWeb请求响应概述

目录 一、请求响应流程-简述 二、深入探究 三、DispatcherServlet 四、请求响应流程-详细分析 一、请求响应流程-简述 web应用部署在tomcat服务器中&#xff0c;前端与后端通过http协议进行数据的请求和响应。前端通过http协议向后端发送数据请求&#xff0c;就可以访问到部…

批量抓取某电影网站的下载链接

思路&#xff1a; 进入电影天堂首页&#xff0c;提取到主页面中的每一个电影的背后的那个urL地址 a. 拿到“2024必看热片”那一块的HTML代码 b. 从刚才拿到的HTML代码中提取到href的值访问子页面&#xff0c;提取到电影的名称以及下载地址 a. 拿到子页面的页面源代码 b. 数据提…

第二代增强-创建采购申请时的增强

文章目录 第二代增强-创建采购申请时的增强业务要求实现过程创建项目编写代码激活增强 维护消息类运行效果断点 第二代增强-创建采购申请时的增强 业务要求 实现过程 创建项目 编写代码 "AFNAM&#xff0c;申请人 "需求&#xff1a;NB类型的采购申请&#xff0c;需要…

嵌入式全栈开发学习笔记---C语言笔试复习大全10

目录 字符数组&#xff08;笔试重点&#xff09; 字符数组初始化 字符串数组输出 字符串的输入 字符串处理函数 puts()输出字符串 gets()输入字符串 strlen()计算字符串长度 strcpy()将后面的字符串拷贝到前面的字符串数组里面 笔试题12 strcmp()比较字符串的大小 …

自动化机器学习——网格搜索法:寻找最佳超参数组合

自动化机器学习——网格搜索法&#xff1a;寻找最佳超参数组合 在机器学习中&#xff0c;选择合适的超参数是模型调优的关键步骤之一。然而&#xff0c;由于超参数的组合空间通常非常庞大&#xff0c;手动调整超参数往往是一项耗时且困难的任务。为了解决这个问题&#xff0c;…

C语言 | Leetcode C语言题解之第67题二进制求和

题目&#xff1a; 题解&#xff1a; void reserve(char* s) {int len strlen(s);for (int i 0; i < len / 2; i) {char t s[i];s[i] s[len - i - 1], s[len - i - 1] t;} }char* addBinary(char* a, char* b) {reserve(a);reserve(b);int len_a strlen(a), len_b st…

性能优化(一):ArrayList还是LinkedList?

引言 集合作为一种存储数据的容器&#xff0c;是我们日常开发中使用最频繁的对象类型之一。JDK为开发者提供了一系列的集合类型&#xff0c;这些集合类型使用不同的数据结构来实现。因此&#xff0c;不同的集合类型&#xff0c;使用场景也不同。 很多同学在面试的时候&#x…

大厂案例 - 通用的三方接口调用方案设计(中)

文章目录 Pre阿里云华为云【AK和SK生成方案】最佳实践1. 创建API密钥管理系统2. 生成AK和SK3. 存储和管理AK和SK4. 提供API密钥分发机制5. 安全性6. 其他注意事项 DB Model Design表结构Next考虑其他建议 API接口设计指导1. 使用POST作为接口请求方式2. 客户端IP白名单3. 单个接…

【研发管理】产品经理知识体系-产品创新流程

导读&#xff1a;产品创新流程是一个系统性的过程&#xff0c;旨在通过创造和引入新的产品或改进现有产品来满足市场需求、解决用户问题或实现竞争优势。 目录 1、产品创新引论 2、决策基本框架 3、模糊前端 4、产品创新流程模型概论 5、门径管理流程 6、并行工程和集成产…

Java与Go:并发

在此之前&#xff0c;我们先要明白什么是并发&#xff1f;为什么要并发编程&#xff1f; 在计算机中&#xff0c;同一时刻&#xff0c;只能有一条指令&#xff0c;在一个CPU上执行 后面的指令必须等到前面指令执行完才能执行&#xff0c;就是串行。在早年CPU核心数还少的时候倒…