总结在先:

①首先根据每张表创建相对应的实体类,该实体类中的属性与表中的字段名相同;

②由于每张表都需要一些增删改查的方法,因此需要创建Mapper接口(每张表各一个),接口中放有对应表的增删改查方法。

③由于是接口,只提供了方法名,本身没有具体方法的实现。因此创建对应的XxxMapper.xml文件,在该xml文件中编写相应Mapper接口方法的SQL语句。

④直接去调用Mapper接口中的方法,不方便进行管理。因此为每个Mapper接口再创建对应的Service类,通过Service类去调用Mapper接口中相应的增删改查方法,Mapper接口又会去调用相应mapper.xml文件中真正的SQL语句。

这便是整个流程。

1. 创建项目

① new Project - Spring Initailizr

②填写 Group: com.nowcoder.mycommunity

③勾选:

 - 选择Web - 勾选Spring Web

 - 选择SQL - 勾选MyBatis Framework

 - 选择NoSQL - 勾选Spring Data Redis

 - Next - Finish

2. 添加依赖:pom.xml

由于刚才勾选了一些组件,因此会自动引入对应的依赖。

我们再添加一些其他的依赖,分别为:

        thymeleaf、test、mysql、devtools(方便我们在线build项目)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--<version>5.1.49</version>-->
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

3. 核心配置文件 application.properties

配置数据库连接、mybatis的一些设置。

spring.datasource.url=jdbc:mysql://localhost:3306/community
spring.datasource.username=root
spring.datasource.password=1472
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# MyBatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.nowcoder.mycommunity.entity
mybatis.configuration.use-generated-keys=true
mybatis.configuration.map-underscore-to-camel-case=true

4. 创建实体类 entity

右键com.nowcoder.mycommunity新建三个实体类,起名为:entity.XXX:

①DiscussPost

该实体类对应数据库中的discuss_post表。

package com.nowcoder.mycommunity.entity;
import java.util.Date;

//对应数据库中的discuss_post表
public class DiscussPost {
    private int id;
    private int userId;
    private String title;
    private String content;
    private int type;          //帖子类型
    private int status;        //帖子状态:正常、拉黑
    private Date createTime;
    private int commentCount;  //评论数量
    private double score;      //帖子得分

    //get()、set()
    //toString()
}

②User

该实体类对应于User表。

package com.nowcoder.mycommunity.entity;

import java.util.Date;

//对应数据库中的user表
public class User {
    private int id;
    private String username;
    private String password;
    private String salt;
    private String email;
    private int type;
    private int statue;
    private String activationCode;
    private String headerUrl;
    private Date createTime;

    //get()、set()
    //toString()
}

③Page

该实体类对应于后面的分类功能。

由于分类功能需要进行数据的处理,直接将许多方法、参数封装到此类中。

package com.nowcoder.mycommunity.entity;

// 封装分页相关的信息
public class Page {
    //当前页码
    private int current = 1; //默认为第一页
    //显示上限
    private int limit = 5;

    //数据总数(用于计算总页数)
    private int rows;
    //查询路径(用于复用分页链接)
    private String path;

    public int getCurrent() {
        return current;
    }

    public void setCurrent(int current) {
        //进行一些判断,免得输入一些不合理的数
        if(current >= 1){
            this.current = current;
        }
    }

    public int getLimit() {
        return limit;
    }

    public void setLimit(int limit) {
        if(limit >= 1 && limit <= 100){
            this.limit = limit;
        }
    }

    public int getRows() {
        return rows;
    }

    public void setRows(int rows) {
        if(rows >= 0){
            this.rows = rows;
        }
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    //额外的方法
    //获取当前页的起始行
    public int getoffset(){
        //计算公式为: current * limit - limit
        return (current - 1) * limit;
    }

    //获取总页数
    public int getTotal(){
        if(rows % limit == 0){  //可以整除
            return rows/limit;
        }else {
            return rows/limit + 1;
        }
    }

    //获取展示的起始页码
    public int getFrom(){
        int from = current - 2;  //展示前后两页
        //如果前面没有两页,则展示的起始页码为1,有两页及以上,则展示的起始页码为2
        return from < 1 ? 1 :from;
    }

    //获取展示的终止页码
    public int getTo(){
        int to = current + 2;
        int total = getTotal();
        //展示的终止页大于总页数,则展示到总页数,否则展示到to
        return to > total ? total : to;
    }
}

注:该类中有一些get()、set()方法需要合理设置,里面添加了一些条件。

5. 创建Mapper

右键com.nowcoder.mycommunity新建两个接口,起名为:dao.XxxMapper:

Mapper主要是对应于SQL语句的一些增删改查的方法。

刚才每个实体类都对应了一张表,我们自然需要对每张表都编写一些增删改查的方法,因此与表对应的每个实体类都需要创建对应的Mapper方法。

①DiscussPostMapper

import org.apache.ibatis.annotations.Param;
import java.util.List;

@Mapper
public interface DiscussPostMapper {
    //查询用户个人的所有帖子:用户ID、分页的起始行号、每页展示的数目
    List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);

    //查询行数:@Param 给参数起别名
    //当SQL语句中需要动态的拼接条件,且方法内只有一个参数时,该参数必须起别名
    int selectDiscussPostRows(@Param("userId") int userId);

}

②UserMapper

package com.nowcoder.mycommunity.dao;
import com.nowcoder.mycommunity.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User selectById(int id);

    User selectByName(String username);

    User selectByEmail(String email);

    int insertUser(User user);

    int updateStatus(int id, int status);

    int updateHeader(int id, String headerUrl);

    int updatePassword(int id, String password);
}

6. 创建Mapper对应的xml文件

刚才创建了两个Mapper接口,里面只有方法,方法中的具体内容(即SQL语句)是放在Mapper对应的xml文件里的。

右键resources - new Directory - 起名:mapper

① 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.mycommunity.dao.DiscussPostMapper">
    <!--定义了一个全局的查询字段-->
    <sql id="selectFields">
        id, 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
        <!--条件拼接:如果没有参数userId,就不拼接if标签里的内容-->
        <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>
</mapper>

②user-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.mycommunity.dao.UserMapper">
    <sql id="selectFields">
        id, username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <!--相较于上一个,没有id,因为插入时id为主键,会自动增加-->
    <sql id="insertFields">
        username, password, salt, email, type, status, activation_code, header_url, create_time
    </sql>

    <select id="selectById" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where id = #{id}
    </select>

    <select id="selectByName" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where username = #{username}
    </select>

    <select id="selectByEmail" resultType="User">
        select <include refid="selectFields"></include>
        from user
        where email = #{email}
    </select>

    <insert id="insertUser" parameterType="User" keyProperty="id">
        insert into user (<include refid="insertFields"></include>)
        value (#{username}, #{password},#{salt},#{email},#{type},#{status},#{activationCode},#{headerUrl},#{createTime})
    </insert>

    <update id="updateStatus">
        update user set status = #{status} where id= #{id}
    </update>

    <update id="updateHeader">
        update user set header_url = #{headerUrl} where id= #{id}
    </update>

    <update id="updatePassword">
        update user set password = #{password} where id= #{id}
    </update>
</mapper>

7. Service

右键com.nowcoder.mycommunity,创建两个Service类,起名:service.XxxService。

① DiscussPostService

package com.nowcoder.mycommunity.service;

import com.nowcoder.mycommunity.dao.DiscussPostMapper;
import com.nowcoder.mycommunity.entity.DiscussPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DiscussPostService {
    @Autowired
    private DiscussPostMapper discussPostMapper;

    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);
    }
}

② UserService

package com.nowcoder.mycommunity.service;

import com.nowcoder.mycommunity.dao.UserMapper;
import com.nowcoder.mycommunity.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User findUserById(int id){
        return userMapper.selectById(id);
    }
}

8. 控制器Controller

用来处理请求。

右键com.nowcoder.mycommunity,创建一个控制器类,起名:HomeController。

@Controller
public class HomeController {
    @Autowired
    private DiscussPostService discussPostService;
    @Autowired
    private UserService userService;

    @RequestMapping(path = "/index", method = RequestMethod.GET)
    public String getIndexPage(Model model, Page page){
        //在方法调用前,SpringMVC会自动实例化Model和Page,并将Page注入Model
        //因此在thymeleaf中可以直接访问page对象中的数据

        //展示的总行数
        //传入的id设为0时,我们不以id进行查询,相当于查所有
        page.setRows(discussPostService.findDiscussPostRows(0));
        page.setPath("/index");

        List<DiscussPost> list = discussPostService.findDiscussPosts(0,page.getoffset(),page.getLimit());
        List<Map<String, Object>> discussPosts = new ArrayList<>();
        System.out.println("进入控制器");
        if(list != null){
            for(DiscussPost post:list){
                Map<String, Object> map = new HashMap<>();
                map.put("post",post);
                User user = userService.findUserById(post.getUserId());
                System.out.println(user);
                map.put("user",user);
                discussPosts.add(map);
            }
        }
        model.addAttribute("discussPosts",discussPosts);
        return "index";
    }

    @RequestMapping(path = "/aaa", method = RequestMethod.GET)
    public String aaa(){
        return "aaa";
    }
}

9. 首页 html

右键resources - new Directory - 起名为:templates,右键创建:index.html

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" crossorigin="anonymous">
    <link rel="stylesheet" th:href="@{/css/global.css}" />
    <title>牛客网-首页</title>
</head>
<body>
<div class="nk-container">
    <!-- 头部 -->
    <header class="bg-dark sticky-top">
        <div class="container">
            <!-- 导航 -->
            <nav class="navbar navbar-expand-lg navbar-dark">
                <!-- logo -->
                <a class="navbar-brand" href="#"></a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                    <span class="navbar-toggler-icon"></span>
                </button>
                <!-- 功能 -->
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="index.html">首页</a>
                        </li>
                        <li aclass="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link position-relative" href="site/letter.html">消息<span class="badge badge-danger">12</span></a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="site/register.html">注册</a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical">
                            <a class="nav-link" href="site/login.html">登录</a>
                        </li>
                        <li class="nav-item ml-3 btn-group-vertical dropdown">
                            <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                <img src="http://images.nowcoder.com/head/1t.png" class="rounded-circle" style="width:30px;"/>
                            </a>
                            <div class="dropdown-menu" aria-labelledby="navbarDropdown">
                                <a class="dropdown-item text-center" href="site/profile.html">个人主页</a>
                                <a class="dropdown-item text-center" href="site/setting.html">账号设置</a>
                                <a class="dropdown-item text-center" href="site/login.html">退出登录</a>
                                <div class="dropdown-divider"></div>
                                <span class="dropdown-item text-center text-secondary">nowcoder</span>
                            </div>
                        </li>
                    </ul>
                    <!-- 搜索 -->
                    <form class="form-inline my-2 my-lg-0" action="site/search.html">
                        <input class="form-control mr-sm-2" type="search" aria-label="Search" />
                        <button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
                    </form>
                </div>
            </nav>
        </div>
    </header>

    <!-- 内容 -->
    <div class="main">
        <div class="container">
            <div class="position-relative">
                <!-- 筛选条件 -->
                <ul class="nav nav-tabs mb-3">
                    <li class="nav-item">
                        <a class="nav-link active" href="#">最新</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">最热</a>
                    </li>
                </ul>
                <button type="button" class="btn btn-primary btn-sm position-absolute rt-0" data-toggle="modal" data-target="#publishModal">我要发布</button>
            </div>
            <!-- 弹出框 -->
            <div class="modal fade" id="publishModal" tabindex="-1" role="dialog" aria-labelledby="publishModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-lg" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="publishModalLabel">新帖发布</h5>
                            <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                                <span aria-hidden="true">&times;</span>
                            </button>
                        </div>
                        <div class="modal-body">
                            <form>
                                <div class="form-group">
                                    <label for="recipient-name" class="col-form-label">标题:</label>
                                    <input type="text" class="form-control" id="recipient-name">
                                </div>
                                <div class="form-group">
                                    <label for="message-text" class="col-form-label">正文:</label>
                                    <textarea class="form-control" id="message-text" rows="15"></textarea>
                                </div>
                            </form>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-secondary" data-dismiss="modal">取消</button>
                            <button type="button" class="btn btn-primary" id="publishBtn">发布</button>
                        </div>
                    </div>
                </div>
            </div>
            <!-- 提示框 -->
            <div class="modal fade" id="hintModal" tabindex="-1" role="dialog" aria-labelledby="hintModalLabel" aria-hidden="true">
                <div class="modal-dialog modal-lg" role="document">
                    <div class="modal-content">
                        <div class="modal-header">
                            <h5 class="modal-title" id="hintModalLabel">提示</h5>
                        </div>
                        <div class="modal-body" id="hintBody">
                            发布完毕!
                        </div>
                    </div>
                </div>
            </div>

            <!-- 帖子列表 -->
            <ul class="list-unstyled">
                <li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
                    <a href="site/profile.html">
                        <img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
                    </a>
                    <div class="media-body">
                        <h6 class="mt-0 mb-3">
                            <a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
                            <span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
                            <span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
                        </h6>
                        <div class="text-muted font-size-12">
                            <u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
                            <ul class="d-inline float-right">
                                <li class="d-inline ml-2">赞 11</li>
                                <li class="d-inline ml-2">|</li>
                                <li class="d-inline ml-2">回帖 7</li>
                            </ul>
                        </div>
                    </div>
                </li>
            </ul>
            <!-- 分页 -->
            <!--有数据时,才显示分页-->
            <nav class="mt-5" th:if="${page.rows>0}">
                <ul class="pagination justify-content-center">
                    <!--首页-->
                    <li class="page-item">
                        <!-- 下面会拼为   /index?current=1&limit=5 -->
                        <a class="page-link" th:href="@{${page.path}(current=1,limit=5)}">首页</a>
                    </li>
                    <!--上一页-->
                    <!-- | |中表示有静态变量,动态变量 -->
                    <!-- 当前页为第一页时,无法点击上一页 -->
                    <li th:class="|page-item ${page.current==1?'disabled':''}|">
                        <a class="page-link" th:href="@{${page.path}(current=${page.current-1})}">上一页</a>
                    </li>
                    <!--中间页码数-->
                    <!-- 为当前页时,点亮该页的图标 -->
                    <li th:class="|page-item ${i==page.current?'active':''}|" th:each="i:${#numbers.sequence(page.from,page.to)}">
                        <a class="page-link" th:href="@{${page.path}(current=${i})}" th:text="${i}">1</a>
                    </li>
                    <!--下一页-->
                    <li th:class="|page-item ${page.current==page.total?'disabled':''}|">
                        <a class="page-link" th:href="@{${page.path}(current=${page.current+1})}">下一页</a>
                    </li>
                    <!--末页-->
                    <!-- 当前页为第一页时,无法点击下一页 -->
                    <li class="page-item">
                        <a class="page-link" th:href="@{${page.path}(current=${page.total})}">末页</a>
                    </li>
                </ul>
            </nav>
        </div>
    </div>

    <!-- 尾部 -->
    <footer class="bg-dark">
        <div class="container">
            <div class="row">
                <!-- 二维码 -->
                <div class="col-4 qrcode">
                    <img src="https://uploadfiles.nowcoder.com/app/app_download.png" class="img-thumbnail" style="width:136px;" />
                </div>
                <!-- 公司信息 -->
                <div class="col-8 detail-info">
                    <div class="row">
                        <div class="col">
                            <ul class="nav">
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">关于我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">加入我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">意见反馈</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">企业服务</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">联系我们</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">免责声明</a>
                                </li>
                                <li class="nav-item">
                                    <a class="nav-link text-light" href="#">友情链接</a>
                                </li>
                            </ul>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col">
                            <ul class="nav btn-group-vertical company-info">
                                <li class="nav-item text-white-50">
                                    公司地址:北京市朝阳区大屯路东金泉时代3-2708北京牛客科技有限公司
                                </li>
                                <li class="nav-item text-white-50">
                                    联系方式:010-60728802(电话)&nbsp;&nbsp;&nbsp;&nbsp;admin@nowcoder.com
                                </li>
                                <li class="nav-item text-white-50">
                                    牛客科技©2018 All rights reserved
                                </li>
                                <li class="nav-item text-white-50">
                                    京ICP备14055008号-4 &nbsp;&nbsp;&nbsp;&nbsp;
                                    <img src="http://static.nowcoder.com/company/images/res/ghs.png" style="width:18px;" />
                                    京公网安备 11010502036488号
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </footer>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" crossorigin="anonymous"></script>
<script th:src="@{/js/global.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>

10. 难点说明

①首先Page类与控制器方法的一些交互

Page类有4个属性:当前页、显示页数的上限、总页数、查询路径。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐