JavaWeb开发知识总结(表单重复提交,数据分页)
1. 表单重复提交
1.1 表单重复提交的原因
1. 当在数据添加页面提交表单到后台后,后台的Servlet中处理完数据后通过转发到其他页面,此时的客户端浏览器中地址是没有变化的,此时刷新(浏览器刷新按钮会重复提交数据,当在地址栏中回车时是重新请求不会重复提交数据)页面时,之前的表单数据会被重复的提交.
2. 当客户端的网速较慢时,用户提交表单向服务器传输数据较慢,第一次点击提交表单数据后数据已经在向服务器提交数据,但网速较慢,此时用户持续的点击提交表单按钮,表单数据会被重复提交.
1.2 解决表单重复提交问题
解决表单重复提交的思想是:使用令牌机制(一次性),就是客户端的表单有一个口令数据,服务器端有一个口令数据,当这两个口令数据相同时,说明是第一次提交数据,则允许添加数据操作;操作完成后销毁服务器中的口令数据,当表单重复提交时,服务器没有口令数据,则不允许添加数据操作,即避免了表单的重复提交.
解决重复提交思路1:用户第一次访问添加数据的表单页面时,为此次表单生成一个唯一的口令(可以为字符串等),并将这个口令存在session域中,并将产生的口令存在表单的隐藏字段,当提交表单时同时将口令字段提交到服务器;在服务器中获取表单中的口令数据及获取保存在session中的口令数据,当两个口令相同时,允许数据添加的操作,并在完成操作后将保存在session中口令数据清除掉,则下次再重复提交本次的表单数据时,session中的口令数据不存在了,则避免了同一个表单数据的重复提交.
解决重复提交思路2:用户第一次访问添加数据的表单页面时,为此次表单生成一个唯一的口令(可以为字符串等),并将这个口令存在session域中,提交表单到服务器;在服务器中获取保存在session中的口令数据,当session中口令数据不为null时说明表单时第一次提交,允许数据添加的操作,并在完成操作后将保存在session中口令数据清除掉,则下次再重复提交本次的表单数据时,session中的口令数据不存在了,则避免了同一个表单数据的重复提交.
案例代码:
<!--表单数据jsp-->
<form action="${pageContext.request.contextPath }/ProductAddServlet" method="post">
<%
// 定义令牌口令,防止表单的重复提交
String token = UUIDUtils.createString();
// 将口令存在session中传递给增加商品Servlet
request.getSession().setAttribute("token", token);
%>
<input type="hidden" name="token" value="${token }">
...
<tr>
<td colspan="2">
<input type="submit" value="提交"/> <input type="reset" value="重置"/> <input type="button" id="back" value="返回"/>
</td>
</tr>
</table>
</form>
/**
* 接收数据添加的Servlet
*/
public class ProductAddServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
// 获取表单中口令字符产
String token1 = request.getParameter("token");
// 获取session中存储的口令
String token2 = (String) request.getSession().getAttribute("token");
// 当两个口不匹配时,跳转提示
if(!token1.equals(token2)) {
// if(token2 == null) { // 利用存储在session中口令数据是否为空时判断表单是否重复提交
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("对不起,表单不能重复提交!,浏览器将跳转到主页面.....");
response.setHeader("Refresh", "4;"+request.getContextPath()+"/ProductQueryAllServlet");
// 当向客户端输出数据后,需要停止后续代码的执行,如果不用return,后续代码会继续执行
return;
}
// 清除口令字符串
request.getSession().removeAttribute("token");
... // 其他的数据操作
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
/**
* 随机字符串工具类
*/
public class UUIDUtils {
/**
* 生成随机的字符串
* @return
*/
public static String createString() {
// 生成字符串是32位形式
return UUID.randomUUID().toString().replace("-", "");
}
}
2. 数据的分页显示
数据的分页显示:当从数据库查询的数据记录较多时,需要使用分页的方式进行数据的显示.
2.1 数据分页功能实现的方式: 一般使用物理分页
- 物理分数据页:每次只查询固定的需要显示记录的条数,每次切换页数时都到数据库中进行查询本页要显示记录的数据,本质是通过SQL语句进行控制查询的数据。
- 缺点:每次切换页面时都需要和数据库进行交互,增加服务器压力;
- 优点:当查询的数据量较大时,不会导致内存溢出。
- 逻辑分页:一次性的将查询的所有的数据加载进内存中,并根据需要进行对数据截取数据进行显示。
- 缺点:当查询的数据量较大时,容易导致内存溢出;
- 优点:与数据库交互次数较少,服务器端压力较小。
2.2 数据库对数据的物理分页的支持:
不同的数据库对数据物理分页实现的方式不同:就是SQL语句的格式的不同
- ORCAL数据库:数据分页使用的SQL语句的嵌套查询实现的。
- SQL SERVER数据库:数据分页使用的top关键字。
- MYSQL数据库:数据分页使用limit的关键字(后续案例以该数据库为准)。
2.3 MYSQL数据库实现数据的分页:
SQL语句实现分页:select * from 表名 where 条件 group by 字段 having 条件 order by 字段 ,limit 开始数据脚标,查询个数
其中的开始数据脚标是从0
开始,个数是指每次查询的记录条数。
在页面使用分页数据时:jsp页面和Servlet之间参数的传递
- jsp页面–>处理请求的Servlet:参数是当前请求的页码(currPage);
- Servlet–>jsp页面:当前页(currPage),总页数(totalPage),总记录数(totalCount),每页显示条数(pageSize),数据的List集合。
注意事项:
1. 当Servlet向jsp页面传递参数时,可以将多个参数封装到一个PageBean的javabean类中,向jsp页面传递一个封装好数据PageBean的对象即可;第一次请求分页的页面时,当前页(currPage)应该传递的是1.
2. SQL语句中的limit的参数,开始数据脚标=(currPage - 1) * pageSize
,查询个数=pageSize
.
案例代码:从数据库查询商品数据并分页显示
<!--商品查询信息分页显示部分代码 product_page.jsp-->
<body>
<h1>商城全部商品列表</h1>
<table border="1" width="1200px">
<tr>
<td>
<input type="checkbox" id="selectAll" />
</td>
<td colspan="11">
名称:<input type="text" id="pname" name="pname" /><input type="button" id="search" value="查询"/>
<input type="button" id="add" value="添加"/>
<input type="button" id="delete" value="删除"/>
</td>
</tr>
<thead>
<tr>
<th>
</th>
<th>序号</th>
<th>商品名称</th>
<th>市场价格</th>
<th>商城价格</th>
<th>商品图片</th>
<th>商品日期</th>
<th>是否热卖</th>
<th>是否下架</th>
<th>商品分类</th>
<th>操作</th>
</tr>
</thead>
<form id="deleteForm" action="${pageContext.request.contextPath}/ProductDeleteServlet" method="post">
<tbody>
<c:forEach var="p" items="${pageBean.list }" varStatus="status">
<tr>
<td>
<input type="checkbox" id="ids" name="ids" value="${p.pid }">
</td>
<td>${ status.count }</td> <!--显示当前商品的计数,就是显示中商品的序号-->
<td>${ p.pname }</td>
<td>${ p.market_price }</td>
<td>${ p.shop_price }</td>
<td>${ p.pimage }</td>
<td>${ p.pdate }</td>
<td>
<c:choose>
<c:when test="${ p.is_hot == 1 }">
是
</c:when>
<c:otherwise>
否
</c:otherwise>
</c:choose>
</td>
<td>
<c:choose>
<c:when test="${ p.pflag == 1 }">
是
</c:when>
<c:otherwise>
否
</c:otherwise>
</c:choose>
</td>
<td>${ p.cid }</td>
<td>
<a href="${pageContext.request.contextPath}/ProductDeleteServlet?pid=${p.pid}">删除</a> | <a href="${pageContext.request.contextPath}/ProductUpdateServlet?pid=${p.pid}">修改</a>
</td>
</tr>
</c:forEach>
<tr>
<td colspan="12" align="center">
第${ pageBean.currPage }/${ pageBean.totalPage }页 总共${pageBean.totalCount }条记录 每页${ pageBean.pageSize }条记录
<!--当位于第一页时,首页不能点击,否则能点击-->
<c:choose>
<c:when test="${pageBean.currPage == 1}">
<a >[首页]</a>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath }/ProductQueryPageServlet?currPage=1">[首页]</a>
</c:otherwise>
</c:choose>
<!--当位于最后一页时,下一页不能点击,否则能点击-->
<c:choose>
<c:when test="${pageBean.currPage == pageBean.totalPage }">
<a >[下一页]</a>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath }/ProductQueryPageServlet?currPage=${pageBean.currPage + 1}">[下一页]</a>
</c:otherwise>
</c:choose>
<!--当位于第一页时,上一页不能点击,否则能点击-->
<c:choose>
<c:when test="${pageBean.currPage == 1}">
<a >[上一页]</a>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath }/ProductQueryPageServlet?currPage=${pageBean.currPage - 1}">[上一页]</a>
</c:otherwise>
</c:choose>
<!--当位于最后一页时,尾页不能点击,否则能点击-->
<c:choose>
<c:when test="${pageBean.currPage == pageBean.totalPage }">
<a >[尾页]</a>
</c:when>
<c:otherwise>
<a href="${pageContext.request.contextPath }/ProductQueryPageServlet?currPage=${pageBean.totalPage}">[尾页]</a>
</c:otherwise>
</c:choose>
</td>
</tr>
</tbody>
</form>
</table>
</body>
/**
* 商品分页查询Servlet ProductQueryPageServlet.java文件
*/
public class ProductQueryPageServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 定义每页显示记录的条数,默认是5条
private String pageSize = "5";
@Override
public void init() throws ServletException {
// Servlet初始化时获取每页显示数据的条数
pageSize = this.getServletConfig().getInitParameter("pageSize");
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取当前页,第一次访问传递的是第一个
// 获取当前页,第一次访问参数值是1
String currPage = request.getParameter("currPage");
// 调用业务层进行分页业务的处理
ProductService productService = new ProductService();
try {
// 定义调用业务层进行分页处理,并返回分页数据PageBean对象
// 参数是当前请求的页码,每页显示记录的条数
PageBean pageBean = productService.queryPage(currPage,pageSize);
// 将pagebean对象存储到request中
request.setAttribute("pageBean", pageBean);
// 将页面转发到jsp显示页面
request.getRequestDispatcher("/shop/product_page.jsp").forward(request, response);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
/**
* 商品管理的业务层 ProductService.java文件
*/
public class ProductService {
/**
* 分页查询数据
* @param currPage
* @param pageSize 每页显示数据的条数
* @return
* @throws SQLException
*/
public PageBean queryPage(String currPage, String pageSize) throws SQLException {
ProductDao productDao = new ProductDao();
// 定义返回到页面的pageBean对象
PageBean pageBean = new PageBean();
// 获取当前页码
pageBean.setCurrPage(Integer.parseInt(currPage));
// 获取每页显示数据条数
Integer page_size = Integer.parseInt(pageSize);
pageBean.setPageSize(page_size);
// 获取总的记录数
int totalCount = productDao.queryCount();
pageBean.setTotalCount(totalCount);
// 获取总的页数
Double totalPage = Math.ceil((totalCount*1.0 / page_size));
Integer total_page = totalPage.intValue();
pageBean.setTotalPage(total_page);
// 获取查询的开始数据及查询数据条数
// limit参数中开始数据脚标=(currPage - 1) * page_size
int begin = (Integer.parseInt(currPage) - 1) * page_size;
// 调用dao层查询数据
List<Product> list = productDao.queryPage(begin, page_size);
pageBean.setList(list);
// 返回分页数据封装的PageBean对象
return pageBean;
}
}
/**
* 商品管理的dao ProductDao.java
*/
public class ProductDao {
/**
* 查询分页数据
* @param begin 开始的数据
* @param page_size 数据的大小
* @return 查询的数据的List集合
* @throws SQLException
*/
public List<Product> queryPage(int begin, Integer page_size)
throws SQLException {
QueryRunner queryRunner = new QueryRunner(JDBCUtils.getDataSource());
String sql = "select * from product order by pdate desc limit ?,?";
List<Product> list = queryRunner.query(sql, new BeanListHandler<>(
Product.class), begin, page_size);
return list;
}
}
/**
* 商品的实体类 Product.java
*/
public class Product {
private String pid; // 商品id
private String pname; // 商品名称
private Double market_price; // 市场价格
private Double shop_price; // 商城价格
private String pimage; // 商品图片
private Date pdate; // 商品日期
private Integer is_hot; // 是否热卖,1代表热卖
private String pdesc; // 商品描述
private Integer pflag; // 商品是否下架,0代表未下架,1代表已下架
private String cid; // 商品分类id
...属性的get/set方法
}
/**
* 分页的javabean类 用于封装传递到前台的分页数据 PageBean.java
*/
public class PageBean {
private Integer currPage; // 当前页码
private Integer totalPage; // 总共页数
private Integer totalCount; // 总的记录数
private Integer pageSize; // 每页显示的记录数
private List<Product> list; // 每页的商品数据
...属性的get/set方法
}