梳理 开发流程
WEB 项目主要解决的是浏览器和服务器的交互的问题
浏览器和服务器之间是由若干次请求完成的因此功能可以拆解为一次一次请求;
每次请求的过程:
请求提交给服务器的视图层→视图层主要由controller 和模版构成;conrtoller将请求发送到业务层 让业务组件处理具体业务- 业务层访问数据库 占用数据库访问组件
所以开发顺序倒着来会比较舒服
先开发dao
社区首页
拆解功能:1 显示前10个帖子
2 开发分页组件 分页显示所有的帖子
先开发数据访问层:
先看表discusspost
context :类型text 因为内容长
score :为了给帖子排名
CREATE TABLE `discuss_post` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` varchar(45) DEFAULT NULL,
`title` varchar(100) DEFAULT NULL,
`content` text,
`type` int DEFAULT NULL COMMENT '0-普通; 1-置顶;',
`status` int DEFAULT NULL COMMENT '0-正常; 1-精华; 2-拉黑;',
`create_time` timestamp NULL DEFAULT NULL,
`comment_count` int DEFAULT NULL,
`score` double DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_user_id` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=281 DEFAULT CHARSET=utf8mb3
先开发Dao -先写实体类 根据表的字段添加类的属性并且添加getter setter 方法
package com.nowcoder.community.entity;
import java.util.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 +
'}';
}
}
再做mapper : 添加@Mapper 注解
DiscussPostMapper:
在这里注解@Param 用于给参数取别名 如果只有一个参数并且在写sql 逻辑里有if 那么必须使用,对于一个方法有多个参数 每个参数也要加上@Param
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.DiscussPost;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface DiscussPostMapper {
List<DiscussPost> selectDiscussPosts(@Param("userId")int userId, @Param("offset")int offset, @Param("limit")int limit);
// @Param注解用于给参数取别名,
// 如果只有一个参数,并且在<if>里使用,则必须加别名.
int selectDiscussPostRows(@Param("userId") int userId);
}
<?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>
<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>
</mapper>
在mapper。xml 里配置name -space 然后
根据mapper里的方法名匹配
selectDiscussPosts
对应service
package com.nowcoder.community.service;
import com.nowcoder.community.dao.DiscussPostMapper;
import com.nowcoder.community.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);
}
}
添加主页Controller
package com.nowcoder.community.Controller;
import com.nowcoder.community.entity.DiscussPost;
import com.nowcoder.community.entity.Page;
import com.nowcoder.community.entity.User;
import com.nowcoder.community.service.DiscussPostService;
import com.nowcoder.community.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
//controller 的访问路径可以为空
@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){
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
//1查到前十条数据
List<DiscussPost> list =discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());
List<Map<String,Object>> discussPosts=new ArrayList<>();
if(list!=null){
for (DiscussPost post :list){
Map<String,Object>map=new HashMap<>();
map.put("post",post);
User user=userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
return "/index"; //访问的是templet 之下的index。html
}
}
期间报错
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
解决方法:
在使用jdbc连接到mysql时提示错误:
如果用户使用了 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥;可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者AllowPublicKeyRetrieval=True参数以允许客户端从服务器获取公钥;但是需要注意的是 AllowPublicKeyRetrieval=True可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启。
所以可以用mysql_native_password,不要用sha256_password方式,就不会有问题了。
在驱动链dao接的后面添加参数allowPublicKeyRetrieval=true&useSSL=false
allowPublicKeyRetrieval=true&useSSL=false
2
nested exception is org.apache.ibatis.binding.BindingException Mybatis参数错误
在mapper 里面如果一个方法有不止一个参数,需要给每个参数前添加@Param 注解
首先明确这个注解是为SQL语句中参数赋值而服务的。
@Param的作用就是给参数命名,比如在mapper里面某方法A(int id),当添加注解后A(@Param("userId") int id),也就是说外部想要取出传入的id值,只需要取它的参数名userId就可以了。将参数值传如SQL语句中,通过#{userId}进行取值给SQL的参数赋值。
分页功能:
逻辑: 点页数 (链接)转换成页码 ,将页码传送给服务端
此时 服务端给客户端返回的不止这一页数据 还包括 分页有关的信息例如:当前是第几页(点亮当前页码的块块),最多有多少页 。
封装一个组件便于复用
新建page entity 添加get set 方法后,需要新建几个方法
package com.nowcoder.community.entity;
/**封装分页有关的信息**/
public class Page {
//当前页码,给默认值为1;
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;
}
//数据库 查询时需要
/**获取当前页的起始行*/
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;
return from <1 ? 1:from;
}
public int getTo(){
int to =current+2;
int total =getTotal();
return to >total ? total:to;
}
}
对controller 进行添加和更改
1 对page 的行数和路径进行set
2 更改帖子里的参数 findDiscussPost 里(limit,offset 是页面的limit,offset ) 通过get方法获得
3 需要返回给页面page
在model 里add attribute
这一步可以省略的原因是: 在springmvc 中方法是由dispatchservlet 初始化的
例如Model ,page 所以数据也是它注入 方法调用之前 springmvc 会自动实例化model page 还会将page 注入model。所以 在theymeleaf中可以直接访问Page对象中的数据;
在index.html 处理分页逻辑
1 对分页区域处理: 假设一条数据都没有的时候不限时分页的区域
判断有无数据: 看page 里的rows 或者discusspost
if="${page.rows>0}
首页逻辑: 链接是动态拼的字符串所以th:href=
末页逻辑: 变量通过表达式获取 page.total
上一页下一页逻辑: 等于实际的当前页码—/+1 (current 需要通过page.current 获取变量)
number.sequence : 返回一个连续数字组成的数组
一些细节:如果首页 不能点上一页
不可点样式通过disable 设置
点亮页码: 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" href="#" 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>