多级留言/评论的功能实现——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.rhkb.cn/news/318695.html

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

相关文章

✔ ★Java大项目——用Java模拟RabbitMQ实现一个消息队列(二)【创建核心类、封装数据库操作】

✔ ★Java大项目——用Java模拟RabbitMQ实现一个消息队列 四. 项⽬创建五. 创建核⼼类 ★创建 Exchange&#xff08;名字、类型、持久化、自动删除、参数&#xff09;创建 MSGQueue&#xff08;名字、持久化、独占标识&#xff09;创建 Binding&#xff08;交换机名字、队列名字…

UDP编程流程(UDP客户端、服务器互发消息流程)

一、UDP编程流程 1.1、 UDP概述 UDP&#xff0c;即用户数据报协议&#xff0c;是一种面向无连接的传输层协议。相比于TCP协议&#xff0c;UDP具有以下特点&#xff1a; 速度较快&#xff1a;由于UDP不需要建立连接和进行复杂的握手过程&#xff0c;因此在传输数据时速度稍快…

Arcpy批量克里金插值报错

Arcpy批量克里金插值报错 文章目录 Arcpy批量克里金插值报错问题解决参考 问题 在进行实验的时候&#xff0c;Arcpy中批量进行克里金插值报错&#xff0c;主要就是在运行这个工具的时候&#xff0c;一直报错&#xff0c;改了很多参数也不行 ERROR 010079: 无法估算半变异函数…

MySQL商城数据库88张表结构(46—50)

46、消息队列表 CREATE TABLE dingchengyu消息队列表 (id int(11) NOT NULL AUTO_INCREMENT COMMENT 序号,userId int(11) DEFAULT NULL COMMENT 用户id,msgTtype tinyint(4) DEFAULT 0 COMMENT 消息类型,createTime datetime DEFAULT NULL COMMENT 创建时间,sendTime datetim…

LabVIEW自动剪板机控制系统

LabVIEW自动剪板机控制系统 随着工业自动化的快速发展&#xff0c;钣金加工行业面临着生产效率和加工精度的双重挑战。传统的手动或脚踏式剪板机已无法满足现代生产的高效率和高精度要求&#xff0c;因此&#xff0c;自动剪板机控制系统的研究与开发成为了行业发展的必然趋势。…

【深度学习】序列模型

深度学习&#xff08;Deep Learning&#xff09;是机器学习的一个分支领域&#xff1a;它是从数据中学习表示的一种新方法&#xff0c;强调从连续的层中进行学习&#xff0c;这些层对应于越来越有意义的表示。 1. 为什么选择序列模型&#xff1f; 循环神经网络&#xff08;RNN…

[嵌入式系统-63]:RT-Thread-内核:内核在不同CPU架构上的移植和不同硬件板BSP上的移植

目录 内核移植 1. CPU 架构移植&#xff1a;由CPU厂家提供 1.1 实现全局中断开关&#xff1a;汇编语言实现 &#xff08;1&#xff09;关闭全局中断 &#xff08;2&#xff09;打开全局中断 1.2 实现线程栈初始化 1.3 实现上下文切换 &#xff08;1&#xff09;实现 rt…

零代码编程:用Kimichat从PDF文件中批量提取图片

一个PDF文件中&#xff0c;有很多图片&#xff0c;想批量提取出来&#xff0c;可以借助kimi智能助手。 在借助kimi智能助手中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个网页爬取Python脚本的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹…

基于深度学习检测恶意流量识别框架(80+特征/99%识别率)

基于深度学习检测恶意流量识别框架 目录 基于深度学习检测恶意流量识别框架简要示例a.检测攻击类别b.模型训练结果输出参数c.前端检测页面d.前端训练界面e.前端审计界面&#xff08;后续更新了&#xff09;f.前端自学习界面&#xff08;自学习模式转换&#xff09;f1.自学习模式…

数据结构与算法之经典排序算法

一、简单排序 在我们的程序中&#xff0c;排序是非常常见的一种需求&#xff0c;提供一些数据元素&#xff0c;把这些数据元素按照一定的规则进行排序。比如查询一些订单按照订单的日期进行排序&#xff0c;再比如查询一些商品&#xff0c;按照商品的价格进行排序等等。所以&a…

021、Python+fastapi,第一个Python项目走向第21步:ubuntu 24.04 docker 安装mysql8、redis(二)

系列文章目录 pythonvue3fastapiai 学习_浪淘沙jkp的博客-CSDN博客https://blog.csdn.net/jiangkp/category_12623996.html 前言 安装redis 我会以三种方式安装&#xff0c; 第一、直接最简单安装&#xff0c;适用于测试环境玩玩 第二、conf配置安装 第三、集群环境安装 一…

电脑崩溃了,之前备份的GHO文件怎么恢复到新硬盘?

前言 之前咱们说到用WinPE系统给电脑做一个GHO镜像备份&#xff0c;这个备份可以用于硬盘完全崩溃换盘的情况下使用。 那么这个GHO镜像文件怎么用呢&#xff1f; 咱们今天详细来讲讲&#xff01; 如果你的电脑系统硬盘崩溃了或者是坏掉了&#xff0c;那么就需要使用之前备份…

ElementUI从unpkg.com完整下载到本地的方法 - 解决unpkg.com不稳定的问题 - 自建镜像站 - 不想打包只想cdn一下

方法 方法1&#xff09;随便弄个文件夹&#xff0c;根据官网npm方法下载包&#xff0c;提取即可 npm i element-ui -S cd /node_modules/element-ui/ ls src 安装npm方法&#xff1a;https://nodejs.org/en 方法2&#xff09;不推荐 - 在github中搜索对应的库zip包&#xff0…

C++仿函数周边及包装器

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

Vue Vant 移动端如何禁止手机调起自带的输入键盘

前言 前不久在公司用Vue2开发了一个手机充值项目&#xff0c;键盘组件用的vant2的NumberKeyboard 数字键盘组件&#xff1b;上线后在IOS端只有一个vant数字键盘组件&#xff0c;但到了Android端&#xff0c;输入框一获取焦点不仅vant数字键盘弹出&#xff0c;连手机自带的键盘…

39 死锁

目录 1.死锁 2.线程同步 3.条件变量 4.案例 死锁 概念 死锁是指在一组进程中的各个进程均占有不会释放的资源&#xff0c;但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态 四个必要条件 互斥条件&#xff1a;一个资源每次只能被一个执行流使用 请求…

如何使用提示测试为LLMs构建单元测试?

原文地址&#xff1a;how-to-build-unit-tests-for-llms-using-prompt-testing 确保您的人工智能交付&#xff1a;快速测试完美生成应用程序的基本指南 2024 年 4 月 26 日 如果你曾经编写过软件&#xff0c;你就会知道测试是开发过程中必不可少的一部分。特别是单元测试&#…

设计模式——保护性暂停

同步模式之保护性暂停 文章目录 同步模式之保护性暂停定义实现应用带超时版 GuardedObject扩展——原理之join扩展——多任务版 GuardedObject 定义 即 Guarded Suspension&#xff0c;用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程&…

【Linux】进程的控制①之进程创建与进程退出

目录 ​编辑 一 、进程的创建 1、fork函数 2.函数的返回值 ①return 可以返回两次&#xff0c;使得父子进程读到的id有两个值 ②写实拷贝&#xff0c;使得父子进程读到的值都对自身有意义 a.为什么要写实拷贝&#xff0c;而不是在创建子进程的时候直接就将空间给子进程开辟好&a…

Linux系统安装Redis7(详细版)

Linux系统安装Redis7 一、windows安装redis二、Linux安装Redis下载redis编辑redis7.conf文件启动redis-server服务如何关闭redis服务设置Redis开机自启动 一、windows安装redis Window 下安装 下载地址&#xff1a;https://github.com/dmajkic/redis/downloads 下载到的Redi…