WEB后端_Day05(MVC概念、图书模块、分页、Cookie)
MVC 概念
MVC 全称:Model 模型、View 视图、Controller 控制器。
MVC 最早出现在JavaEE 三层中的Web 层,它可以有效的指导Web 层的代码如何有效分离,单独工作。
- View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
- Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。
- Model 模型:将与业务逻辑相关的数据封装为具体的JavaBean 类,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo。
MVC 是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了降低耦合度)。
图书模块
编写图书模块的数据库表
create table t_book(
`id` int primary key auto_increment,
`name` varchar(100),
`price` decimal(11,2),
`author` varchar(100),
`sales` int,
`stock` int,
`img_path` varchar(200)
);
## 插入初始化测试数据
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'java 从入门到放弃' , '国哥' , 80 , 9999 , 9 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '数据结构与算法' , '严敏君' , 78.5 , 6 , 13 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '怎样拐跑别人的媳妇' , '龙伍' , 68, 99999 , 52 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '木虚肉盖饭' , '小胖' , 16, 1000 , 50 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'C++编程思想' , '刚哥' , 45.5 , 14 , 95 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '蛋炒饭' , '周星星' , 9.9, 12 , 53 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '赌神' , '龙伍' , 66.5, 125 , 535 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'Java 编程思想' , '阳哥' , 99.5 , 47 , 36 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'JavaScript 从入门到精通' , '婷姐' , 9.9 , 85 , 95 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'cocos2d-x 游戏编程入门' , '国哥' , 49, 52 , 62 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'C 语言程序设计' , '谭浩强' , 28 , 52 , 74 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'Lua 语言程序设计' , '雷丰阳' , 51.5 , 48 , 82 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '西游记' , '罗贯中' , 12, 19 , 9999 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '水浒传' , '华仔' , 33.05 , 22 , 88 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '操作系统原理' , '刘优' , 133.05 , 122 , 188 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '数据结构java 版' , '封大神' , 173.15 , 21 , 81 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'UNIX 高级环境编程' , '乐天' , 99.15 , 210 , 810 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , 'javaScript 高级编程' , '国哥' , 69.15 , 210 , 810 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '大话设计模式' , '国哥' , 89.15 , 20 , 10 , 'static/img/default.jpg');
insert into t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
values(null , '人月神话' , '刚哥' , 88.15 , 20 , 80 , 'static/img/default.jpg');
## 查看表内容
select id,name,author,price,sales,stock,img_path from t_book;
编写实体
public class Book {
private Integer id;
private String name;
private String author;
private BigDecimal price;
private Integer sales;
private Integer stock;
private String imgPath = "static/img/default.jpg";
Dao接口
public interface BookDao {
//新增图书
public int addBook(Book book);
//根据id删除图书
public int deleteBookById(Integer id);
//修改图书
public int updateBook(Book book);
//根据id查询一本书
public Book queryBookById(Integer id);
//查询所有图书
public List<Book> queryBooks();
}
Dao的实现
public class BookDaoImpl extends BaseDAO implements BookDao {
@Override
public int addBook(Book book) {
String sql = "insert into t_book(name,author,price,sales,stock,img_path) values(?,?,?,?,?,?)";
return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath());
}
@Override
public int deleteBookById(Integer id) {
String sql = "delete from t_book where id=?";
return update(sql,id);
}
@Override
public int updateBook(Book book) {
String sql = "update t_book set price=? , sales=? , stock=? where id=?";
return update(sql,book.getPrice(),book.getSales(),book.getStock(),book.getId());
}
@Override
public Book queryBookById(Integer id) {
String sql = "select * from t_book where id=?";
return queryForOne(Book.class,sql,id);
}
@Override
public List<Book> queryBooks() {
String sql = "select * from t_book";
return queryForList(Book.class,sql);
}
}
Service接口
public interface IBookService {
public void addBook(Book book);
public void deleteBookById(Integer id);
public void updateBook(Book book);
public Book queryBookById(Integer id);
public List<Book> queryBooks();
}
Service实现
public class BookServiceImpl implements IBookService {
private BookDao bookDao = new BookDaoImpl();
@Override
public void addBook(Book book) {
bookDao.addBook(book);
}
@Override
public void deleteBookById(Integer id) {
bookDao.deleteBookById(id);
}
@Override
public void updateBook(Book book) {
bookDao.updateBook(book);
}
@Override
public Book queryBookById(Integer id) {
return bookDao.queryBookById(id);
}
@Override
public List<Book> queryBooks() {
return bookDao.queryBooks();
}
}
实现图书的后台管理
进入后台管理页面‘
点击图书管理
进入到图书列表页 在该页可以对图书进行新增 删除 修改操作
实现数据的修改:
1 通过修改向后台传递id
2 在后台通过id查询到对应的记录 并跳转到manager_edit.jsp 在页面实现数据的回显
3 当用户修改完数据之后 点击提交按钮,此时将修改之后的数据提交到后台,后台执行真正的更新操作
<td><a href="book?_method=findBook&id=${book.id}">修改</a></td>
public void findBook(HttpServletRequest req , HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
Integer intId = null;
if(!"".equals(id)&&id!=null){
intId = Integer.parseInt(id) ;
}
Book book = bookService.queryBookById(intId);
req.setAttribute("book",book);
req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req,resp);
}
public void update(HttpServletRequest req , HttpServletResponse resp) throws ServletException, IOException {
String priceStr = req.getParameter("price");
String stockStr = req.getParameter("stock");
String salesStr = req.getParameter("sales");
//在解析之前都需要判断字符串不能weinull和空串 否则解析失败
BigDecimal price = BigDecimal.valueOf(Double.parseDouble(priceStr));
Integer stock = Integer.parseInt(stockStr);
Integer sales = Integer.parseInt(salesStr);
String name = req.getParameter("name");
String author= req.getParameter("author");
String idStr = req.getParameter("id");
Integer id = Integer.parseInt(idStr);
Book book = new Book(id,name,author,price,sales,stock,null);
bookService.updateBook(book);
//当修改 成功之后 重新查询所有图书 可以看到修改的效果
list(req,resp);
}
删除
1 当点击删除按钮 删除不能直接删除 需要经过用户的二次确认
2 当用户在确认框上点击了取消则不执行删除 如果点击确认 则执行删除
<td><a href="book?_method=delete&id=${book.id}" class="del">删除</a></td>
<script type="text/javascript">
$(function (){
$(".del").click(function (){
var flag = confirm("您确定要删除这本书吗?");
return flag;
})
})
</script>
public void delete(HttpServletRequest req , HttpServletResponse resp) throws ServletException, IOException {
String id = req.getParameter("id");
bookService.deleteBookById(Integer.parseInt(id));
//当修改 成功之后 重新查询所有图书 可以看到修改的效果
list(req,resp);
}
删除分为物理删除和逻辑删除
物理删除指的是直接执行sql的delete语句 将数据直接从数据库删掉
逻辑删除 数据依然存在数据库中,只是对数据的有效性标记做了修改 这样的删除的本质是修改
新增
<form action="book">
<input type="hidden" name="_method" value="update">
<input type="hidden" name="id" value="${book.id}">
<table>
<tr>
<td colspan="7" style="font-size:28px;font-weight:800">编辑图书</td>
</tr>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<tr>
<td>
<c:if test="${!empty book}">
<input name="name" disabled="disabled" type="text" value="${book.name}"/>
</c:if>
<c:if test="${empty book}">
<input name="name" type="text" value="${book.name}"/>
</c:if>
</td>
<td><input name="price" type="text" value="${book.price}"/></td>
<td>
<c:if test="${empty book}">
<input name="author" type="text" value="${book.author}"/>
</c:if>
<c:if test="${!empty book}">
<input name="author" disabled="disabled" type="text" value="${book.author}"/>
</c:if>
</td>
<td><input name="sales" type="text" value="${book.sales}"/></td>
<td><input name="stock" type="text" value="${book.stock}"/></td>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>
public void update(HttpServletRequest req , HttpServletResponse resp) throws ServletException, IOException {
String priceStr = req.getParameter("price");
String stockStr = req.getParameter("stock");
String salesStr = req.getParameter("sales");
//在解析之前都需要判断字符串不能weinull和空串 否则解析失败
BigDecimal price = BigDecimal.valueOf(Double.parseDouble(priceStr));
Integer stock = Integer.parseInt(stockStr);
Integer sales = Integer.parseInt(salesStr);
String name = req.getParameter("name");
String author= req.getParameter("author");
String idStr = req.getParameter("id");
Integer id = null;
if(idStr != null && !"".equals(idStr)){
id = Integer.parseInt(idStr);
}
Book book = new Book(id,name,author,price,sales,stock,null);
//判断id是否为空 如果id为空 则是新增 否则为修改
if(id == null ){
bookService.addBook(book);
}else{
bookService.updateBook(book);
}
//当修改 成功之后 重新查询所有图书 可以看到修改的效果
list(req,resp);
}
分页
分析分页中所需要的数据:
-
当前页 pageNo 当前页面布局 用户输入
-
总页数 pageTotal 计算 总记录数/ 每页显示的记录数
-
总记录数 totalCount 查询数据库 select count(*) from 表名 dao
-
每页显示的记录数 pageSize 一般是常量 可以固定 也可以由用户决定
-
当前页的数据 pageList 通过数据库查询而来 select * from table limit begin pageSize; dao
begin= (pageNo -1) * pageSize
实现的思路:
-
BookServlet 接收两个参数 : 当前第几页 每页显示的记录数
-
将接收到的参数传递给service 去调用dao的相关操作 将结果进行封装 返回给servlet
-
在dao层 主要查询总记录数 当前页所需要的数据
分页数据的封装
public class Page<T> {
public static final int PAGESIZE= 2;
private int pageNo;//当前页码
private int pageTotal;//总页数
private int totalCount;//总计路数
private List<T> pageList;
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getPageTotal() {
return pageTotal;
}
public void setPageTotal(int pageTotal) {
this.pageTotal = pageTotal;
}
public int getTotalCount() {
return totalCount;
}
public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}
public List<T> getPageList() {
return pageList;
}
public void setPageList(List<T> pageList) {
this.pageList = pageList;
}
}
Servlet
public void list(HttpServletRequest req , HttpServletResponse resp) throws ServletException, IOException {
String pageNoStr = req.getParameter("pageNo");
Integer pageNo = 1;
if(!"".equals(pageNoStr)&&pageNoStr!=null){
pageNo = Integer.parseInt(pageNoStr);
}
Page<Book> page = bookService.pageList(pageNo,Page.PAGESIZE);
req.setAttribute("page",page);
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
}
service接口
public Page<Book> pageList(int pageNo,int pageSize);
Service的实现
@Override
public Page<Book> pageList(int pageNo, int pageSize) {
Page<Book> page = new Page<>();
// 总记录数
int totalCount = bookDao.selectCount();
//总页数
int pageTotal = totalCount / pageSize;
if(totalCount % pageSize > 0){
pageTotal+=1;
}
List<Book> pageBooks = bookDao.selectPage((pageNo-1) * pageSize,pageSize);
page.setPageList(pageBooks);
page.setPageNo(pageNo);
page.setTotalCount(totalCount);
page.setPageTotal(pageTotal);
return page;
}
dao接口
//查询总记录数
public int selectCount();
//查询当前页的数据
public List<Book> selectPage(int begin,int pageSize);
dao的实现
@Override
public int selectCount() {
String sql = "select count(*) from t_book";
Number number = (Number)queryForSingleValue(sql);
return number.intValue();
}
@Override
public List<Book> selectPage(int begin, int pageSize) {
String sql = "select * from t_book limit ?,?";
return queryForList(Book.class,sql,begin,pageSize);
}
分页组件
<div id="page_nav">
<c:if test="${requestScope.page.pageNo > 1}">
<a href="book?_method=list&pageNo=1">首页</a>
<a href="book?_method=list&pageNo=${page.pageNo-1}">上一页</a>
</c:if>
<c:choose>
<c:when test="${page.pageTotal <= 5}">
<c:set var="begin" value="1"/>
<c:set var="end" value="${page.pageTotal}"/>
</c:when>
<c:when test="${page.pageTotal > 5}">
<c:choose>
<c:when test="${page.pageNo <= 3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<c:when test="${page.pageNo > requestScope.page.pageTotal - 3}">
<c:set var="begin" value="${page.pageTotal - 4}"/>
<c:set var="end" value="${page.pageTotal}"/>
</c:when>
<c:otherwise>
<c:set var="begin" value="${page.pageNo - 2}"/>
<c:set var="end" value="${page.pageNo + 2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == page.pageNo}">
【${i}】
</c:if>
<c:if test="${i != page.pageNo}">
<a href="book?_method=list&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
<c:if test="${page.pageNo < page.pageTotal}">
<a href="book?_method=list&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="book?_method=list&pageNo=${page.pageTotal}">末页</a>
</c:if>
共${page.pageTotal}页,共${page.totalCount}条记录 到第<input value="${page.pageNo}" name="pn" id="pn_input"/>页
<input type="button" id="subBtn" value="确定">
</div>
跳转到指定页
$("#subBtn").click(function (){
var no = $("#pn_input").val()
location.href="http://localhost:8080/book/book?_method=list&pageNo="+no;
})
删除修改回到数据所在的页面
在删除修改的过程中 需要全程携带当前页的页数
<td><a href="book?_method=findBook&id=${book.id}&pageNo=${page.pageNo}">修改</a></td>
前提
提出问题
- HTTP协议是一种无状态的协议,WEB服务器本身不能识别出哪些请求是同一个浏览器发出的 ,浏览器的每一次请求都是完全孤立的
- 即使 HTTP1.1 支持持续连接,但当用户有一段时间没有提交请求,连接也会关闭。
- 怎么才能实现网上商店中的购物车呢:某个用户从网站的登录页面登入后,再进入购物页面购物时,负责处理购物请求的服务器程序必须知道处理上一次请求的程序所得到的用户信息。
- 作为 web 服务器,必须能够采用一种机制来唯一地标识一个用户,同时记录该用户的状态
会话和会话状态
- WEB应用中的会话是指一个客户端浏览器与WEB服务器之间连续发生的一系列请求和响应过程。
- WEB应用的会话状态是指WEB服务器与浏览器在会话过程中产生的状态信息,借助会话状态,WEB服务器能够把属于同一会话中的一系列的请求和响应过程关联起来。
如何实现有状态的会话
- WEB服务器端程序要能从大量的请求消息中区分出哪些请求消息属于同一个会话,即能识别出来自同一个浏览器的访问请求,这需要浏览器对其发出的每个请求消息都进行标识:属于同一个会话中的请求消息都附带同样的标识号,而属于不同会话的请求消息总是附带不同的标识号,这个标识号就称之为会话ID(SessionID)。
- 在 Servlet 规范中,常用以下两种机制完成会话跟踪
- Cookie
- Session
Cookie
什么是Cookie?
- cookie机制采用的是在客户端保持 HTTP 状态信息的方案
- Cookie是在浏览器访问WEB服务器的某个资源时,由WEB服务器在HTTP响应消息头中附带传送给浏览器的一个小文本文件。
- 一旦WEB浏览器保存了某个Cookie,那么它在以后每次访问该WEB服务器时,都会在HTTP请求头中将这个Cookie回传给WEB服务器。
- 底层的实现原理: WEB服务器通过在HTTP响应消息中增加Set-Cookie响应头字段将Cookie信息发送给浏览器,浏览器则通过在HTTP请求消息中增加Cookie请求头字段将Cookie回传给WEB服务器。
- 一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。
- 一个WEB站点可以给一个WEB浏览器发送多个Cookie,一个WEB浏览器也可以存储多个WEB站点提供的Cookie。
- 浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4KB。
如何创建Cookie
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//创建Cookie
Cookie cookie = new Cookie("key", "Cookie_value");
//将cookie添加到相应头中
resp.addCookie(cookie);
}
获取Cookie
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取所有cookie
Cookie[] cookies = req.getCookies();
//在其中查找某个特定的cookie
for(Cookie cookie : cookies){
String key= cookie.getName();
if(key.equals("key")){
String value = cookie.getValue();
System.out.println("value = " + value);
}
}
}
封装查找cookie的工具类
public class CookieUtils {
public static Cookie getCookie(String name, HttpServletRequest req, HttpServletResponse resp){
Cookie[] cookies = req.getCookies();
if(name==null || cookies.length == 0 ||cookies==null){
return null;
}else{
//在其中查找某个特定的cookie
for(Cookie cookie : cookies){
String key= cookie.getName();
if(key.equals("key")){
return cookie;
}
}
}
return null;
}
}
Cookie 值的修改
方案一:
1、先创建一个要修改的同名(指的就是key)的Cookie 对象
2、在构造器,同时赋于新的Cookie 值。
3、调用response.addCookie( Cookie );
// 1、先创建一个要修改的同名的Cookie 对象
// 2、在构造器,同时赋于新的Cookie 值。
Cookie cookie = new Cookie("key1","newValue1");
// 3、调用response.addCookie( Cookie ); 通知客户端保存修改
resp.addCookie(cookie);
方案二:
1、先查找到需要修改的Cookie 对象
2、调用setValue()方法赋于新的Cookie 值。
3、调用response.addCookie()通知客户端保存修改
Cookie cookie = CookieUtils.getCookie("key",req,resp);
cookie.setValue("zhongbei");
resp.addCookie(cookie);
Cookie 生命控制
Cookie 的生命控制指的是如何管理Cookie 什么时候被销毁(删除)
setMaxAge()
正数,表示在指定的秒数后过期
负数,表示浏览器一关,Cookie 就会被删除(默认值是-1)
零,表示马上删除Cookie
//创建Cookie
Cookie cookie = new Cookie("key", "Cookie_value");
cookie.setMaxAge( 60 * 60);
//将cookie添加到相应头中
resp.addCookie(cookie);
Cookie 有效路径Path 的设置
Cookie 的path 属性可以有效的过滤哪些Cookie 可以发送给服务器。哪些不发。
path 属性是通过请求的地址来进行有效的过滤。
CookieA path=/工程路径
CookieB path=/工程路径/abc
请求地址如下:
http://ip:port/工程路径/a.jsp
-
- CookieA 发送
- CookieB 不发送
- CookieA 发送
http://ip:port/工程路径/abc/a.jsp
-
-
CookieA 发送
- CookieB 发送
-
protected void testPath(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
Cookie cookie = new Cookie("path1", "path1");
// getContextPath() ===>>>> 得到工程路径
cookie.setPath( req.getContextPath() + "/abc" ); // ===>>>> /工程路径/abc
resp.addCookie(cookie);
resp.getWriter().write("创建了一个带有Path 路径的Cookie");
}
//将cookie添加到相应头中
resp.addCookie(cookie);
Cookie 有效路径Path 的设置
Cookie 的path 属性可以有效的过滤哪些Cookie 可以发送给服务器。哪些不发。
path 属性是通过请求的地址来进行有效的过滤。
CookieA path=/工程路径
CookieB path=/工程路径/abc
请求地址如下:
http://ip:port/工程路径/a.jsp
-
- CookieA 发送
- CookieB 不发送
- CookieA 发送
http://ip:port/工程路径/abc/a.jsp
-
-
CookieA 发送
- CookieB 发送
-
protected void testPath(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
Cookie cookie = new Cookie("path1", "path1");
// getContextPath() ===>>>> 得到工程路径
cookie.setPath( req.getContextPath() + "/abc" ); // ===>>>> /工程路径/abc
resp.addCookie(cookie);
resp.getWriter().write("创建了一个带有Path 路径的Cookie");
}