第一个大的项目,具体其中的技术细节就不做笔记了,需要的可以自己去牛客官网学习;不过其中没有具体讲这个技术细节,只是教会你该怎么用;
视频链接:https://www.nowcoder.com/study/live/246。
pojo/entity
类;(下面所有的 pojo 类都忽略 get/set 方法)注意:
一些前期的准备工作,顺便测试一下数据库连接:
mybatis.configuration.mapUnderscoreToCamelCase=true
;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;
@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 +
'}';
}
}
@Mapper
注解将当前类标识为一个 mapper 接口;@Mapper
public interface UserMapper {
User selectById(int id);
//根据用户名来查询,用户名是唯一的
User selectByName(String username);
User selectByEmail(String email);
int insertUser(User user);
//以id为条件更新一些参数:状态、头像路径、密码
int updateStatus(int id, int status);
int updateHeader(int id, String headerUrl);
int updatePassword(int id, String password);
}
<?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.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>
@SpringBootTest
public class MapperTests {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectUser() {
User user = userMapper.selectById(101);
System.out.println(user);
user = userMapper.selectByName("liubei");
System.out.println(user);
user = userMapper.selectByEmail("nowcoder101@sina.com");
System.out.println(user);
}
@Test
public void testInsertUser() {
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("test@qq.com");
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);
}
}
/**
* 对应表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;
@Override
public String toString() {
return "DiscussPost{" +
"id=" + id +
", userId=" + userId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", createTime=" + createTime +
", commentCount=" + commentCount +
", score=" + score +
'}';
}
}
userId
,并且使用动态 sql 来实现拼接;offset
-偏移量 和 limit
-每页显示多少帖子 数据;@Mapper
public interface DiscussPostMapper {
//查询所有帖子,要实现登录之后显示我的帖子,也要实现分页显示的功能
List<DiscussPost> selectAllDiscussPosts(int userId,int offset,int limit);
//查询一共有多少条数据
// @Param注解用于给参数取别名,如果只有一个参数,并且在<if>里使用,则必须加别名.
int selectDiscussPostRows(@Param("userId") int userId);
}
注意:
@Param
注解用于给参数取别名,如果只有一个参数,并且在 sql 动态拼接中即 <if>
标签里使用,则必须加别名。*
来显示查询数据,要使用字段,sql 优化之一;#{java属性}
中使用的也是 java 某实体类中对应的属性名称。<?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>
<!--List<DiscussPost> selectAllDiscussPosts(int userId,int offset,int limit);-->
<select id="selectAllDiscussPosts" 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>
<!--int selectDiscussPostRows(@Param("userId") int userId);-->
<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>
解释:
@SpringBootTest
public class DiscussPostTest {
@Autowired
private DiscussPostMapper discussPostMapper;
//用户输入为null表示首页展示
@Test
public void testSelectAllDiscussPosts(){
List<DiscussPost> discussPosts = discussPostMapper.selectAllDiscussPosts(0, 0, 10);
discussPosts.forEach(System.out::println);
}
@Test
public void testSelectDiscussPostRows(){
int discussPostRows = discussPostMapper.selectDiscussPostRows(0);
System.out.println(discussPostRows);
}
}
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User findUserById(int id) {
return userMapper.selectById(id);
}
}
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
//实现主页功能
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model) {
List<DiscussPost> list = discussPostService.findDiscussPosts(0, 0, 10);
//使用一个map容器将所有的User和DiscussPost中的userId关联起来
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost discussPost : list) {
HashMap<String, Object> map = new HashMap<>();
map.put("discussPost", discussPost);
//根据userId获取User对象
User user = userService.findUserById(discussPost.getUserId());
map.put("user", user);
//将这一对关联的对象放进list集合中
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "index";
}
}
将文件引用路径改成 thymeleaf
格式的;
在 html 表头标签加上:xmlns:th="http://www.thymeleaf.org"
;
引入外部 css 样式加上:th:href="@{/css/global.css}"
处理帖子列表部分的内容:
th:each="map:${discussPosts}"
th:src="${map.user.headerUrl}"
th:utext
标签,th:utext="${map.discussPost.title}"
th:if="${map.discussPost.type==1}"
th:if="${map.discussPost.status==1}"
th:utext="${map.user.username}"
th:text="${map.discussPost.createTime}"
注意:
th:text="${#dates.format(xxx.xxx.xxx,'yyyy-MM-dd HH:mm:ss')}"
(这种方法只能修改 Date 格式的日期,不能修改 LocalDateTime 格式(新日期)的数据);<!-- 帖子列表 -->
<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.discussPost.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
<span class="badge badge-secondary bg-primary" th:if="${map.discussPost.type==1}">置顶</span>
<span class="badge badge-secondary bg-danger" th:if="${map.discussPost.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.discussPost.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>
http://localhost:8080/community/index
,观看是否可以显示正确的首页数据(10 条数据);一种是在查询 sql 语句的时候关联查询用户,可以使用 association 处理一对多关系,也可以使用分步查询;
另外一种是在 HomeController 中单独的针对每个 DiscussPost,根据 userId 获取用户数据,并将他们关联起来;使用第二种,后面使用 redis 的时候更方便。
/**
* 封装分页相关的信息.
*/
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
*/
public int getFrom() {
int from = current - 2;
return from < 1 ? 1 : from;
}
/**
* 获取结束页码
* @return
*/
public int getTo() {
int to = current + 2;
int total = getTotal();
return to > total ? total : to;
}
}
getIndexPage(Model model,Page page)
方法,设置总页数,设置复用的路径,将查找的数据变成所有的数据,而不是固定的10条discussPosts
一样需要我们手动放入),所以 thymeleaf 可以直接访问 Page 对象中的数据;//实现主页功能
@RequestMapping(value = "/index", method = RequestMethod.GET)
public String index(Model model, Page page) {
//插入分页功能
//设置总共页数,初始页面,是不存在用户信息的
page.setRows(discussPostService.findDiscussPostRows(0));
//设置复用的路径,这个是给浏览器发送请求用的,所以要加上/
page.setPath("/index");
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
//使用一个map容器将所有的User和DiscussPost中的userId关联起来
List<Map<String, Object>> discussPosts = new ArrayList<>();
if (list != null) {
for (DiscussPost discussPost : list) {
HashMap<String, Object> map = new HashMap<>();
map.put("discussPost", discussPost);
//根据userId获取User对象
User user = userService.findUserById(discussPost.getUserId());
map.put("user", user);
//将这一对关联的对象放进list集合中
discussPosts.add(map);
}
}
model.addAttribute("discussPosts", discussPosts);
return "index";
}
th:if="${page.rows>0}"
th:href="@{${page.path}(current=1)}"
,这里传送的请求其实就是: /index?current=1
;th:href="@{${page.path}(current=${page.total})}"
th:each="i:${#numbers.sequence(page.from,page.to)}"
,跳转到对应页面:th:href="@{${page.path}(current=${i})}"
th:class="|page-item ${page.current==1?'disabled':''}|"
th:class="|page-item ${i==current?'active':''}|"
<!-- 分页 -->
<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}">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>
http://localhost:8080/community/index
,观看是否可以显示正确的显示所有分页数据;一开始用的是 5.3.4 版本,发现会报错:初学Spring遇到Unsupported class file major version 61 错误
找不到 @RunWith
注解,高版本取消了这个注解,直接使用@SpringBootTest
注解就可以了,@RunWith注解找不到,怎么办?
访问一个页面,发送请求,没有得到想象中的响应页面,原因是粗心错误:访问一个servlet却直接下载文件
@SpringBootApplication
:定义主程序@SpringBootConfiguration
:当前类当作一个配置文件@EnableAutoConfiguration
:允许配置文件的自动装配@ComponentScan
:自动扫描配置类所在的包以及所有子包下的bean@Component,@Controller,@Service,@Repository
@ContextConfiguration(classes = CommunityApplication.class)
,这个测试类要implements ApplicationContextAware
,重写其中的setApplicationContext
方法@Primary
:当使用 @Autowired
来根据类型进行属性注入的时候,或者 IOC 容器根据类型来进行 getBean()
装配的时候,优先被提取的类@Repository("helloDao")
:字符串中定义bean的名字,默认是首字母小写的类名@PostConstruct
:在类的构造器之后执行@PreDestroy
:在类的销毁之前执行@Scope(value = "prototype")
:定义多例beanrequest.getMethod()
request.getServletPath()
request.getHeaderNames();request.getHeader(name);
request.getParameter("code")
mysql -uroot -p
,输入这个之后回车输入密码是隐藏的;alter user root@localhost identified by 'abc123';
,不要忘记最后的分号;create database communtiy;
show databases;
drop database communtiy;
use community;