仿牛客社区项目(第三章)(中)

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

第二章SpringBoot进阶开发社区核心功能

四、事务管理

  1. 回顾
  • 什么是事务

    • 事务是由N步数据库操作序列组成的逻辑执行单元这系列操作要么全执行要么全放弃执行。
  • 事务的特性ACID

    • 原子性Atomicity事务是应用中不可再分的最小执行体。
    • 一致性Consistency事务执行的结果须使数据从一个一致性状态变为另一个一致性状态。
    • 隔离性Isolation各个事务的执行互不干扰任何事务的内部操作对其他的事务都是隔离的。
    • 持久性Durability事务一旦提交对数据所做的任何改变都要记录到永久存储器中。
  1. 事务的隔离性
  • 常见的并发异常

    • 第一类丢失更新、第二类丢失更新。
    • 脏读、不可重复读、幻读。
  • 常见的隔离级别

    • Read Uncommitted读取未提交的数据。
    • Read Committed读取已提交的数据。
    • Repeatable Read可重复读。
    • Serializable串行化。

在这里插入图片描述
在这里插入图片描述
 
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

 

  1. 实现机制
  • 悲观锁数据库

    • 共享锁S锁事务A对某数据加了共享锁后其他事务只能对该数据加共享锁但不能加排他锁。
    • 排他锁X锁事务A对某数据加了排他锁后其他事务对该数据既不能加共享锁也不能加排他锁。
  • 乐观锁自定义

    • 版本号、时间戳等 在更新数据前检查版本号是否发生变化。若变化则取消本次更新否则就更新数据版本号+1。
  1. Spring事务管理
  • 声明式事务

    • 通过XML配置声明某方法的事务特征。
    • 通过注解声明某方法的事务特征。
  • 编程式事务

    • 通过 TransactionTemplate 管理事务并通过它执行数据库的操作。

 

五、显示评论

  • 数据层

    • 根据实体查询一页评论数据。
    • 根据实体查询评论的数量。
  • 业务层

    • 处理查询评论的业务。
    • 处理查询评论数量的业务。
  • 表现层

    • 显示帖子详情数据时同时显示该帖子所有的评论数据。

1. util包

CommunityConstant 接口中添加两个字段帖子和评论代码如下

public interface CommunityConstant {

    /**
     * 激活成功
     */
    int ACTIVATION_SUCCESS = 0;

    /**
     * 重复激活
     */
    int ACTIVATION_REPEAT = 1;

    /**
     * 激活失败
     */
    int ACTIVATION_FAILURE = 2;

    /**
     * 默认状态的登录凭证的超时时间
     */
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    /**
     * 记住状态的登录凭证超时时间
     */
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;

    /**
     * 实体类型: 帖子
     */
    int ENTITY_TYPE_POST = 1;

    /**
     * 实体类型: 评论
     */
    int ENTITY_TYPE_COMMENT = 2;
}

 

2. 实体类

util 包下创建 Comment 类与数据库表 Comment 中的字段互相对应代码如下

public class Comment {
    private int id;
    private int userId; // 评论人的id
    private int entityType; // 针对谁进行评论 1帖子 2评论
    private int entityId; // 评论对象的id  针对帖子的评论帖子的id  针对评论的评论评论的id
    private int targetId; // a回复b针对评论的评论评论对象b的user_id方便前端快速展示b的用户名
    private String content;
    private int status;
    private Date createTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getEntityType() {
        return entityType;
    }

    public void setEntityType(int entityType) {
        this.entityType = entityType;
    }

    public int getEntityId() {
        return entityId;
    }

    public void setEntityId(int entityId) {
        this.entityId = entityId;
    }

    public int getTargetId() {
        return targetId;
    }

    public void setTargetId(int targetId) {
        this.targetId = targetId;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "Comment{" +
                "id=" + id +
                ", userId=" + userId +
                ", entityType=" + entityType +
                ", entityId=" + entityId +
                ", targetId=" + targetId +
                ", content='" + content + '\'' +
                ", status=" + status +
                ", createTime=" + createTime +
                '}';
    }
}

 

3. dao层

dao 包先创建 CommentMapper 类代码如下

@Mapper
public interface CommentMapper {
        List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);

        int selectCountByEntity(int entityType, int entityId);
}

 
对应的 comment-mapper.xml 代码如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.CommentMapper">

    <sql id="selectFields">
        id, user_id, entity_type, entity_id, target_id, content, status, create_time
    </sql>

    <select id="selectCommentsByEntity" resultType="Comment">
        select <include refid="selectFields"></include>
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
        order by create_time asc
        limit #{offset}, #{limit}
    </select>

    <select id="selectCountByEntity" resultType="int">
        select count(id)
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
    </select>

</mapper>

 

4. service层

service 包下创建 CommentService 类代码如下

@Service
public class CommentService {

    @Autowired
    private CommentMapper commentMapper;

    public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
        return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
    }

    public int findCommentCount(int entityType, int entityId) {
        return commentMapper.selectCountByEntity(entityType, entityId);
    }
}

 

5. controller层

在原有的 DiscussPostController 类中添加 getDiscussPost 方法代码如下

@Controller
@RequestMapping("/discuss")
public class DiscussPostController implements CommunityConstant {

    @Autowired
    private DiscussPostService discussPostService;

    @Autowired
    private HostHolder hostHolder;

    @Autowired
    private UserService userService;

    @Autowired
    private CommentService commentService;

    @RequestMapping(path = "/add", method = RequestMethod.POST)
    @ResponseBody
    public String addDiscussPost(String title, String content) {
        User user = hostHolder.getUser();
        if (user == null) {
            return CommunityUtil.getJSONString(403,"你还没有登录哦");
        }

        DiscussPost post = new DiscussPost();
        post.setUserId(user.getId());
        post.setTitle(title);
        post.setContent(content);
        post.setCreateTime(new Date());
        discussPostService.addDiscussPost(post);

        // 报错的情况会统一处理
        return CommunityUtil.getJSONString(0, "发布成功!");
    }

    @RequestMapping(path = "/detail/{discussPostId}", method = RequestMethod.GET)
    public String getDiscussPost(@PathVariable("discussPostId") int discussPostId, Model model, Page page) {
        // 帖子
        DiscussPost post = discussPostService.findDiscussPostById(discussPostId);
        model.addAttribute("post", post);
        // 作者
        User user = userService.findUserById(post.getUserId());
        model.addAttribute("user", user);

        // 评论分页信息
        page.setLimit(5);
        page.setPath("/discuss/detail/" + discussPostId);
        page.setRows(post.getCommentCount());

        // 评论: 给帖子的评论
        // 回复: 给评论的评论
        // 评论列表
        List<Comment> commentList = commentService.findCommentsByEntity(
                ENTITY_TYPE_POST, post.getId(), page.getOffset(), page.getLimit());
        // 评论VO列表
        List<Map<String, Object>> commentVoList = new ArrayList<>();
        if (commentList != null) {
            for (Comment comment : commentList) {

                // 评论VO
                Map<String, Object> commentVo = new HashMap<>();
                // 评论
                commentVo.put("comment", comment);
                // 查询到评论的作者
                commentVo.put("user", userService.findUserById(comment.getUserId()));

                // 回复列表
                List<Comment> replyList = commentService.findCommentsByEntity(
                        ENTITY_TYPE_COMMENT, comment.getId(), 0, Integer.MAX_VALUE);
                // 回复VO列表
                List<Map<String, Object>> replyVoList = new ArrayList<>();
                if (replyList != null) {
                    for (Comment reply : replyList) {
                        Map<String, Object> replyVo = new HashMap<>();
                        // 回复
                        replyVo.put("reply", reply);
                        // 作者
                        replyVo.put("user", userService.findUserById(reply.getUserId()));
                        // 回复目标
                        User target = reply.getTargetId() == 0 ? null : userService.findUserById(reply.getTargetId());
                        replyVo.put("target", target);

                        replyVoList.add(replyVo);
                    }
                }
                commentVo.put("replys", replyVoList);

                // 回复数量
                int replyCount = commentService.findCommentCount(ENTITY_TYPE_COMMENT, comment.getId());
                commentVo.put("replyCount", replyCount);

                commentVoList.add(commentVo);
            }
        }

        model.addAttribute("comments", commentVoList);

        return "/site/discuss-detail";
    }
    
}

 

6. View层

index.html

<ul class="d-inline float-right">
         <li class="d-inline ml-2">赞 11</li>
          li class="d-inline ml-2">|</li>
 </ul>

 

discuss-detail.html :
由于帖子详情页面的修改比较混杂将在代码整合的时候展示这部分修改的部分。

 

7. 功能测试

在这里插入图片描述

在这里插入图片描述

 

六、添加评论

  • 数据层

    • 增加评论数据。
    • 修改帖子的评论数量。
  • 业务层

    • 处理添加评论的业务先增加评论、再更新帖子的评论数量。
  • 表现层

    • 处理添加评论数据的请求。
    • 设置添加评论的表单。

1. dao层

CommentMapper 类中添加 insertComment 方法用于增加评论数据代码如下

@Mapper
public interface CommentMapper {
        List<Comment> selectCommentsByEntity(int entityType, int entityId, int offset, int limit);

        int selectCountByEntity(int entityType, int entityId);

        int insertComment(Comment comment);
}

 
对应的 comment-mapper.xml 代码如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.CommentMapper">

    <sql id="selectFields">
        id, user_id, entity_type, entity_id, target_id, content, status, create_time
    </sql>

    <sql id="insertFields">
        user_id, entity_type, entity_id, target_id, content, status, create_time
    </sql>

    <select id="selectCommentsByEntity" resultType="Comment">
        select <include refid="selectFields"></include>
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
        order by create_time asc
        limit #{offset}, #{limit}
    </select>

    <select id="selectCountByEntity" resultType="int">
        select count(id)
        from comment
        where status = 0
        and entity_type = #{entityType}
        and entity_id = #{entityId}
    </select>

    <insert id="insertComment" parameterType="Comment">
        insert into comment(<include refid="insertFields"></include>)
        values(#{userId},#{entityType},#{entityId},#{targetId},#{content},#{status},#{createTime})
    </insert>

</mapper>

 

DiscussPostMapper 类中添加 updateCommentCount 方法用于修改帖子的评论数量代码如下

@Mapper
public interface DiscussPostMapper {

    List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);

    // @Param注解用于给参数取别名
    // 如果只有一个参数并且在<if>里使用则必须加别名
    // 根据userId查询帖子数量
    int selectDiscussPostRows(@Param("userId") int userId);

    int insertDiscussPost(DiscussPost discussPost);

    DiscussPost selectDiscussPostById(int id);

    int updateCommentCount(int id, int commentCount);
}

 

对应的 discusspost-mapper.xml 代码如下

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.nowcoder.community.dao.DiscussPostMapper">

    <sql id="selectFields">
        id, user_id, title, content, type, status, create_time, comment_count, score
    </sql>

    <sql id = "insertFields">
        user_id, title, content, type, status, create_time, comment_count, score
    </sql>

    <select id="selectDiscussPosts" resultType="DiscussPost">
        select
        <include refid="selectFields"></include>
        from discuss_post
        where status != 2
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
        order by type desc, create_time desc
        limit #{offset}, #{limit}
    </select>

    <select id="selectDiscussPostRows" resultType="int">
        select count(id)
        from discuss_post
        where status != 2
        <if test="userId!=0">
            and user_id = #{userId}
        </if>
    </select>


    <insert id="insertDiscussPost" parameterType="DiscussPost">
        insert into discuss_post(<include refid="insertFields"></include>)
        values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
    </insert>

    <select id="selectDiscussPostById" resultType="DiscussPost">
        select <include refid="selectFields"></include>
        from discuss_post
        where id = #{id}
    </select>

    <update id="updateCommentCount">
        update discuss_post set comment_count = #{commentCount} where id = #{id}
    </update>
</mapper>

 

2. service层

CommentService 类中添加 addComment 方法此方法用到了事务注解涉及到两张表的操作代码如下

@Service
public class CommentService implements CommunityConstant {

    @Autowired
    private CommentMapper commentMapper;

    @Autowired
    private SensitiveFilter sensitiveFilter;

    @Autowired
    private DiscussPostService discussPostService;


    public List<Comment> findCommentsByEntity(int entityType, int entityId, int offset, int limit) {
        return commentMapper.selectCommentsByEntity(entityType, entityId, offset, limit);
    }

    public int findCommentCount(int entityType, int entityId) {
        return commentMapper.selectCountByEntity(entityType, entityId);
    }

    @Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
    public int addComment(Comment comment) {
        if (comment == null) {
            throw new IllegalArgumentException("参数不能为空");
        }
        // 添加评论
        comment.setContent(HtmlUtils.htmlEscape(comment.getContent()));
        comment.setContent(sensitiveFilter.filter(comment.getContent()));
        int rows = commentMapper.insertComment(comment);

        // 更新帖子评论数量
        if (comment.getEntityType() == ENTITY_TYPE_POST) {
            int count = commentMapper.selectCountByEntity(comment.getEntityType(), comment.getEntityId());
            discussPostService.updateCommentCount(comment.getEntityId(), count);
        }
        return  rows;

    }

 

DiscussPostService 中添加 updateCommentCoun 方法代码如下

@Service
public class DiscussPostService {

    @Autowired
    private DiscussPostMapper discussPostMapper;


    @Autowired
    private SensitiveFilter sensitiveFilter;

    public List<DiscussPost> findDiscussPosts(int userId, int offset, int limit) {
        return discussPostMapper.selectDiscussPosts(userId, offset, limit);
    }

    public int findDiscussPostRows(int userId) {
        return discussPostMapper.selectDiscussPostRows(userId);
    }

    public int addDiscussPost(DiscussPost post) {
        if (post == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }

        // 转义 HTML 标记
        post.setTitle(HtmlUtils.htmlEscape(post.getTitle()));
        post.setContent(HtmlUtils.htmlEscape(post.getContent()));
        // 过滤敏感词
        post.setTitle(sensitiveFilter.filter(post.getTitle()));
        post.setContent(sensitiveFilter.filter(post.getContent()));

        return discussPostMapper.insertDiscussPost(post);
    }

    public DiscussPost findDiscussPostById(int id) {
        return discussPostMapper.selectDiscussPostById(id);
    }

    public int updateCommentCount(int id, int commentCount) {
        return discussPostMapper.updateCommentCount(id, commentCount);

    }

 

3. controller层

创建 CommentController 类代码如下

@RequestMapping("/comment")
public class CommentController {

    @Autowired
    private CommentService commentService;

    @Autowired
    private HostHolder hostHolder;

    @RequestMapping(value = "/add/{discussPostId}", method = RequestMethod.POST)
    public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
        comment.setUserId(hostHolder.getUser().getId());
        comment.setStatus(0);
        comment.setCreateTime(new Date());
        commentService.addComment(comment);

        return "redirect:/discuss/detail/" + discussPostId;

    }
}

 

4. View层

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

 

5. 功能测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
 
 
创作不易如果有帮助到你请给文章点个赞和收藏让更多的人看到
关注博主不迷路内容持续更新中。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6