名称 | 价格 | 作者 | 销量 | 库存 | 图片路径 | 操作 | |
${book.title} | ${book.price} | ${book.author} | ${book.sales} | ${book.stock} | ${book.imgPath} | 修改 | 删除 |
添加图书 |
这个demo包括用户登录、注册、注销和分页,主要介绍分页,所以下面主要展示关于分页的说明
分页(两种):
第一种:简单查询分页
第二种:带价格的查询分页
下方有源码链接,需要的可以下载
关于数的表格结构:
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT, /* 编号*/
`title` varchar(100) NOT NULL,/*书名 */
`author` varchar(100) NOT NULL,/*作者 */
`price` double(11,2) NOT NULL,/* 价格*/
`sales` int(11) NOT NULL,/* 销售量*/
`stock` int(11) NOT NULL,/* 库存量*/
`img_path` varchar(100) NOT NULL,/* 图片路径*/
PRIMARY KEY (`id`)/* 主键*/
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8;
package com.xxj.bean;
public class Book {
private Integer id;
private String title;
private String author;
private Double price;
private Integer sales;
private Integer stock;
private String imgPath ="static/img/default.jpg"; //这个地方给了一个默认的图片路径
public Book(Integer id, String title, String author, Double price, Integer sales, Integer stock, String imgPath) {
super();
this.id = id;
this.title = title;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
this.imgPath = imgPath;
}
public Book(Integer id, String title, String author, Double price, Integer sales, Integer stock) {
super();
this.id = id;
this.title = title;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
}
public Book() {
super();
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
public Integer getSales() {
return sales;
}
public void setSales(Integer sales) {
this.sales = sales;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public String getImgPath() {
return imgPath;
}
public void setImgPath(String imgPath) {
this.imgPath = imgPath;
}
@Override
public String toString() {
return "Book [id=" + id + ", title=" + title + ", author=" + author + ", price=" + price + ", sales=" + sales
+ ", stock=" + stock + ", imgPath=" + imgPath + "]";
}
}
package com.xxj.bean;
import java.util.List;
public class Page<T> {
private List<T> list; //从数据库中查询当前页Book的集合
//设置为public便于后面操作
public static final int PAGE_SIZE = 4; //每页显示多少条记录 设置的固定值
private int pageNo; //当前页 用户输入的页码
private int totalPageNo; //总页数 计算出来的
private int totalRecord; //从数据库中查询的总记录数
public int getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
}
public void setTotalPageNo(int totalPageNo) {
this.totalPageNo = totalPageNo;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public int getPageNo() {
if (pageNo < 1) {
// 如果当前页码小于1,直接返回1
return 1;
} else if (pageNo > getTotalPageNo()) {
// 如果当前页码大于总页数,返回总页数
return getTotalPageNo();
} else {
return pageNo;
}
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public static int getPageSize() {
return PAGE_SIZE;
}
// 总页数是由总记录数和每页显示的条数计算得到
public int getTotalPageNo() {
if (totalRecord % PAGE_SIZE == 0) {
return totalRecord / PAGE_SIZE;
} else {
return totalRecord / PAGE_SIZE + 1;
}
}
@Override
public String toString() {
return "Page [pageNo=" + pageNo + ", totalPageNo=" + totalPageNo + ", totalRecord=" + totalRecord + "]";
}
}
其中使用到了ThreadLocal,介绍如下:
JDK 1.2的版本中就提供java.lang.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap
,其key就是一个ThreadLocal,而Object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。 1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
package com.xxj.utils;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class JDBCTools {
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal;
static{
try {
//是一个Map,是Hashtable的子类,它的key和value都是String类型。
Properties pro = new Properties();
//如果是加载bin目录下的资源文件(因为src或config目录下的配置文件,最后编译后在bin目录下,和.class在一起),所以用类加载器去加载
pro.load(JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(pro);
threadLocal = new ThreadLocal<Connection>();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection(){
try {
Connection conn = threadLocal.get();
if(conn == null){
conn = dataSource.getConnection();
threadLocal.set(conn);
}
return conn;
} catch (SQLException e) {
/*
* SQLException编译时,
* RuntimeException:运行时异常,不强制在编译期间编写throws或try...catch
*/
throw new RuntimeException(e);
}
}
public static void free(){
try {
Connection conn = threadLocal.get();
if(conn!=null){
threadLocal.remove();
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
配置文件:
url=jdbc:mysql://localhost:3306/数据库名
username=用户名
password=密码
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=50
minIdle=10
maxWait=1000
下面使用到了Apache的DButils中的相关技术,介绍如下:
commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。
其中QueryRunner类封装了SQL的执行,是线程安全的。
(1)可以实现增、删、改、查、批处理、
(2)考虑了事务处理需要共用Connection。
(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。
(1)更新
public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
…
(2)插入
public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值
…
(3)批处理
public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句
public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:只支持INSERT语句
…
(4)使用QueryRunner类实现查询 重点
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。
…
ResultSetHandler接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet rs)该方法的返回值将作为QueryRunner类的query()方法的返回值。
该接口有如下实现类可以使用:
package com.xxj.dao.basic;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com.xxj.utils.JDBCTools;
public abstract class BasicDAO<T> {
//clazz代表BasicDAO的子类的泛型实参类型
//例如:class BookDAOImpl extends BasicDAO,clazz代表的类型
private Class clazz;
//QueryRunner是Apache给封装的类
private QueryRunner queryRunner = new QueryRunner();
/*
* 子类继承父类时,不会继承父类的构造器。
* 但是创建子类对象时,一定调用父类的构造器,默认调用父类的无参构造。
*/
protected BasicDAO(){
//当创建子类对象时,以下代码会被执行
//(1)获取BasicDAO的子类的Class对象
//例如:class BookDAOImpl extends BasicDAO,clazz代表的类型
//subType代表BookDAOImpl的类型
Class<? extends BasicDAO> subType = this.getClass();
//(2)获取泛型父类的类型
//例如:class BookDAOImpl extends BasicDAO,clazz代表的类型
//获取BasicDAO的类型
Type superType = subType.getGenericSuperclass();
//BasicDAO属于ParameterizedType类型
//(3)强转为ParameterizedType类型
ParameterizedType pt = (ParameterizedType) superType;
//(4)获取实际类型参数
//例如:class BookDAOImpl extends BasicDAO,clazz代表的类型
//获取BasicDAO的类型中类型
Type[] actualTypeArguments = pt.getActualTypeArguments();
//(5)因为这里只有一个,所以我取数组的[0]元素为clazz赋值
clazz = (Class) actualTypeArguments[0];
}
protected List<T> getBeanList(String sql, Object... args){
try {
return queryRunner.query(JDBCTools.getConnection(), sql, new BeanListHandler<T>(clazz), args);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//例如:查询总记录数,查询最高工资,查询平均工资等
@SuppressWarnings("unchecked")
protected Object getSingleValue(String sql, Object... args){
try {
return queryRunner.query(JDBCTools.getConnection(), sql, new ScalarHandler(), args);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
package com.xxj.dao;
import java.util.List;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
public interface BookDAO {
/**
* 用于查询符合条件的所有的Book对象的数量
* @param args 可以传入最低价格和最高价格
* @return 一共有多少符合规则的记录
*/
Object getValue(Object... args); //用于查找符合条件的总记录数
/**
* 获取带分页的所有图书信息
* @param page 传入的page对象是一个带页码的page对象
* @return 返回的page对象是包含所有属性的page对象
*/
public Page<Book> getBooksByPage(Page<Book> page);
/**
* 根据价格范围获取带分页的图书信息
* @param page 携带有页码的page
* @param minPrice 最小值
* @param maxPrice 最大值
* @return
*/
public Page<Book> getPageBooksByPrice(Page<Book> page, double minPrice, double maxPrice);
}
package com.xxj.dao.impl;
import java.util.List;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
import com.xxj.dao.BookDAO;
import com.xxj.dao.basic.BasicDAO;
public class BookDAOImpl extends BasicDAO<Book> implements BookDAO {
/*
* 根据价钱获取数据库中符合条件的数据总数
*/
public Object getValue(Object... args) {
String sql = "select count(*) from books where price between ? and ?";
return getSingleValue(sql, args[0],args[1]);
}
/*
* 查询指定页码的Book信息
* @param page 一个只有当前页码属性的Page对象(作用是:将PageNo封装为Page对象)
* @return 返回一个List属性为【当前页码的所有的Book对象】的Page对象
*/
@Override
public Page<Book> getBooksByPage(Page<Book> page) {
//这里注意一个小细节:当我们查询的时候,如果表的字段名和bean中的不一致需要取别名
String sql = "SELECT id, title, author, price,`sales`,`stock`,img_path imgPath FROM books LIMIT ?, ?";
// 这里将最低价格赋值为0,最高价格为Integer的最大值。
//这样就能查询出所有的Book对象
//这里需要注意的是如果不转化为Long,则会报类型转换异常且必须强转为Long
long totalNum = (Long)getValue(0,Integer.MAX_VALUE);
// 设置数据库中一共有多少条记录
page.setTotalRecord((int)totalNum);
int totalPageNo = (int) (totalNum / Page.PAGE_SIZE );
page.setTotalPageNo(totalPageNo);
// 查询出当前页码中的所有的Book对象
List<Book> bookList = getBeanList(sql,(page.getPageNo()-1)*Page.PAGE_SIZE,Page.PAGE_SIZE);
// 赋值给page对象的List属性
page.setList(bookList);
//返回page对象
return page;
}
@Override
public Page<Book> getPageBooksByPrice(Page<Book> page, double minPrice, double maxPrice) {
String sql = "SELECT id, title, author, price,sales,stock,img_path imgPath FROM books WHERE price BETWEEN ? AND ? LIMIT ?, ?";
// 获取数据库中的记录数
long totalRecord = (Long) getValue(minPrice, maxPrice);
// 给一共有多少记录属性赋值
page.setTotalRecord((int)totalRecord);
// 得到LIMIT的第一个参数
int a = (page.getPageNo() - 1) * Page.PAGE_SIZE;
//防止当page.getPageNo()为0时,a是一个负值,造成SQL异常
if(a < 0) {
a = 0;
}
List<Book> books = getBeanList(sql, minPrice, maxPrice, a, Page.PAGE_SIZE);
// 将查询出结果赋予Page对象
page.setList(books);
return page;
}
}
package com.xxj.service;
import java.util.List;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
public interface BookService {
//分页查找图书的方法
public Page<Book> getBooksByPage(String pageNo);
//通过价钱分页查赵图书
public Page<Book> getPageBooksByPrice(String pageNo, String minPrice, String maxPrice);
}
package com.xxj.service.impl;
import java.util.List;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
import com.xxj.dao.BookDAO;
import com.xxj.dao.impl.BookDAOImpl;
import com.xxj.service.BookService;
public class BookServiceImpl implements BookService {
private BookDAO bookDAO = new BookDAOImpl();
@Override
public Page<Book> getBooksByPage(String pageNo) {
//当用户什么都不输入,默认为1,为首页
int currentPageNo = 1;
//接收当前页并转为整型。当用户输入的不合法。如:abs,捕获处理异常
try {
currentPageNo = Integer.parseInt(pageNo);
}catch (NumberFormatException e) {
}
// 创建一个Page对象
Page<Book> page = new Page<Book>();
// 只给当前页码属性赋值
page.setPageNo(currentPageNo);
// 返回一个包含了所有的属性值的Page对象
return bookDAO.getBooksByPage(page);
}
@Override
public Page<Book> getPageBooksByPrice(String pageNoStr, String minPrice, String maxPrice) {
// 默认页码
int pageNo = 1;
// 默认最低价格
Double min = 0.0;
// 默认最高价格
Double max = Double.MAX_VALUE;
//以防出现异常,捕获到异常也不打印,让它们失效,使用默认值
try {
pageNo = Integer.parseInt(pageNoStr);
min = Double.parseDouble(minPrice);
max = Double.parseDouble(maxPrice);
} catch (NullPointerException e1) {
} catch (NumberFormatException e2) {
}
// 如果最低价格大于最高价格,交换,以防用户最大值和最小值填写错误位置
if (min > max) {
min += max;
max = min - max;
min = min - max;
}
Page<Book> page = new Page<Book>();
// 赋予当前页码值
page.setPageNo(pageNo);
return bookDAO.getPageBooksByPrice(page, min, max);
}
}
其就是一个普通的继承自HttpServlet的Servlet,doPost调用doGet。在doGet中,获取方法名的请求参数,通过反射来调用继承自BaseServlet的子Servlet的相应方法
因为每个servlet都要执行doPost,doGet方法处理相关请求,我们就使用动态反射简化代码。之后我们只用指定相关的方法处理我们的请求即可。
package com.xxj.servlet;
import java.io.IOException;
import java.lang.reflect.Method;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class BaseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码字符集
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("text/html;charset=UTF-8");
//获取方法名
String method = request.getParameter("method");
try {
//获取方法的对象
Method methodObj = this.getClass().getDeclaredMethod(method, HttpServletRequest.class,HttpServletResponse.class);
//通过方法的对象调用具体的方法
methodObj.invoke(this, request,response);
} catch (Exception e) {
e.printStackTrace();
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
package com.xxj.servlet;
import java.io.IOException;
import java.util.List;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
import com.xxj.service.BookService;
import com.xxj.service.impl.BookServiceImpl;
/**
*这个是用来处理普通查询
*/
public class BookManagerServlet extends BaseServlet{
private static final long serialVersionUID = 1L;
private BookService bookService = new BookServiceImpl();
public void getPageBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//获取当前页的值
String pageNo = request.getParameter("pageNo");
//拿到当前页的所有对象
Page<Book> pageBook = bookService.getBooksByPage(pageNo);
//放到request域中
request.setAttribute("pageBook", pageBook);
//进行转发到显示页面
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request, response);
}
}
package com.xxj.servlet;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.xxj.bean.Book;
import com.xxj.bean.Page;
import com.xxj.service.BookService;
import com.xxj.service.impl.BookServiceImpl;
/**
*这个是用来通过价格查询进行分页
*/
public class BookClientServlet extends BaseServlet{
private static final long serialVersionUID = 1L;
private BookService bookService = new BookServiceImpl();
//通过价钱查询页面的book对象
public void getClientBooksByPageAndPrice(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
//设置字符集
request.setCharacterEncoding("UTF-8");
//取值
String pageNoStr = request.getParameter("pageNo");
String minPrice = request.getParameter("min");
String maxPrice = request.getParameter("max");
//调用service层的方法获取page对象
Page<Book> page = bookService.getPageBooksByPrice(pageNoStr, minPrice, maxPrice);
//加入域中
request.setAttribute("page", page);
//转发
request.getRequestDispatcher("/pages/client/book_client.jsp").forward(request, response);
}
}
里面调用JSP的动作标签,转发到BookClientServlet中,并携带方法的请求参数
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
base.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%-- --%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
header.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
welcome.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
① 需要有首页,前一页,当前页 - 1,当前页,当前页 + 1,下一页,末页。这些都是a标签
② 需要提示用户一共有多少条记录,一共有几页。并提供输入框,用于输入要跳转的页码。并提供一个跳转的按钮分析:
⒈ 首页的href可以直接写方法的请求参数
⒉ 前一页 和 当前页 - 1,需要判断当前页码是否为1:如果当前页码为1,则需要将这两个a标签变为文字显示;否则就显示a标签
⒊ 同理:下一页 和 当前页 + 1,的显示和隐藏替换和上面类似
⒋ 末页的href的pageNo请求参数就是一共有多少页码
⒌ 一共有多少条记录,和一共有多少页。都可以通过page对象来获取
⒍ 提供一个输入框,添加一个id,方便获取其value值
⒎ 提供一个按钮,添加一个id,方便添加点击事件
⒏ 按钮的点击事件:首先获取输入跳转页码的输入框value属性值,然后调用window.location.href = 要跳转的页面,来实现要跳转的页面.可以直接转化为location=要跳转的页面,来实现要跳转的页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
图书管理
<%@ include file="/WEB-INF/include/base.jsp" %>
图书管理系统
<%@ include file="/WEB-INF/include/header.jsp" %>
名称
价格
作者
销量
库存
图片路径
操作
${book.title}
${book.price}
${book.author}
${book.sales}
${book.stock}
${book.imgPath}
修改
删除
添加图书
书城.Copyright ©2019
最终实现的页码导航栏的效果是:指定一次显示5个页码,尽量让当前页码在中间位置
分析:
totalPageNo<5
begin:1
end:totalPageNo
totalPageNo>=5
pageNo<=3
begin:1
end:5
【1】2345
1【2】345
12【3】45
pageNo>3&&pageNo<=totalPageNo-2
begin:pageNo-2
end:pageNo+2
23【4】56
34【5】67
45【6】78
pageNo>totalPageNo-2
begin:totalPageNo-4
end:totalPageNo
456【7】8
4567【8】
实现上述的集中情况要使用到jstl的标签库:
代码:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>
书城首页
<%@include file="/WEB-INF/include/base.jsp" %>
<%-- <%=request.getScheme()%>://<%=request.getServerName()%>:<%=request.getServerPort()%><%=request.getContextPath() %> --%>
网上书城
<%@include file="/WEB-INF/include/welcome.jsp" %>
价格: 元 -
元
书名:
${book.title }
价格:
¥${book.price }
销量:
${book.sales }
库存:
${book.stock }
书城.Copyright ©2019
链接:https://pan.baidu.com/s/1H0hPhXm5pPC0CRzvziEhew 提取码:3twb