安装 mysql5.7 或者 mysql8 数据库都可以无所谓
然后找到牛客提供的数据库 sql 文件
在该文件目录下打开命令行
依次输入:
mysql -u root -p
create database community
use community
source ./init-schema.sql
此时就自动把所有要用到的数据表导进去了,但是目前表中仍然没有任何数据,故需要添加对应数据
开启 navicat,连接到 community 数据库,再把 init_data.sql 拖拽进去就可以导入所有数据了
你需要关注的就是下面几个配置项:
代码清单:application.properties
server.port=10086
# 指定应用的端口号为 10086
server.servlet.context-path=/community
# 指定应用的上下文路径为 "/community",即应用的根路径为 "/community"
# ThymeleafProperties
spring.thymeleaf.cache=false
# 配置 Thymeleaf 模板引擎的缓存为关闭状态,方便开发阶段的模板修改和调试
# DataSourceProperties
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 数据库驱动类
spring.datasource.url=jdbc:mysql://localhost:3306/community?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong
# 数据库连接URL,指定数据库为localhost的3306端口上的community数据库,使用UTF-8字符编码,禁用SSL,时区设置为Hongkong
spring.datasource.username=root
# 数据库用户名
spring.datasource.password=123456
# 数据库密码
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
# 数据源类型为HikariDataSource,使用HikariCP连接池
spring.datasource.hikari.maximum-pool-size=15
# 连接池的最大连接数为15
spring.datasource.hikari.minimum-idle=5
# 连接池的最小空闲连接数为5
spring.datasource.hikari.idle-timeout=30000
# 连接的最大空闲时间为30秒
# MybatisProperties
mybatis.mapper-locations=classpath:mapper/*.xml
# MyBatis映射文件的位置,使用classpath:mapper/*.xml表示在类路径下的mapper目录中查找映射文件
mybatis.type-aliases-package=com.zhiller.community.zhillercommunity.entity
# MyBatis实体类的包名,用于自动扫描实体类的别名
mybatis.configuration.useGeneratedKeys=true
# 配置MyBatis使用自动生成的主键
mybatis.configuration.mapUnderscoreToCamelCase=true
# 配置MyBatis将下划线命名的数据库字段映射为驼峰命名的Java属性
注意,如果你使用的是 mysql5,那么对应的数据连接池就要写
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
如果是 mysql8,就写
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
定义查询对象
对象是一个 User 实体,它对应 community 数据库里的数据表 user
代码清单:entity/User.java
package com.zhiller.community.zhillercommunity.entity;
import java.util.Date;
public class User {
private int id;
private String username;
private String password;
private String salt;
private String email;
private int type;
private int status;
private String activationCode;
private String headerUrl;
private Date createTime;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getActivationCode() {
return activationCode;
}
public void setActivationCode(String activationCode) {
this.activationCode = activationCode;
}
public String getHeaderUrl() {
return headerUrl;
}
public void setHeaderUrl(String headerUrl) {
this.headerUrl = headerUrl;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", salt='" + salt + '\'' +
", email='" + email + '\'' +
", type=" + type +
", status=" + status +
", activationCode='" + activationCode + '\'' +
", headerUrl='" + headerUrl + '\'' +
", createTime=" + createTime +
'}';
}
}
配置 mybatis 的第一步就是需要定义 Mapper
mapper 用于设置操作数据库的方法
代码清单:/dao/UserMapper.java
package com.zhiller.community.zhillercommunity.dao;
import com.zhiller.community.zhillercommunity.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);
}
既然设置了方法,那么就需要配置方法对应的 SQL 查询语句
mybatis 简化了这一操作,使用 xml 来配置所有的 SQL 语句
一个 XML 配置文件对应一个 mapper 文件
代码清单:resource/mapper/user-mapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiller.community.zhillercommunity.dao.UserMapper">
<sql id="insertFields">
username, password, salt, email, type, status, activation_code, header_url, create_time
sql>
<sql id="selectFields">
id, 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>)
values(#{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>
依据主测试类编写专属于 mybatis 的测试类
如果下方代码运行成功,则证明数据库链接正确!
package com.zhiller.community.zhillercommunity;
import com.zhiller.community.zhillercommunity.dao.UserMapper;
import com.zhiller.community.zhillercommunity.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = ZhillerCommunityApplication.class)
public class MapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectUser() {
// 根据id查询用户
User user = userMapper.selectById(101);
System.out.println(user);
// 根据用户名查询用户
user = userMapper.selectByName("liubei");
System.out.println(user);
// 根据邮箱查询用户
user = userMapper.selectByEmail("[email protected]");
System.out.println(user);
}
@Test
public void testInsertUser() {
// 创建一个新用户对象
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("[email protected]");
user.setHeaderUrl("http://www.nowcoder.com/101.png");
user.setCreateTime(new Date());
// 插入用户
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
@Test
public void updateUser() {
// 更新用户状态
int rows = userMapper.updateStatus(150, 1);
System.out.println(rows);
// 更新用户头像
rows = userMapper.updateHeader(150, "http://www.nowcoder.com/102.png");
System.out.println(rows);
// 更新用户密码
rows = userMapper.updatePassword(150, "hello");
System.out.println(rows);
}
}
根据上一节所述经验,我们应该能很快根据数据库中结构构建对应的 entity
类
故依葫芦画瓢,写出 DiscussPost
数据表的 entity
类
代码清单:DiscussPost.java
package com.zhiller.community.zhillercommunity.entity;
import java.sql.Date;
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;
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 String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
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;
}
public int getCommentCount() {
return commentCount;
}
public void setCommentCount(int commentCount) {
this.commentCount = commentCount;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "DiscussPost{" +
"id=" + id +
", userId=" + userId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", createTime=" + createTime +
", commentCount=" + commentCount +
", score=" + score +
'}';
}
}
既然我们要查询,则必然需要调用 mybatis
所以再创建一个 mapper 接口来表示我们将要使用到的 SQL 方法
代码清单:/dao/DiscussPostMapper.java
package com.zhiller.community.zhillercommunity.dao;
import com.zhiller.community.zhillercommunity.entity.DiscussPost;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface DiscussPostMapper {
// 查询所有的DiscussPost
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
// @Param注解用于给参数取别名,
// 如果只有一个参数,并且在里使用,则必须加别名.
int selectDiscussPostRows(@Param("userId") int userId);
}
别忘了在 resource 文件下注册对应的 mapper 配置文件
代码清单:resource/mapper/discusspost-mapper.xml
DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhiller.community.zhillercommunity.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"/>
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>
mapper>
编写对应测试方法
package com.zhiller.community.zhillercommunity;
import com.zhiller.community.zhillercommunity.dao.DiscussPostMapper;
import com.zhiller.community.zhillercommunity.dao.UserMapper;
import com.zhiller.community.zhillercommunity.entity.DiscussPost;
import com.zhiller.community.zhillercommunity.entity.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Date;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = ZhillerCommunityApplication.class)
public class MapperTest {
// 自动装配别忘了!!!
@Autowired
private DiscussPostMapper discussPostMapper;
@Test
public void testDiscussPost() {
List<DiscussPost> discussPostList = discussPostMapper.selectDiscussPosts(0, 0, 10);
for (DiscussPost discussPost : discussPostList) {
System.out.println(discussPost);
}
int rows = discussPostMapper.selectDiscussPostRows(149);
System.out.println(rows);
}
}
对应的 Service 实际上很简单,就和我们的测试方法一样,也是两步走:
代码清单:/service/DiscussPostService.java
package com.zhiller.community.zhillercommunity.service;
import com.zhiller.community.zhillercommunity.dao.DiscussPostMapper;
import com.zhiller.community.zhillercommunity.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);
}
}
定制了完善的 entity、service,接下来就是使用 controller 控制界面的显示了
由于我们是后端开发,无需理会前端如何,除非你是全栈;
前端部分由 thymeleaf 借助模板搞定,虽然没有做到前后端分离,但是也还是可以的;
代码清单:/controller/HomeController.java
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@GetMapping("/index")
public String getIndexPage(Model model, Page page) {
// 方法调用钱,SpringMVC会自动实例化Model和Page,并将Page注入Model.
// 所以,在thymeleaf中可以直接访问Page对象中的数据.
// 分页查询实体Page我们还没有定义,此刻会飘红,先不管
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
// 获取所有DiscussPost
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
// discussPosts将会被用作列表渲染
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost post : list) {
Map<String, Object> map = new HashMap<>();
// post和user为对应参数,后续html文件里面会用到
map.put("post", post);
User user = userService.findUserById(post.getUserId());
map.put("user", user);
discussPosts.add(map);
}
}
// 向html模板提供一个可用参数,参数指向discussPosts
model.addAttribute("discussPosts", discussPosts);
return "/index";
}
}
下面是一些常见的模板介绍
th:each="map:${discussPosts}
th:src="${map.user.headerUrl}"
th:src 用于动态绑定图片地址
既然我们已经确定了子项别名,故可以直接通过该别名取出对应数据
th:utext="${map.post.title}
th:utext 是 Thymeleaf 的内联表达式,用于将表达式的结果作为不经过 HTML 转义的文本输出
th:if="${map.post.type==1}
可以执行判断操作,动态显示或隐藏部分标签
th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"
可以内含指令,来改变输出格式
开始制作分页查询模块
创建对应的 entity 类(注意!并不是说所有的 entity 都必须对应数据表,比如这个 page 就是为了索引用的)
代码清单:entity/page.java
package com.zhiller.community.zhillercommunity.entity;
public class Page {
// 当前页码
private int current = 1;
// 显示上限
private int limit = 10;
// 数据总数(用于计算总页数)
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;
}
/**
* 获取当前页的起始行
*
* @return
*/
public int getOffset() {
// current * limit - limit
return (current - 1) * limit;
}
/**
* 获取总页数
*
* @return
*/
public int getTotal() {
// rows / limit [+1]
if (rows % limit == 0) {
return rows / limit;
} else {
return rows / limit + 1;
}
}
/**
* 获取起始页码
*
* @return int
*/
public int getFrom() {
int from = current - 2;
return from < 1 ? 1 : from;
}
/**
* 获取结束页码
*
* @return int
*/
public int getTo() {
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}
}
对应的就是分页查询的 thymeleaf 模板代码
<nav
class="mt-5"
th:if="${page.rows>0}"
>
<ul class="pagination justify-content-center">
<li class="page-item">
<a
class="page-link"
th:href="@{${page.path}(current=1)}"
>首页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}"
>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>