JavaWeb开发知识总结(表单重复提交,数据分页)

本文总结了JavaWeb开发中的表单重复提交问题及其解决方案,介绍了令牌机制来避免重复提交。同时详细讲解了数据的分页显示,包括物理分页的概念、优缺点,并以MySQL数据库为例,展示了使用LIMIT关键字实现分页的SQL语句。
摘要由CSDN通过智能技术生成

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="提交"/>&nbsp;&nbsp;&nbsp;&nbsp;<input type="reset" value="重置"/>&nbsp;&nbsp;&nbsp;&nbsp;<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 数据分页功能实现的方式: 一般使用物理分页

  1. 物理分数据页:每次只查询固定的需要显示记录的条数,每次切换页数时都到数据库中进行查询本页要显示记录的数据,本质是通过SQL语句进行控制查询的数据。
    • 缺点:每次切换页面时都需要和数据库进行交互,增加服务器压力;
    • 优点:当查询的数据量较大时,不会导致内存溢出。
  2. 逻辑分页:一次性的将查询的所有的数据加载进内存中,并根据需要进行对数据截取数据进行显示。
    • 缺点:当查询的数据量较大时,容易导致内存溢出;
    • 优点:与数据库交互次数较少,服务器端压力较小。

2.2 数据库对数据的物理分页的支持:

不同的数据库对数据物理分页实现的方式不同:就是SQL语句的格式的不同

  1. ORCAL数据库:数据分页使用的SQL语句的嵌套查询实现的。
  2. SQL SERVER数据库:数据分页使用的top关键字。
  3. MYSQL数据库:数据分页使用limit的关键字(后续案例以该数据库为准)。

2.3 MYSQL数据库实现数据的分页:

SQL语句实现分页:select * from 表名 where 条件 group by 字段 having 条件 order by 字段 ,limit 开始数据脚标,查询个数 其中的开始数据脚标是从0开始,个数是指每次查询的记录条数。

在页面使用分页数据时:jsp页面和Servlet之间参数的传递

  1. jsp页面–>处理请求的Servlet:参数是当前请求的页码(currPage);
  2. 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="查询"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <input type="button" id="add" value="添加"/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <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 }页&nbsp;&nbsp;&nbsp;&nbsp;总共${pageBean.totalCount }条记录&nbsp;&nbsp;&nbsp;&nbsp;每页${ pageBean.pageSize }条记录&nbsp;&nbsp;&nbsp;&nbsp;
                <!--当位于第一页时,首页不能点击,否则能点击-->
                <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方法
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值