JavaWeb之高階分頁查詢
分頁的原理:
**假分頁/邏輯分頁/記憶體 分頁: 一次性把所有資料全部查詢出來存放到記憶體中,每次翻頁都從記憶體中去擷取指定數量的資料.
優點:每次翻頁比較快,簡單; 缺點:若資料過大,可能造成記憶體溢位問題.
真分頁/物理分頁/資料庫分頁: 每次翻頁的時候都從資料庫中取出指定數量的資料(MySQL: LIMIT ?,?語句).
優點:若資料過大,不會造成記憶體溢位問題; 缺點:複雜,每次翻頁較慢.**
所以一般我們需要進行分頁,肯定是每次翻頁的時候去資料庫中取出指定數量資料再按照每頁的資料數量進行分頁.LIMIT?,?語句
1):查詢符合條件的結果集.
SELECT * FROM 表名 [WHERE 條件1 AND 條件2] [ORDER BY .....] LIMIT ?,?;
第一個?:當前頁從哪一個索引開始擷取資料:start/begin/beginIndex(從0開始).
beginIndex = (currentPage - 1) * pageSize;
第二個?:擷取多少條資料:pageSize;
2):查詢符合條件的結果總數.
SELECT COUNT(*) FROM 表名 [WHERE 條件1 AND 條件2];
高階分頁查詢的本質就是多條件篩選
而對其操作的實質就是拼SQL的查詢條件.
SELECT * FROM 表名 WHERE 條件1 AND 條件2 ….
以下不多說直接附上程式碼,進行一個高階分頁查詢
和商品的CRUD操作的綜合Demo.
domian層
package com._jerry._domain;
import java.math.BigDecimal;
import lombok.Data;
@Data
public class Product {
private String productName;
private Long id;
private BigDecimal salePrice;
private BigDecimal costPrice;
private Double cutoff;
private String supplier;
private String brand;
//封裝分類物件
private ProductDir productDir;
}
package com._jerry._domain;
import lombok.Data;
@Data
public class ProductDir {
private Long id;
private String dirName;
private Long parent_id;
}
DAO層
package com._jerry._Interface;
import com._jerry._domain.Product;
import com._jerry._result.PageResult;
public interface IproductDAO {
// List<Product> query(IQuery qo);
/**
* 物件集合查詢結果
* @return
*/
PageResult pageQuery(IQuery qo, String tableName);
/**
* 定義CRUD的操作 除了查詢操作意外
*/
Product get(Long id);
void delete(Long id);
int update(Product newProduct);
void save(Product product);
}
package com._jerry._Interface;
import java.util.List;
import com._jerry._domain.ProductDir;
public interface IProductDirDAO {
List<ProductDir> dirs();
}
package com._jerry._implement;
import java.util.List;
import com._jerry._Interface.IQuery;
import com._jerry._Interface.IResultSetHandler;
import com._jerry._Interface.IproductDAO;
import com._jerry._domain.Product;
import com._jerry._jdbcTemplate.JdbcTemplate;
import com._jerry._result.PageResult;
import com._jerry._result.ResultSetHandler;
import com._jerry._util.QueryUtil;
@SuppressWarnings("all")
public class ProductDAOImpl implements IproductDAO {
private IResultSetHandler<List<Product>> rsh = new ResultSetHandler();
/*public List<Product> query(IQuery qo) {
String sql = "SELECT * FROM product"+qo.getQuery();
return JdbcTemplate.query(sql, rsh,qo.getParameters().toArray());
}*/
//高階查詢和分頁查詢綜合方法
public PageResult pageQuery(IQuery qo, String tableName) {
PageResult pageResult = QueryUtil.doQuery(qo, tableName, rsh);
return pageResult;
}
//crud操作
public void delete(Long id) {
String sql = "DELETE FROM product WHERE id = ?";
JdbcTemplate.upDate(sql, id);
}
public int update(Product newProduct) {
String sql = "UPDATE product SET productName=?,dir_id=?,salePrice=?," +
"supplier=?,brand=?,cutoff=?,costPrice=? WHERE id = ?";
Object[] params = { newProduct.getProductName(),
newProduct.getProductDir().getId(), newProduct.getSalePrice(),
newProduct.getSupplier(), newProduct.getBrand(),
newProduct.getCutoff(), newProduct.getCostPrice(),
newProduct.getId() };
int num = JdbcTemplate.upDate(sql, params);
return num;
}
public void save(Product obj) {
String sql = "INSERT INTO product (productName,dir_id,salePrice,supplier,brand,"
+ "cutoff,costPrice) VALUES(?,?,?,?,?,?,?)";
Object[] params = { obj.getProductName(), obj.getProductDir().getId(),
obj.getSalePrice(), obj.getSupplier(), obj.getBrand(),
obj.getCutoff(), obj.getCostPrice() };
JdbcTemplate.upDate(sql, params);
}
public Product get(Long id) {
//用來做更新操作的時候 取出指定id的物件
String sql = "SELECT * FROM product WHERE id = ?";
List<Product> product = JdbcTemplate.query(sql, rsh, id);
return product.size() == 1 ? product.get(0):null;
}
}
package com._jerry._implement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com._jerry._Interface.IProductDirDAO;
import com._jerry._Interface.IResultSetHandler;
import com._jerry._domain.ProductDir;
import com._jerry._jdbcTemplate.JdbcTemplate;
public class ProductDirDAOImpl implements IProductDirDAO{
private IResultSetHandler<List<ProductDir>> rsh = new ProductDirResultHandler();
public ProductDir getDir(Long id){
String sql = "SELECT * FROM productdir WHERE id = ?";
List<ProductDir> list = JdbcTemplate.query(sql, rsh, id);
if (list != null) {
return list.get(0);
}
return null;
}
public List<ProductDir> dirs() {
String sql = "SELECT * FROM productdir";
List<ProductDir> list = JdbcTemplate.query(sql, new ProductDirResultHandler());
return list;
}
class ProductDirResultHandler implements IResultSetHandler<List<ProductDir>>{
public List<ProductDir> handler(ResultSet rs) throws SQLException {
List<ProductDir> list = new ArrayList<>();
while (rs.next()) {
ProductDir productDir = new ProductDir();
list.add(productDir);
productDir.setId(rs.getLong("id"));
productDir.setDirName(rs.getString("dirName"));
productDir.setParent_id(rs.getLong("parent_id"));
}
return list;
}
}
}
service層
package com._jerry._servlet;
import java.io.IOException;
import java.math.BigDecimal;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com._jerry._Interface.IProductDirDAO;
import com._jerry._Interface.IproductDAO;
import com._jerry._domain.Product;
import com._jerry._domain.ProductDir;
import com._jerry._implement.ProductDAOImpl;
import com._jerry._implement.ProductDirDAOImpl;
import com._jerry._implement.ProductQueryObject;
import com._jerry._result.PageResult;
import com._jerry._util.StringUtil;
@WebServlet("/query")
@SuppressWarnings("all")
public class QueryServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private IproductDAO dao;
private IProductDirDAO dirDAO;
public void init() throws ServletException {
dao = new ProductDAOImpl();
dirDAO = new ProductDirDAOImpl();
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
//做請求判斷是crud的哪一個 進行請求分發操作
String cmd = req.getParameter("cmd");
if ("save".equals(cmd)) {
saveOrUpdate(req, resp);
} else if ("delete".equals(cmd)) {
delete(req, resp);
} else if ("edit".equals(cmd)) {
edit(req, resp);
} else {
query(req, resp);
}
}
protected void query(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
ProductQueryObject qo = new ProductQueryObject();
request2Object(req, qo);
//接受請求封裝成物件
req.setAttribute("qo", qo);//做資料回顯
req.setAttribute("dirs", dirDAO.dirs().toArray());
PageResult pageResult = dao.pageQuery(qo, "product");
req.setAttribute("pageResult", pageResult);
req.getRequestDispatcher("/WEB-INF/views/page.jsp").forward(req, resp);
}
protected void delete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String id = req.getParameter("id");
dao.delete(Long.valueOf(id));
resp.sendRedirect("/query?cmd=query");
}
protected void saveOrUpdate(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Product p = new Product();
this.request2CrudObject(req, p);
if (p.getId() != null) {
dao.update(p);
} else {
dao.save(p);
}
resp.sendRedirect("/query");
}
protected void edit(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String id = req.getParameter("id");
if (StringUtil.hasLength(id)) {
Product oldProduct = dao.get(Long.valueOf(id));
req.setAttribute("oldProduct", oldProduct);
}
req.setAttribute("edit_dirs", dirDAO.dirs());
req.getRequestDispatcher("/WEB-INF/views/edit.jsp").forward(req, resp);
}
private void request2Object(HttpServletRequest req, ProductQueryObject qo) {
String keyword = req.getParameter("keyword");
String name = req.getParameter("name");
String dirId = req.getParameter("dirId");
String minPrice = req.getParameter("minPrice");
String maxPrice = req.getParameter("maxPrice");
String currentPage = req.getParameter("currentPage");
String pageSize = req.getParameter("pageSize");
if (StringUtil.hasLength(keyword)) {
qo.setKeyword(keyword);
}
if (StringUtil.hasLength(name)) {
qo.setName(name);
}
if (StringUtil.hasLength(dirId) && Integer.valueOf(dirId) != -1) {
qo.setDir_id(Long.valueOf(dirId));
}
if (StringUtil.hasLength(minPrice)) {
qo.setMinPrice(new BigDecimal(minPrice));
}
if (StringUtil.hasLength(maxPrice)) {
qo.setMaxPrice(new BigDecimal(maxPrice));
}
if (StringUtil.hasLength(currentPage)) {
qo.setCurrentPage(Integer.valueOf(currentPage));
}
if (StringUtil.hasLength(pageSize)) {
qo.setPageSize(Integer.valueOf(pageSize));
}
}
private void request2CrudObject(HttpServletRequest req, Product p) {
//做crud請求的物件封裝操作
String id = req.getParameter("id");
String productName = req.getParameter("productName");
String salePrice = req.getParameter("salePrice");
String costPrice = req.getParameter("costPrice");
String dir_id = req.getParameter("dir_id");
String brand = req.getParameter("brand");
String cutoff = req.getParameter("cutoff");
String supplier = req.getParameter("supplier");
if (StringUtil.hasLength(dir_id)) {
ProductDir dir = new ProductDir();
dir.setId(Long.valueOf(dir_id));
p.setProductDir(dir);
}
if (StringUtil.hasLength(id)) {
p.setId(Long.valueOf(id));
}
p.setProductName(productName);
p.setSalePrice(new BigDecimal(salePrice));
p.setCostPrice(new BigDecimal(costPrice));
p.setBrand(brand);
p.setCutoff(Double.valueOf(cutoff));
p.setSupplier(supplier);
}
}
然後我們所建立的PageResult結果集物件的處理程式碼
package com._jerry._result;
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
@Getter
@SuppressWarnings("all")
public class PageResult {//商品結果集查詢物件
private List listData;
private Integer totalCount;
private Integer currentPage = 1;//設定預設值
private Integer pageSize = 5;
private Integer beginPage = 1;//首頁
private Integer prevPage;//上一頁
private Integer nextPage;//下一頁
private Integer totalPage;//總頁數
private List<Integer> pageSizeItems = Arrays.asList(3,5,10);
/**
* @param listData
* @param totalCount
* @param currentPage
* @param pageSize
*/
public PageResult(List listData, Integer totalCount, Integer currentPage,
Integer pageSize) {
this.listData = listData;
this.totalCount = totalCount;
this.currentPage = currentPage;
this.pageSize = pageSize;
this.prevPage = this.currentPage - 1 >= 1 ? this.currentPage - 1: this.beginPage;
this.totalPage = this.totalCount % this.pageSize == 0 ? this.totalCount / this.pageSize :
this.totalCount / this.pageSize + 1;
this.nextPage = this.currentPage + 1 <= this.totalPage ? this.currentPage + 1 :
this.totalPage;
}
}
抽取的公用查詢IQuery的工具類
package com._jerry._util;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com._jerry._Interface.IQuery;
import com._jerry._Interface.IResultSetHandler;
import com._jerry._domain.Product;
import com._jerry._jdbcTemplate.JdbcTemplate;
import com._jerry._result.PageResult;
public class QueryUtil {
//通用高階查詢工具類
/**
* 分頁查詢操作
* @param qo SQL拼接語句
* @param tableName 表名
* @param rsh 處理結果集
* @return 分頁結果集物件
*/
@SuppressWarnings("all")
public static PageResult doQuery(IQuery qo,String tableName,IResultSetHandler rsh){
//結果集查詢
String baseSql = "SELECT * FROM "+tableName+qo.getQuery()+" LIMIT ?,? ";
List<Object> newParams = new ArrayList<>(qo.getParameters());
newParams.add((qo.getCurrentPage() - 1)* qo.getPageSize() );//offset指資料開始索引的位置
newParams.add(qo.getPageSize());
List<Product> listData = JdbcTemplate.query(baseSql, rsh, newParams.toArray());
//結果總數查詢
String countSql = "SELECT COUNT(*) FROM "+tableName+qo.getQuery();
Integer totalCount = JdbcTemplate.query(countSql, new IResultSetHandler<Long>() {
public Long handler(ResultSet rs) throws SQLException {
if (rs.next()) {
return rs.getLong(1);
}
return 0L;
}
}, qo.getParameters().toArray()).intValue();
return new PageResult(listData, totalCount, qo.getCurrentPage(), qo.getPageSize());
}
}
package com._jerry._Interface;
import java.util.List;
public interface IQuery {
/**
* 用來獲取存放參數的集合
* @return
*/
List<Object> getParameters();
/**
* 高階查詢的當前頁
* @return
*/
Integer getCurrentPage();
/**
* @return 獲取每頁多少條資料
*/
Integer getPageSize();
/**
* 獲取拼接的sql語句
* @return
*/
String getQuery();
}
封裝的QueryObject查詢物件共有方法
package com._jerry._implement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com._jerry._Interface.IQuery;
public class QueryObject implements IQuery{
//用來儲存設定引數
private List<Object> parameters = new ArrayList<>();
//用來儲存sql語句的條件語句
private List<String> conditions = new ArrayList<>();
public String getQuery() {
parameters.clear();
conditions.clear();
StringBuilder sql = new StringBuilder(200);
this.customizeQuery();
for (int i = 0; i < conditions.size(); i++) {
if (i == 0) {
sql.append(" WHERE ");
}else {
sql.append(" AND ");
}
sql.append(conditions.get(i));
}
return sql.toString();
}
public List<Object> getParameters() {
return this.parameters;
}
protected void addQuery(String condition,Object...params) {
this.conditions.add("("+condition+")");
this.parameters.addAll(Arrays.asList(params));
}
//暴露一個方法給子類新增自己的查詢方法
protected void customizeQuery() {
}
//暴露一個方法給子類判斷字串是否為空
protected boolean hasLength(String str) {
return str != null && !"".equals(str.trim());
}
public Integer getCurrentPage() {
return null;
}
public Integer getPageSize() {
return null;
}
}
當前商品的查詢物件和sql語句拼接條件
package com._jerry._implement;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.Setter;
@Getter@Setter
public class ProductQueryObject extends QueryObject{
private String name;
private BigDecimal minPrice;
private BigDecimal maxPrice;
private Long dir_id;
private String keyword;
//封裝分頁查詢物件中使用者傳入的二個數據
private Integer currentPage = 1;
private Integer pageSize = 5;//設定預設值
protected void customizeQuery() {
if (hasLength(name)) {
//語句前面的空格不能忘記
addQuery(" productName LIKE ?", "%"+name+"%");
}
if (minPrice != null) {
addQuery(" salePrice >= ?", minPrice);
}
if (maxPrice != null) {
addQuery(" salePrice <= ?", maxPrice);
}
if (dir_id != null) {
addQuery(" dir_id = ?", dir_id);
}
if (hasLength(keyword)) {
addQuery(" productName LIKE ? OR brand LIKE ?", "%"+keyword+"%","%"+keyword+"%");
}
}
}
結果集處理器
package com._jerry._Interface;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface IResultSetHandler<T> {
T handler(ResultSet rs) throws SQLException;
}
package com._jerry._result;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com._jerry._Interface.IResultSetHandler;
import com._jerry._domain.Product;
import com._jerry._domain.ProductDir;
import com._jerry._implement.ProductDirDAOImpl;
public class ResultSetHandler implements IResultSetHandler<List<Product>> {
//定義一個Map集合來做分類物件的Cache
private Map<Long,ProductDir> cache = new HashMap<>();
private ProductDirDAOImpl dirs = new ProductDirDAOImpl();
public List<Product> handler(ResultSet rs) throws SQLException {
List<Product> list = new ArrayList<>();
while (rs.next()) {
Product product = new Product();
list.add(product);
product.setProductName(rs.getString("productName"));
product.setBrand(rs.getString("brand"));
product.setCostPrice(rs.getBigDecimal("costPrice"));
product.setCutoff(rs.getDouble("cutoff"));
product.setSalePrice(rs.getBigDecimal("salePrice"));
product.setId(rs.getLong("id"));
product.setSupplier(rs.getString("supplier"));
Long dir_id = rs.getLong("dir_id");
if (dir_id != null && dir_id != 0) {
ProductDir dir = cache.get(dir_id);
if (dir == null) {
dir = dirs.getDir(dir_id);
//快取設計
product.setProductDir(dir);
cache.put(dir_id, dir);
}
product.setProductDir(dir);
}
}
return list;
}
}
以上的程式碼就不附帶JdbcTemplate工具類程式碼了
綜合上述程式碼的演示,在分頁結果集PageResult類中有二個欄位值是會設值預設值,而且最終決定他們值的是操作使用者,使用者需要傳入兩條資料(使用者設定的):
currentPage = 1;
pageSize = 10;//原因在於所有的查詢物件都可以擁有分頁操作
所以我們在servlet中也要接收這二條引數,將其封裝在查詢物件中一起傳入查詢物件中.最終我們需要在後端來實現計算的有三個欄位值
總頁數/末頁(totalPage):
totalPage = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1;
上一頁(prevPage):
prevPage = currentPage - 1 >= 1 ? currentPage - 1 : 1;
下一頁(nextPage):
nextPage = currentPage + 1 <= totalPage ? currentPage + 1 : totalPage;
以上還有一個問題:
在翻頁的時候,丟失了高階查詢的資料:
為什麼會丟失:
1):之前我們把高階查詢的資料,共享在請求中.
2):但是,翻頁的(上一頁/下一頁)都是超連結,每次點選都會重新發送一個新的請求.
既然是新的請求,所以不能享有上一個請求中的資料.
分析問題:
高階查詢表單中封裝了所有的查詢資訊.
翻頁的超連結,攜帶有當前頁資訊.
解決方案:
把翻頁的資訊儲存到高階查詢表單中.
使用JavaScript來做提交高階查詢表單.
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>商品列表</title>
<script type="text/javascript">
//使用JavaScript方法來解決高階查詢的資料丟失問題
function goPage(pageNum){
//把翻頁的程式碼存入高階查詢表單中
document.getElementById("currentPage").value = pageNum;
document.getElementById("pageSize").value = document.getElementById("ps").value;
//提交表單 因為表單一個只有一個所以獲取第0個
document.forms[0].submit();
}
</script>
</head>
<body>
<h3 align="center">貨品資訊表</h3>
<form action="/query" method="post">
<input type="hidden" name="currentPage" id="currentPage" value="1"/>
<input type="hidden" name="pageSize" id="pageSize" value="3"/>
<div align="center">
商品名稱:<input type="search" name="name" value="${qo.name}" /> 價格區間:<input
type="number" name="minPrice" value="${qo.minPrice}" />- <input
type="number" name="maxPrice" value="${qo.maxPrice}" /> <select
name="dirId">
<option value="-1">請選擇分類</option>
<c:forEach items="${dirs}" var="dir">
<c:if test="${dir.id != 1}">
<option value="${dir.id}" ${dir.id == qo.dir_id?"selected":""}>${dir.dirName}</option>
</c:if>
</c:forEach>
</select> <input type="text" name="keyword" value="${qo.keyword}" placeholder="商品名稱/商品品牌"
style="width: 150px" /> <input type="submit" value="查詢"
style="background-color: orange;">
<a href="/query?cmd=edit">新增商品資訊</a>
</div>
</form>
<table border="1" width="100%" cellpadding="0" cellspacing="0">
<tr style="background-color: orange;" align="center">
<th>貨品編號</th>
<th>貨品名稱</th>
<th>貨品品牌</th>
<th>貨品分類</th>
<th>供 應 商</th>
<th>零 售 價</th>
<th>成 本 價</th>
<th>折 扣</th>
<th>操 作</th>
</tr>
<c:forEach items="${pageResult.listData}" var="p" varStatus="times">
<tr style='background-color:${times.count % 2 == 0 ? "gray" : ""};'>
<td>${p.id}</td>
<td>${p.productName}</td>
<td>${p.brand}</td>
<td>${p.productDir.dirName}</td>
<td>${p.supplier}</td>
<td>${p.salePrice}</td>
<td>${p.costPrice}</td>
<td>${p.cutoff}</td>
<td>
<a href="/query?cmd=edit&id=${p.id}">編輯</a>
<a href="/query?cmd=delete&id=${p.id}">刪除</a>
</td>
</tr>
</c:forEach>
<tr>
<td colspan="9" align="right">
<a href="javascript:goPage(${pageResult.beginPage})">首頁</a>
<a href="javascript:goPage(${pageResult.prevPage})">上一頁</a>
<a href="javascript:goPage(${pageResult.nextPage})">下一頁</a>
<a href="javascript:goPage(${pageResult.totalPage})">末頁</a>
總頁數:${pageResult.totalPage},當前第${pageResult.currentPage}/${pageResult.totalPage}頁 ,
一共${pageResult.totalCount}條資料,
每頁顯示
<select id="ps" onchange="goPage(1)">
<c:forEach items="${pageResult.pageSizeItems}" var="ps">
<option ${pageResult.pageSize == ps ? "selected":""}>${ps}</option>
</c:forEach>
</select>條資料
</td>
</tr>
</table>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>商品編輯頁面</title>
</head>
<body>
<form action="/query?cmd=save" method="post">
<input type="hidden" name="id" value