文章目录
- 一、JavaEE 项目的三层架构
- 二、项目——阶段二(已经把静态页面做好)
- 三、项目——阶段三
- 四、项目——阶段四:使用 EL 表达式修改表单回显
- 五、项目——阶段五:内容介绍
- 六、项目——阶段五:分页
- 七、项目——阶段六:购物车
- 八、项目——阶段七:订单
- 九、项目——阶段八:拦截请求Filter
一、JavaEE 项目的三层架构
二、项目——阶段二(已经把静态页面做好)
1、阶段二要求用户注册和登陆的实现
需求 1:用户注册
需求如下:
1)访问注册页面
2)填写注册信息,提交给服务器
3)服务器应该保存用户
4)当用户已经存在----提示用户注册 失败,用户名已存在
5)当用户不存在-----注册成功
需求 2:用户登陆
需求如下:
1)访问登陆页面
2)填写用户名密码后提交
3)服务器判断用户是否存在
4)如果登陆失败 —>>>>返回用户名或者密码错误信息
5)如果登录成功 —>>>> 返回登陆成功 信息
2、搭建环境
3、创建数据库
DROP DATABASE IF EXISTS book;
CREATE DATABASE book;
USE book;
CREATE TABLE book_user(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(100) NOT NULL UNIQUE,
`password` VARCHAR(32) NOT NULL,
`email` VARCHAR(200)
);
INSERT INTO book_user(`username`,`password`,`email`)
VALUE ('cen111','123321123','cencen@qq.com');
SELECT * FROM book_user;
4、编写JavaBean对象
public class User {
private Integer id;
private String username;
private String password;
private String email;
public User() {
}
public User(Integer id, String username, String password, String email) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
'}';
}
}
5、编写工具类JDBCUtils
用来管理数据库
(1)导入jar包(数据库和连接池需要)
(2)在 src 源码目录下编写 jdbc.properties 属性配置文件
username=root
password=20020809l
url=jdbc:mysql://localhost:3306/book
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
# 连接池最大连接数量
(3)编写 JdbcUtils 工具类
public class JDBCUtils {
private static DruidDataSource dataSource;
static {
try {
Properties properties = new Properties();
//读取 jdbc.properties属性配置文件
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//从流中加载数据
properties.load(inputStream);
//创建数据库连接池
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取数据库连接池中的连接
//如果返回null就是获取连接失败,有值才成功
public static Connection getconnection(){
Connection conn = null;
try {
conn = dataSource.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return conn;
}
//关闭连接,放回数据库连接池
public static void close(Connection conn){
if (conn!=null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
(4)JdbcUtils 测试
public class JdbcUtilsTest {
@Test
public void testJdbcUtils(){
for (int i=0;i<30;i++){
Connection connection = JDBCUtils.getconnection();
System.out.println(JDBCUtils.getconnection());
//数据库只有那么大,每次用完记得释放
JDBCUtils.close(connection);
}
}
}
6、编写BaseDao
(1)编写BaseDao
public class BaseDao {
//使用DbUtils操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行:Insert\Update\Delete语句
*
* @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
*/
public int updata(String sql,Object...args){
Connection connection = JDBCUtils.getconnection();
try {
return queryRunner.update(connection,sql,args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(connection);
}
return -1;
}
/**
* @Description 查询返回一个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanHandler<T>(type),args)
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(con);
}
return null;
}
/**
* @Description 查询返回多个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(con);
}
return null;
}
/**
* 执行返回一行一列的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql,Object...args){
Connection conn = JDBCUtils.getconnection();
try {
return queryRunner.query(conn,sql,new ScalarHandler(),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(conn);
}
return null;
}
}
7、编写 UserDao 和测试
UserDao 接口:
public class BaseDao {
//使用DbUtils操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行:Insert\Update\Delete语句
*
* @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
*/
public int updata(String sql,Object...args){
Connection connection = JDBCUtils.getconnection();
try {
return queryRunner.update(connection,sql,args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(connection);
}
return -1;
}
/**
* @Description 查询返回一个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(con);
}
return null;
}
/**
* @Description 查询返回多个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(con);
}
return null;
}
/**
* 执行返回一行一列的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql,Object...args){
Connection conn = JDBCUtils.getconnection();
try {
return queryRunner.query(conn,sql,new ScalarHandler(),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JDBCUtils.close(conn);
}
return null;
}
}
UserDaoImpl 实现类:
public class UserDaoImpl extends BaseDao implements UserDao {
@Override
public User queryUserByUsername(String username) {
String sql = "select `id`,`username`,`password`,`email` from book_user where username = ?";
return queryForOne(User.class,sql,username);
}
@Override
public User queryUserByUsernameAndPassword(String username, String password) {
String sql = "select `id`,`username`,`password`,`email` from book_user where username = ? and password = ?";
return queryForOne(User.class, sql, username,password);
}
@Override
public int saveUser(User user) {
String sql = "insert into book_user(`username`,`password`,`email`) values(?,?,?)";
return updata(sql, user.getUsername(),user.getPassword(),user.getEmail());
}
}
UserDao 测试:
public class UserDaoTest {
UserDao userDao = new UserDaoImpl();
@Test
public void queryUserByUsername() {
if(userDao.queryUserByUsername("asdcen111")==null){
System.out.println("用户名可用");
}else{
System.out.println("用户名已存在");
}
}
@Test
public void queryUserByUsernameAndPassword() {
if (userDao.queryUserByUsernameAndPassword("cen111","123321123") == null){
System.out.println("用户名或密码错误,登陆失败");
}else{
System.out.println("登录成功");
}
}
@Test
public void saveUser() {
System.out.println(userDao.saveUser(new User(null,"www", "12341234", "123421@qq.com")));
}
}
8、编写 UserService 和测试
UserService 接口:
public interface UserService {
/**
* 注册用户
* @Param user
*/
public void registUser(User user);
/**
* 登录
* @Param user
* @return 如果返回null,说明登录失败,返回有值,是登录成功
*/
public User login(User user);
/**
* 检查 用户名是否可用
* @Param username
* @return 返回true表示用户已存在,返回false表示用户名可用
* @date 2022/1/16 22:18
* @auther cencen
*/
public boolean existsUsername(String username);
}
UserServiceImpl 实现类:
public class UseServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void registUser(User user) {
userDao.saveUser(user);
}
@Override
public User login(User user) {
return userDao.queryUserByUsernameAndPassword(user.getUsername(),user.getPassword());
}
@Override
public boolean existsUsername(String username) {
if (userDao.queryUserByUsername(username) == null){
//等于null,说明没看到,没查到表示可用
return false;
}
return true;
}
}
UserService 测试:
public class UserServiceTest {
UserService userService = new UseServiceImpl();
@Test
public void registUser() {
userService.registUser(new User(null,"weq","waerwef","1234124@123.com"));
}
@Test
public void login() {
System.out.println(userService.login(new User(null,"werg","43252",null)));
System.out.println(userService.login(new User(null,"cen111","123321123",null)));
}
@Test
public void existsUsername() {
if (userService.existsUsername("fdsae")){
System.out.println("用户名已存在");
}else{
System.out.println("用户名可用");
}
}
}
9、编写web层
(1)用户注册流程
(2)修改 regist.html 和 regist_success.html 页面
(3)编写 RegistServlet 程序
public class RegistServlet extends HttpServlet {
private UserService userService = new UseServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2、检查 验证码是否正确 === 写死,要求验证码为:abcde
if ("abcdef".equalsIgnoreCase(code)){
//正确
// 3、检查 用户名是否可用
if (userService.existsUsername(username)){
// 不可=》 跳回注册页面
System.out.println("用户名【"+username+"】已存在!");
req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);//跳回注册页面
}else {
// 可用=》 调用Sservice保存到数据库
userService.registUser(new User(null,username,password,email));
// 跳到注册成功页面 regist_success.html
req.getRequestDispatcher("/pages/user/regist_success.html").forward(req,resp);//跳回注册页面
}
}else {
//不正确
// 跳回注册页面
System.out.println("验证码错误");
req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);
}
}
}
10、用户登录功能的实现
(1)图解用户登录
(2)修改 login.html 页面和 login_success.html 页面
(3)LoginServlet 程序
public class LoginServlet extends HttpServlet {
UserService userService = new UseServiceImpl();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1、获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 调用 userService.login()登录处理业务
User loginUser = userService.login(new User(null,username,password,null));
//如果等于null,则登录失败
if (loginUser == null){
//跳回登录页面
req.getRequestDispatcher("/pages/user/login.html").forward(req,resp);
}else {
//登录成功
//跳转到成功页面login_success.html
req.getRequestDispatcher("/pages/user/login_success.html").forward(req,resp);
}
}
}
三、项目——阶段三
1、页面 jsp 动态化
1、在 html 页面顶行添加 page 指令。
<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>
2、修改文件后缀名为:.jsp
3、使用 IDEA 搜索替换.html 为.jsp(快捷键:Ctrl+Shift+R)
2、抽取页面中相同的内容
使用格式:在原语句位置替换成
<%@include file=“路径”%>
i. head 中 css、jquery、base 标签
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 动态使用base标签 -->
<%
String basePath = request.getScheme()
+ "://"
+ request.getServerName()
+ ":"
+ request.getServerPort()
+ request.getContextPath()
+ "/";
%>
<!--写base标签,永远固定相对路径跳转的结果-->
<base href="<%=basePath%>">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>
ii. 每个页面的页脚
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div id="bottom">
<span>
尚硅谷书城.Copyright ©2015
</span>
</div>
iii. 登录成功后的菜单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
<span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span>
<a href="../order/order.jsp">我的订单</a>
<a href="../../index.jsp">注销</a>
<a href="../../index.jsp">返回</a>
</div>
iv. manager 模块的菜单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
<a href="book_manager.jsp">图书管理</a>
<a href="order_manager.jsp">订单管理</a>
<a href="../../index.jsp">返回商城</a>
</div>
3、登录,注册错误提示,及表单回显
以登录回显为示例:
Servlet 程序端需要添加回显信息到 Request 域中
jsp 页面,需要输出回显信息
4、BaseServlet 的抽取(优化)
在实际的项目开发中,一个模块,一般只使用一个 Servlet 程序。
代码优化一:合并 LoginServlet 和 RegistServlet 程序为 UserServlet 程序
public class UserServlet extends HttpServlet {
private UserService userService = new UserServiceImpl();
/**
* 处理登录的功能
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 1、获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
// 调用 userService.login()登录处理业务
User loginUser = userService.login(new User(null,username, password, null));
// 如果等于 null,说明登录 失败!
if (loginUser == null) {
// 把错误信息,和回显的表单项信息,保存到 Request 域中
req.setAttribute("msg","用户或密码错误!");
req.setAttribute("username", username);
// 跳回登录页面
req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
} else {
// 登录 成功
//跳到成功页面 login_success.html
req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
}
}
/**
* 处理注册的功能
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 1、获取请求的参数
String username = req.getParameter("username");
String password = req.getParameter("password");
String email = req.getParameter("email");
String code = req.getParameter("code");
// 2、检查 验证码是否正确 === 写死,要求验证码为:abcde
if ("abcde".equalsIgnoreCase(code)) {
// 3、检查 用户名是否可用
if (userService.existsUsername(username)) {
System.out.println("用户名[" + username + "]已存在!");
// 把回显信息,保存到 Request 域中
req.setAttribute("msg", "用户名已存在!!");
req.setAttribute("username", username);
req.setAttribute("email", email);
// 跳回注册页面
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
} else {
// 可用
// 调用 Sservice 保存到数据库
userService.registUser(new User(null, username, password, email));
// 跳到注册成功页面 regist_success.jsp
req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
}
} else {
// 把回显信息,保存到 Request 域中
req.setAttribute("msg", "验证码错误!!");
req.setAttribute("username", username);
req.setAttribute("email", email);
System.out.println("验证码[" + code + "]错误");
req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
}
}
protected void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException {
String action = req.getParameter("action");
if ("login".equals(action)) {
login(req, resp);
} else if ("regist".equals(action)) {
regist(req, resp);
}
}
}
优化代码二:使用反射优化大量 else if 代码:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
String action = req.getParameter("action");
try {
// 获取 action 业务鉴别字符串,获取相应的业务 方法反射对象
Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
// System.out.println(method);
// 调用目标业务 方法
method.invoke(this, req, resp);
} catch (Exception e) {
e.printStackTrace();
}
}
代码优化三:抽取 BaseServlet 程序。
BaseServlet 程序代码:
public abstract class BaseServlet extends HttpServlet {
protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
try {
//获取action业务鉴别字符串,获取相应的业务 方法反射对象
Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
//System.out.println(method);
//调用目标业务、方法
method.invoke(this,req,resp);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
5、数据的封装和抽取 BeanUtils 的使用
BeanUtils 工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中。
BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操作。
可以省去获取完属性之后还要创建新对象 “new 对象”,然后赋值 "对象.setXxx”
BeanUtils 它不是 Jdk 的类。而是第三方的工具类。所以需要导包。
1、导入需要的 jar 包:
commons-beanutils-1.8.0.jar
commons-logging-1.1.1.jar
2、编写 WebUtils 工具类使用:
public class WebUtils {
//把第一个参数从HttpServlet的变成Map目的在于,Map适用于dao层、servlet层、web层,但HttpServlet只适用于web层
public static <T> T copyParamToBean(Map value, T bean){
try {
System.out.println("注入之前"+bean);
//把所有请求的参数都注入到user对象中
BeanUtils.populate(bean,value);
System.out.println("注入之后:"+bean);
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
3、在UserServlet方法中
四、项目——阶段四:使用 EL 表达式修改表单回显
以登录为示例:
五、项目——阶段五:内容介绍
1、MVC 概念
MVC 全称:Model 模型、 View 视图、 Controller 控制器。
MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 Web 层的代码如何有效分离,单独工作。
jsp负责展示 数据处理应该交给servlet
View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。
转到某个页面。或者是重定向到某个页面。
Model 模型:将与业务逻辑相关的数据封装为具体的 JavaBean 类,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo。
MVC 是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用 (目的还是为了降低耦合度)。
2、图书模块
(1)编写图书模块的数据库表
create table book_books(
`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 book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'java 从入门到放弃' , '国哥' , 80 , 9999 , 9 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '数据结构与算法' , '严敏君' , 78.5 , 6 , 13 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '怎样拐跑别人的媳妇' , '龙伍' , 68, 99999 , 52 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '木虚肉盖饭' , '小胖' , 16, 1000 , 50 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'C++编程思想' , '刚哥' , 45.5 , 14 , 95 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '蛋炒饭' , '周星星' , 9.9, 12 , 53 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '赌神' , '龙伍' , 66.5, 125 , 535 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'Java 编程思想' , '阳哥' , 99.5 , 47 , 36 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'JavaScript 从入门到精通' , '婷姐' , 9.9 , 85 , 95 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'cocos2d-x 游戏编程入门' , '国哥' , 49, 52 , 62 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'C 语言程序设计' , '谭浩强' , 28 , 52 , 74 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'Lua 语言程序设计' , '雷丰阳' , 51.5 , 48 , 82 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '西游记' , '罗贯中' , 12, 19 , 9999 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '水浒传' , '华仔' , 33.05 , 22 , 88 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '操作系统原理' , '刘优' , 133.05 , 122 , 188 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '数据结构 java 版' , '封大神' , 173.15 , 21 , 81 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'UNIX 高级环境编程' , '乐天' , 99.15 , 210 , 810 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'javaScript 高级编程' , '国哥' , 69.15 , 210 , 810 , 'static/img/default.jpg');
INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '大话设计模式' , '国哥' , 89.15 , 20 , 10 , 'static/img/default.jpg');
INSERT INTO book_books(`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 book_books;
(2)编写图书模块的 JavaBean
public class Book {
private Integer id;
private String name;
private String author;
private BigDecimal price;
private Integer sales;
private Integer stock;
private String img_path = "static/img/default.jpg";
public Book() {
}
public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String img_path) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
//要求给定的图书封面图书路径不能为空
if (img_path != null && "".equals(img_path)){
this.img_path = img_path;
}
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal 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 getImg_path() {
return img_path;
}
public void setImg_path(String img_path) {
if (img_path != null && "".equals(img_path)){
this.img_path = img_path;
}
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", sales=" + sales +
", stock=" + stock +
", img_path='" + img_path + '\'' +
'}';
}
}
(3)编写图书模块的 Dao 和测试 Dao
Dao 接口
public interface BookDao {
//添加书
public int addBook(Book book);
//根据id删除书
public int deleteBookById(Integer id);
//修改
public int updateBook(Book book);
//查询一
public Book queryBookById(Integer id);
//查询二
public List<Book> queryBooks();
}
BookDaoImpl 实现类:
public class BookDaoImpl extends BaseDao implements BookDao {
@Override
public int addBook(Book book) {
String sql = "insert into book_books(`name` , `author` , `price` , `sales` , `stock` , `img_path`) values(?,?,?,?,?,?)";
return update(sql, book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImg_path());
}
@Override
public int deleteBookById(Integer id) {
String sql = "delete from book_books where id = ?";
return update(sql,id);
}
@Override
public int updateBook(Book book) {
String sql = "update book_books set `name`=?, `author`=? , `price`=? , `sales`=? , `stock`=? , `img_path`=? where id=?";
return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImg_path(),book.getId());
}
@Override
public Book queryBookById(Integer id) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from book_books where id = ?";
return queryForOne(Book.class,sql,id);
}
@Override
public List<Book> queryBooks() {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from book_books";
return queryForList(Book.class,sql);
}
}
BookDao 的测试
public class BookDaoTest {
private BookDao bookDao = new BookDaoImpl();
@Test
public void addBook() {
//bookDao.addBook(new Book(null,"侠盗猎车手","191125",new BigDecimal(9999),1100000,0,null));
bookDao.addBook(new Book(null,"国哥为什么这么帅!", "191125", new BigDecimal(9999),1100000,0,null
));
}
@Test
public void deleteBookById() {
bookDao.deleteBookById(21);
}
@Test
public void updateBook() {
bookDao.updateBook(new Book(21,"大家都可以这么帅!", "国哥", new BigDecimal(9999),1100000,0,null
));
}
@Test
public void queryBookById() {
System.out.println( bookDao.queryBookById(21) );
}
@Test
public void queryBooks() {
for (Book queryBook : bookDao.queryBooks()) {
System.out.println(queryBook);
}
}
}
注意url加上 ?useUnicode=true&characterEncoding=UTF-8
否则插入数据库会乱码
(4)编写图书模块的 Service 和测试 Service
BookService 接口
public interface BookService {
public void addBook(Book book);
public void deleteBookById(Integer id);
public void updateBook(Book book);
public Book queryBookById(Integer id);
public List<Book> queryBooks();
}
BookServiceImpl 实现类:
public class BookServiceImpl implements BookService {
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();
}
}
BookService 的测试:
public class BookServiceTest {
private BookService bookService = new BookServiceImpl();
@Test
public void addBook() {
bookService.addBook(new Book(null,"国哥在手,天下我有!", "1125", new BigDecimal(1000000),100000000, 0, null));
}
@Test
public void deleteBookById() {
bookService.deleteBookById(22);
}
@Test
public void updateBook() {
bookService.updateBook(new Book(22,"社会我国哥,人狠话不多!", "1125", new BigDecimal(999999),10, 111110, null));
}
@Test
public void queryBookById() {
System.out.println(bookService.queryBookById(22));
}
@Test
public void queryBooks() {
for (Book queryBook : bookService.queryBooks()) {
System.out.println(queryBook);
}
}
}
(5)编写图书模块的 Web 层,和页面联调测试
1.5.1、图书列表功能的实现
1、图解列表功能流程:
jsp负责展示 数据处理应该交给servlet
2、BookServlet 程序中添加 list 方法
protected void list(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
//2.把全部图书保存到Request域中
req.setAttribute("books",books);
//3.请求转发到/pages/manager/book_manager.jsp页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
}
3、修改【图书管理】请求地址
在manager_menu.jsp中
4、修改 pages/manager/book_manager.jsp 页面的数据遍历输出
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
<!-- 静态包含base标签,css样式,jQuery文件 -->
<%@include file="/pages/common/head.jsp"%></head>
<body>
<div id="header">
<img class="logo_img" alt="" src="../../static/img/logo.gif" >
<span class="wel_word">图书管理系统</span>
<!-- 静态包含manager管理模块的菜单 -->
<%@include file="/pages/common/manager_menu.jsp"%>
</div>
<div id="main">
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
//改这里!!!!!!!!!!!!!!!
<c:forEach items="${requestScope.books}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
<td><a href="book_edit.jsp">修改</a></td>
<td><a href="#">删除</a></td>
</tr>
</c:forEach>
//---------------------------------------------
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td><a href="pages/manager/book_edit.jsp">添加图书</a></td> </tr>
</tr>
</table>
</div>
<%@include file="/pages/common/footer.jsp"%>
</body>
</html>
1.5.2、前后台的简单介绍
1.5.3、添加图书功能的实现
1.5.3.1、添加图书流程细节:
1.5.3.2、问题说明:表单重复提交:
当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会发起浏览器记录的最后一次
请求。
1.5.3.3、BookServlet 程序中添加 add 方法
protected void add(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取请求的参数==封装成Book对象
Book book = WebUtils.copyParamToBean(req.getParameterMap(),new Book());
//2.调用BookService.addBook()保存图书
bookService.addBook(book);
//3.跳到图书列表页面 /manager/bookServlet?action=list
resp.sendRedirect(req.getContextPath()+"/manager/bookServlet?action=list");
}
1.5.3.4、修改 book_edit.jsp 页面
1.5.4、删除图书功能的实现
1.5.4.1、图解删除流程:
1.5.4.2、BookServlet 程序中的 delete 方法:
protected void delete(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 1、获取请求的参数 id,图书编程
int id = WebUtils.parseInt(req.getParameter("id"), 0);
// 2、调用 bookService.deleteBookById();删除图书
bookService.deleteBookById(id);
// 3、重定向回图书列表管理页面
// /book/manager/bookServlet?action=list
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
}
1.5.4.3、给 WebUtils 工具类添加转换 int 类型的工具方法
/**
* 将字符串转换成为 int 类型的数据
* @param strInt
* @param defaultValue
* @return
*/
public static int parseInt(String strInt,int defaultValue) {
try {
return Integer.parseInt(strInt);
} catch (Exception e) {
e.printStackTrace();
}
return defaultValue;
}
1.5.4.4、修改删除的连接地址:
1.5.4.5、给删除添加确认提示操作:
在book_manager.jsp中
<script type="text/javascript">
$(function () {
$("a.deleteClass").click(function () {
// 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
/**
* confirm是确认提示框函数
* 参数是它的提示内容
* 它有两个按钮,一个确认,一个是取消。
* 返回true表示点击了,确认,返回false表示点击取消。
*
* td:first =>表单第一列元素的意思
*/
return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() + "】?");
// return false// 阻止元素的默认行为===不提交请求
})
})
</script>
1.5.5、修改图书功能的实现
1.5.5.1:图解修改图书细节:
1.5.5.2、更新【修改】的请求地址:
1.5.5.3、BookServlet 程序中添加 getBook 方法:
protected void getBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//1.获取请求的参数图书编号
int id = WebUtils.parseInt(req.getParameter("id"),0);
//2.调用bookServlet。queryBookById查询图书
Book book = bookService.queryBookById(id);
//3.保存图书到Request域中
req.setAttribute("book",book);
//4.请求转发到 pages/manager/book_edit.jsp页面
req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req,resp);
}
1.5.5.4、在 book_edit.jsp 页面中显示修改的数据
<div id="main">
<form action="manager/bookServlet" method="get">
<input type="hidden" name="action" value="${ empty param.id ? "add" : "update" }" />
<input type="hidden" name="id" value="${ requestScope.book.id }" />
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<tr>
<td><input name="name" type="text" value="${requestScope.book.name}"/></td>
<td><input name="price" type="text" value="${requestScope.book.price}"/></td>
<td><input name="author" type="text" value="${requestScope.book.author}"/></td>
<td><input name="sales" type="text" value="${requestScope.book.sales}"/></td>
<td><input name="stock" type="text" value="${requestScope.book.stock}"/></td>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>
</div>
1.5.5.5、在 BookServlet 程序中添加 update 方法
protected void update(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
// 1、获取请求的参数==封装成为 Book 对象
Book book = WebUtils.copyParamToBean(req.getParameterMap(),new Book());
// 2、调用 BookService.updateBook( book );修改图书
bookService.updateBook(book);
// 3、重定向回图书列表管理页面
// 地址:/工程名/manager/bookServlet?action=list
resp.sendRedirect(req.getContextPath() + "/manager/bookServlet?action=list");
}
1.5.5.6、解决 book_edit.jsp 页面,即要实现添加,又要实现修改操作。
六、项目——阶段五:分页
1、图书分页
(1)分页模块的分析
(2)分页模型 Page 的抽取(当前页数,总页数,总记录数,当前页数据,每页记录数)
/**
* Page 是分页的模型对象
* @param <T> 是具体的模块的 javaBean 类
*/
public class Page<T> {
public static final Integer PAGE_SIZE = 4;
// 当前页码
private Integer pageNo;
// 总页码
private Integer pageTotal;
// 当前页显示数量
private Integer pageSize = PAGE_SIZE;
// 总记录数
private Integer pageTotalCount;
// 当前页数据
private List<T> items;
}
(3)分页的初步实现
BookDao 代码:
//求总的记录数
@Override
public Integer queryForPageTotalCount() {
String sql = "select count(*) from t_book";
//Number为所有包装类的父类
Number count = (Number) queryForSingleValue(sql);
return count.intValue();
}
@Override
public List<Book> queryForPageItems(int begin, int pageSize) {
String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPathfrom t_book limit ?,?";
return queryForList(Book.class,sql,begin,pageSize);
}
BookService 代码
@Override
public Page<Book> page(int pageNum, int pageSize) {
Page<Book> page = new Page<Book>();
// 设置当前页码
page.setPageNum(pageNum);
// 设置每页显示的数量
page.setPageSize(pageSize);
// 求总记录数
Integer pageTotalCount = bookDao.queryForPageTotalCount();
// 设置总记录数
page.setPageTotalCount(pageTotalCount);
// 求总页码
Integer pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0) {
pageTotal+=1;
}
// 设置总页码
page.setPageTotal(pageTotal);
// 求当前页数据的开始索引
int begin = (page.getPageNum() - 1) * pageSize;
// 求当前页数据
List<Book> items = bookDao.queryForPageItems(begin,pageSize);
// 设置当前页数据
page.setItems(items);
return page;
}
BookServlet 程序的代码:
/**
* 处理分页功能
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
//1 获取请求的参数 pageNo 和 pageSize
int pageNum = WebUtils.parseInt(req.getParameter("pageNum"), 1);
int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
//2 调用 BookService.page(pageNo,pageSize):Page 对象
Page<Book> page = bookService.page(pageNum,pageSize);
//3 保存 Page 对象到 Request 域中
req.setAttribute("page",page);
//4 请求转发到 pages/manager/book_manager.jsp 页面
req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
}
manager_menu.jsp 中【图书管理】请求地址的修改:
book_manager.jsp 修改:
(4)首页、上一页、下一页、末页实现
在book_manager.jsp中
<div id="page_nav">
<%-- 大于首页,才显示 --%>
<c:if test="${requestScope.page.pageNum>1}">
<a href="manager/bookServlet?action=page&pageNum=1">首页</a>
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum-1}">上一页</a>
</c:if>
<a href="#">3</a>
【${requestScope.page.pageNum}】
<a href="#">5</a>
<%-- 如果已经是最后一页则不显示下一页 --%>
<c:if test="${requestScope.page.pageNum < requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum+1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${param.pageNum}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
(5)分页模块中跳转到指定页数功能实现
<div id="page_nav">
<%-- 大于首页,才显示 --%>
<c:if test="${requestScope.page.pageNum>1}">
<a href="manager/bookServlet?action=page&pageNum=1">首页</a>
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum-1}">上一页</a>
</c:if>
<a href="#">3</a>
【${requestScope.page.pageNum}】
<a href="#">5</a>
<%-- 如果已经是最后一页则不显示下一页 --%>
<c:if test="${requestScope.page.pageNum < requestScope.page.pageTotal}">
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum+1}">下一页</a>
<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
到第<input value="${param.pageNum}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script type="text/javascript">
$(function () {
//
$("#searchPageBtn").click(function () {
var pageNum = $("#pn_input").val();
//javaScript语言中提供了一个Location地址栏对象
//它有一个属性叫href,它可以获取浏览器地址栏中的地址
//href属性可读,可写
location.href = "http://localhost:8088/Book/manager/bookServlet?action=page&pageNum="+pageNum;
//location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNum="+pageNum;
})
})
</script>
</div>
Page 对象中的修改:
public void setPageNo(Integer pageNum) {
/* 数据边界的有效检查 */
if (pageNum < 1) {
pageNum = 1;
}
if (pageNum > pageTotal) {
pageNum = pageTotal;
}
this.pageNum = pageNum;
}
BookService 中 page 方法的修改:
@Override
public Page<Book> page(int pageNum,int pageSize) {
Page<Book> page = new Page<Book>();
//设置每页显示的数量
page.setPageSize(pageSize);
//求总记录数
Integer pageTotalCount = bookDao.queryForPageTotalCount();
//设置总记录数
page.setPageTotalCount(pageTotalCount);
//求总页码
Integer pageTotal = pageTotalCount / pageSize;
if (pageTotalCount % pageSize > 0){
pageTotal+=1;
}
//设置总页码
page.setPageTotal(pageTotal);
//设置当前页码
page.setPageNum(pageNum);
//求当前页数据的开始索引
int begin =( (page.getPageNum()-1) * pageSize);
//求当前页数据
List<Book> items = bookDao.queryForPageItems(begin,pageSize);
//设置当前页数据
page.setItems(items);
return page;
}
(6)分页模块中,页码 1,2,【3】,4,5 的显示,要显示 5 个页码,并且页码可以点击跳转。
需求:显示 5 个连续的页码,而且当前页码在中间。除了当前页码之外,每个页码都可以点击跳到指定页。
情况 1:如果总页码小于等于 5 的情况,
页码的范围是:1-总页码
1 页 1
2 页 1,2
3 页 1,2,3
4 页 1,2,3,4
5 页 1,2,3,4,5
小情况 2:当前页码为最后 3 个,8,9,10
页码范围是:总页码减 4 - 总页码
6,7【8】9,10
6,7,8【9】10
6,7,8,9【10】
小情况 3:4,5,6,7
页码范围是:当前页码减 2 - 当前页码加 2
2,3,4,5,6
3,4,5,6,7
4,5,6,7,8
5,6,7,8,9
化简前
<c:choose>
<%-- 情况1:如果页码小于等于5的情况,页码范围是:1-总页码 --%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:forEach begin="1" end="${requestScope.page.pageTotal}" var="i">
<c:if test="${i == requestScope.page.pageNum}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNum}">
<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--情况 2:总页码大于 5 的情况--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况 1:当前页码为前3个1,2,3的情况,页码范围:1-5。 --%>
<c:when test="${requestScope.page.pageNum <= 3}">
<c:forEach begin="1" end="5" var="i">
<c:if test="${i == requestScope.page.pageNum}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNum}">
<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--小情况 2:当前页码为前3个8,9,10的情况,页码范围:总页码减4~总页码。 --%>
<c:when test="${requestScope.page.pageNum > requestScope.page.pageTotal-3}">
<c:forEach begin="${requestScope.page.pageTotal-4}" end="${requestScope.page.pageTotal}" var="i">
<c:if test="${i == requestScope.page.pageNum}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNum}">
<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
</c:if>
</c:forEach>
</c:when>
<%--小情况 3:页码范围:当前页码减2~当前页码加2。 --%>
<c:otherwise>
<c:forEach begin="${requestScope.page.pageNum-2}" end="${requestScope.page.pageNum+2}" var="i">
<c:if test="${i == requestScope.page.pageNum}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNum}">
<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
</c:if>
</c:forEach>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
化简后:
<%--分页条的开始--%>
<div id="page_nav">
<%--大于首页,才显示--%>
<c:if test="${requestScope.page.pageNo > 1}">
<a href="${ requestScope.page.url }&pageNo=1">首页</a>
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo-1}">上一页</a>
</c:if>
<%--页码输出的开始--%>
<c:choose>
<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
<c:when test="${ requestScope.page.pageTotal <= 5 }">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--情况2:总页码大于5的情况--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == requestScope.page.pageNo}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNo}">
<a href="${ requestScope.page.url }&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
<%--页码输出的结束--%>
<%-- 如果已经 是最后一页,则不显示下一页,末页 --%>
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${ requestScope.page.pageTotal }页,${ requestScope.page.pageTotalCount }条记录
到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script type="text/javascript">
$(function () {
// 跳到指定的页码
$("#searchPageBtn").click(function () {
var pageNo = $("#pn_input").val();
<%--var pageTotal = ${requestScope.page.pageTotal};--%>
<%--alert(pageTotal);--%>
// javaScript语言中提供了一个location地址栏对象
// 它有一个属性叫href.它可以获取浏览器地址栏中的地址
// href属性可读,可写
location.href = "${pageScope.basePath}${ requestScope.page.url }&pageNo=" + pageNo;
});
});
</script>
</div>
<%--分页条的结束--%>
(7)修改分页后,增加,删除,修改图书信息的回显页面
以修改图书为示例:
1>在修改的请求地址上追加当前页码参数:
2>在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数
3>在服务器重定向的时候,获取当前页码追加上进行跳转
2、首页 index.jsp 的跳转
3、分页条的抽取
(1)抽取分页条中请求地址为 url 变量
4.1.1.在 page 对象中添加 url 属性
4.1.2 在 Servlet 程序的 page 分页方法中设置 url 的分页请求地址
4.1.3、修改分页条中请求地址为 url 变量输出,并抽取一个单独的 jsp 页面
把manager/bookServlet?action=page换成${ requestScope.page.url }
单独将url拿出来方便改正
<%--分页条的开始--%>
<div id="page_nav">
<%--大于首页,才显示--%>
<c:if test="${requestScope.page.pageNo > 1}">
<a href="${ requestScope.page.url }&pageNo=1">首页</a>
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo-1}">上一页</a>
</c:if>
<%--页码输出的开始--%>
<c:choose>
<%--情况1:如果总页码小于等于5的情况,页码的范围是:1-总页码--%>
<c:when test="${ requestScope.page.pageTotal <= 5 }">
<c:set var="begin" value="1"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--情况2:总页码大于5的情况--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%--小情况1:当前页码为前面3个:1,2,3的情况,页码范围是:1-5.--%>
<c:when test="${requestScope.page.pageNo <= 3}">
<c:set var="begin" value="1"/>
<c:set var="end" value="5"/>
</c:when>
<%--小情况2:当前页码为最后3个,8,9,10,页码范围是:总页码减4 - 总页码--%>
<c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%--小情况3:4,5,6,7,页码范围是:当前页码减2 - 当前页码加2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == requestScope.page.pageNo}">
【${i}】
</c:if>
<c:if test="${i != requestScope.page.pageNo}">
<a href="${ requestScope.page.url }&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
<%--页码输出的结束--%>
<%-- 如果已经 是最后一页,则不显示下一页,末页 --%>
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageNo+1}">下一页</a>
<a href="${ requestScope.page.url }&pageNo=${requestScope.page.pageTotal}">末页</a>
</c:if>
共${ requestScope.page.pageTotal }页,${ requestScope.page.pageTotalCount }条记录
到第<input value="${param.pageNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<script type="text/javascript">
$(function () {
// 跳到指定的页码
$("#searchPageBtn").click(function () {
var pageNo = $("#pn_input").val();
location.href = "${pageScope.basePath}${ requestScope.page.url }&pageNo=" + pageNo;
});
});
</script>
</div>
<%--分页条的结束--%>
4、首页价格搜索
5、显示登录用户信息
login_success_menu.jsp中:
UserServlet中:
index.jsp中:
6、登出—注销用户
- 销毁Session中用户登录的信息(或者销毁Session)
- 重定向到首页或登录界面
/**
* 注销
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
protected void loginout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 1. 销毁Session中用户登录的信息(或者销毁Session)
req.getSession().invalidate();
// 2. 重定向到首页或登录界面
resp.sendRedirect(req.getContextPath());
}
再替换地址即可
7、表单重复提交 —> 验证码
表单重冥提交有三种常见的情况:
一:提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5(刷新),就会发起最后一次的请求。造成表单重复提交问題。
解决方法: 使用重定向来进行跳转
二:用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,
就会着急,然后多点了几次提交探作,也会造成表单重复提交。
三:用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。
public class RegistServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取用户名
String username = req.getParameter("username");
System.out.println("保存到数据库:"+username);
//1.防止刷新重复到数据库
//错误:req.getRequestDispatcher("/ok.jsp").forward(req,resp);
//改正:resp.sendRedirect(req.getContextPath()+"/ok.jsp");
//2.不停点击提交
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resp.sendRedirect(req.getContextPath()+"/ok.jsp");
}
}
所有解决办法:
验证码
8、谷歌Kaptcha图片验证码的使用
实验步骤:
- 导入谷歌验证码jar包------kaptcha-2.3.2.jar
- 在web.xml中去配置用于生成验证码的Servlet程序
<servlet>
<servlet-name>KaptchaServlet</servlet-name>
<servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>KaptchaServlet</servlet-name>
<url-pattern>/kaptchaServlet</url-pattern>
</servlet-mapping>
- 在表单中使用img标签去显示验证码图片并使用它
验证码刷新原理:
七、项目——阶段六:购物车
1、购物车模型
(1)购物车商品项
public class CartItem {
private Integer id;
private String name;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
public CartItem() {
}
public CartItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice) {
this.id = id;
this.name = name;
this.count = count;
this.price = price;
this.totalPrice = totalPrice;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(BigDecimal totalPrice) {
this.totalPrice = totalPrice;
}
@Override
public String toString() {
return "CartItem{" +
"id=" + id +
", name='" + name + '\'' +
", count=" + count +
", price=" + price +
", totalPrice=" + totalPrice +
'}';
}
}
(1)购物车对象
/**
* 购物车对象
* @Classname Cart
* @Description TODO
* @Date 2022/2/6 15:15
* @Created by cencen
*/
public class Cart {
// private Integer totalCount;
// private BigDecimal totalCount;
// private BigDecimal totalPrice;
//key是商品编号
//value是商品信息
private Map<Integer,CartItem> items = new LinkedHashMap<Integer,CartItem>();
/**
* 添加商品项
* @Param cartItem
*/
public void addItem(CartItem cartItem){
//先查看购物车中是否已经添加过此商品,如已添加,则数量累加,总金额更新,如果没有添加,则直接放到集合中
CartItem item = items.get(cartItem.getId());
if (item == null){
//之前没有添加过此商品
items.put(cartItem.getId(),cartItem);
}else {
//已经添加过的情况
item.setCount(item.getCount()+1);//数量累加
item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));//更新总金额
}
}
/**
* 删除商品项
*/
public void deleteItem(Integer id){
items.remove(id);
}
/**
* 清空购物车
*/
public void clear(){
items.clear();
}
/**
* 修改商品数量
*/
public void updateCount(Integer id,Integer count){
CartItem cartItem = items.get(id);
if (cartItem != null){
cartItem.setCount(count);//修改商品数量
cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));//更新总金额
}
}
//获取该商品项总数量
public Integer getTotalCount() {
Integer totalCount = 0;
for (Map.Entry<Integer,CartItem>entry:items.entrySet()){
totalCount += entry.getValue().getCount();
}
return totalCount;
}
//获取该商品项总价格
public BigDecimal getTotalPrice() {
BigDecimal totalPrice = new BigDecimal(0);
for (Map.Entry<Integer,CartItem>entry:items.entrySet()){
totalPrice = totalPrice.add(entry.getValue().getTotalPrice());
}
return totalPrice;
}
public Map<Integer,CartItem> getItems() {
return items;
}
public void setItems(Map<Integer,CartItem> items) {
this.items = items;
}
@Override
public String toString() {
return "Cart{" +
"totalCount=" + getTotalCount() +
", totalPrice=" + getTotalPrice() +
", items=" + items +
'}';
}
}
2、购物车功能的实现
(1)购物车的展示
(2)添加商品到购物车功能
在CartServlet中
/**
* 加入购物车
*/
@Override
protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的参数 商品编号
int id = WebUtils.parseInt(req.getParameter("id"),0);
//调用bookServlet.qurryBookById(id):Book得到图书的信息
Book book = bookService.queryBookById(id);
//把图书信息,转换成CartItem商品项
CartItem cartItem = new CartItem(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());
//调用Cart.addItem(CartItem);添加商品项
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart == null){
cart = new Cart();
req.getSession().setAttribute("cart",cart);
}
cart.addItem(cartItem);
//重定向回原来商品所在的地址页面,因为在http中有一个请求头Referer,它可以把请求发起时,浏览器地址栏中的地址发给服务器
resp.sendRedirect(req.getHeader("Referer"));
}
index.jsp 页面 js 的代码:
(3)清空购物车功能
CartServlet 程序
/**
* 清空购物车
*/
protected void clearcart(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取购物车对象
Cart cart = (Cart) req.getSession().getAttribute("cart");
//清空购物车
cart.clear();
//重定向回购物车展示页面
resp.sendRedirect(req.getHeader("Referer"));
}
cart.jsp 页面的内容
给清空购物车添加请求地址,和添加 id 属性:
清空的确认提示操作
(4)删除购物车商品数量功能
CartServlet 程序:
/**
* 删除商品
*/
protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取商品编号
int id = WebUtils.parseInt(req.getParameter("id"), 0);
//获取购物车对象
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart != null) {
//删除购物车商品项
cart.deleteItem(id);
//重定向回原来界面
resp.sendRedirect(req.getHeader("Referer"));
}
}
购物车/pages/cart/cart.jsp 页面的代码:
删除的请求地址
删除的确认提示操作:
(5)修改购物车商品数量
CartServlet 程序
/**
* 修改商品数量
*/
protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求的参数,商品编号,商品数量
int id = WebUtils.parseInt(req.getParameter("id"),0);
int count = WebUtils.parseInt(req.getParameter("count"),0);
//获取Cart购物车对象
Cart cart = (Cart) req.getSession().getAttribute("cart");
if (cart != null){
//修改商品数量
cart.updateCount(id,count);
}
//重定向回购物车展示页面
resp.sendRedirect(req.getHeader("Referer"));
}
修改 pages/cart/cart.jsp 购物车页面:
修改商品数量 js 代码:
(6)把内容显示到首页,购物车数据回显
在添加商品到购物车的时候,保存最后一个添加的商品名称:
在 pages/client/index.jsp 页面中输出购物车信息:
八、项目——阶段七:订单
1、订单模块的实现
(1)创建订单模块的数据库表
use book;
create table t_order(
`order_id` varchar(50) primary key,
`create_time` datetime,
`price` decimal(11,2),
`status` int,
`user_id` int,
foreign key(`user_id`) references book_user(`id`)
);
create table t_order_item(
`id` int primary key auto_increment,
`name` varchar(100),
`count` int,
`price` decimal(11,2),
`total_price` decimal(11,2),
`order_id` varchar(50),
foreign key(`order_id`) references t_order(`order_id`)
);
(2)创建订单模块的数据模型
/**
* 订单
*/
public class Order {
private String orderId;
private Date createTime;
private BigDecimal price;
// 0 未发货,1 已发货,2 表示已签收
private Integer status = 0;
private Integer userId;
/**
* 订单项
*/
public class OrderItem {
private Integer id;
private String name;
private Integer count;
private BigDecimal price;
private BigDecimal totalPrice;
private String orderId;
(3)编写订单模块的 Dao 程序和测试
OrderDao 接口
public interface OrderDao {
public int saveOrder(Order order);
}
OrderDao 实现
public class OrderDaoImpl extends BaseDao implements OrderDao {
@Override
public int saveOrder(Order order) {
String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`) values(?,?,?,?,?)";
return update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),order.getStatus(),order.getUserId());
}
}
OrderItemDao 接口
public interface OrderItemDao {
public int saveOrderItem(OrderItem orderItem);
}
OrderItemDao 实现
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
@Override
public int saveOrderItem(OrderItem orderItem) {
String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`) values(?,?,?,?,?)";
return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(),orderItem.getOrderId());
}
}
测试
public class OrderDaoTest {
@Test
public void saveOrder() {
OrderDao orderDao = new OrderDaoImpl();
orderDao.saveOrder(new Order("1234567891",new Date(),new BigDecimal(100),0, 1));
}
}
public class OrderItemDaoTest {
@Test
public void saveOrderItem() {
OrderItemDao orderItemDao = new OrderItemDaoImpl();
orderItemDao.saveOrderItem(new OrderItem(null,"java 从入门到精通", 1,newBigDecimal(100),newBigDecimal(100),"1234567890"));
orderItemDao.saveOrderItem(new OrderItem(null,"javaScript 从入门到精通", 2,newBigDecimal(100),newBigDecimal(200),"1234567890"));
orderItemDao.saveOrderItem(new OrderItem(null,"Netty 入门", 1,new BigDecimal(100),newBigDecimal(100),"1234567890"));
}
}
(4)编写订单模块的 Service 和测试
OrderService 接口
public interface OrderService {
public String createOrder(Cart cart,Integer userId);
}
OrderService 实现类
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
@Override
public String createOrder(Cart cart, Integer userId) {
//订单号===唯一性
String orderId = System.currentTimeMillis()+""+userId;
//创建一个订单对象
Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);
//保存订单
orderDao.saveOrder(order);
//遍历购物车中每一个商品项转换成为订单项保存到数据库
for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
//获取每一个购物车中的商品项
CartItem cartItem = entry.getValue();
//转换为每一个订单项
OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
//保存每一个订单项到数据库
orderItemDao.saveOrderItem(orderItem);
}
//清空购物车
cart.clear();
return orderId;
}
}
测试
public class OrderServiceTest {
@Test
public void createOrder() {
Cart cart = new Cart();
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));
OrderService orderService = new OrderServiceImpl();
System.out.println( "订单号是:" + orderService.createOrder(cart, 1) );
}
}
(5)编写订单模块的 web 层和页面联调
修改 OrderService 程序:
public class OrderServiceImpl implements OrderService {
private OrderDao orderDao = new OrderDaoImpl();
private OrderItemDao orderItemDao = new OrderItemDaoImpl();
private BookDao bookDao = new BookDaoImpl();
@Override
public String createOrder(Cart cart, Integer userId) {
//订单号===唯一性
String orderId = System.currentTimeMillis()+""+userId;
//创建一个订单对象
Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);
//保存订单
orderDao.saveOrder(order);
//遍历购物车中每一个商品项转换成为订单项保存到数据库
for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
//获取每一个购物车中的商品项
CartItem cartItem = entry.getValue();
//转换为每一个订单项
OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
//保存每一个订单项到数据库
orderItemDao.saveOrderItem(orderItem);
//更新库存和销量
Book book = bookDao.queryBookById(cartItem.getId());
book.setSales(book.getSales()+cartItem.getCount());
book.setStock(book.getStock()-cartItem.getCount());
bookDao.updateBook(book);
}
//清空购物车
cart.clear();
return orderId;
}
}
OrderServlet 程序:
public class OrderServlet extends BaseServlet{
OrderService orderService = new OrderServiceImpl();
/*
* 生成订单
* */
protected void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取Cart购物车对象
Cart cart = (Cart) req.getSession().getAttribute("cart");
//获取Userid
User loginUser = (User) req.getSession().getAttribute("user");
//如果没登陆,跳转到登录页面
if (loginUser == null){
req.getRequestDispatcher("/pages/user/login.jsp").forward(req,resp);
return;
}
Integer userId = loginUser.getId();
//调用orderService.createOrder(Cart,Userid);生成订单
String orderId = orderService.createOrder(cart,userId);
//以下方法当用户刷新时会造成重复提交表单,因此使用重定向方法,但重定向方法只支持Session域
//req.setAttribute("orderId",orderId);
//请求转发到/pages/cart/checkout.jsp
//req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp);
req.getSession().setAttribute("orderId",orderId);
resp.sendRedirect(req.getContextPath()+"/pages/cart/checkout.jsp");
}
}
修改 pages/cart/cart.jsp 页面,结账的请求地址:
修改 pages/cart/checkout.jsp 页面,输出订单号:
九、项目——阶段八:拦截请求Filter
1、使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查
即登入后台前需要认证
public class ManagerFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
Object user = httpServletRequest.getSession().getAttribute("user");
if (user == null){
httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
}else {
filterChain.doFilter(servletRequest,servletResponse);
}
}
}
web.xml配置
<filter>
<filter-name>ManagerFilter</filter-name>
<filter-class>com.cen.filter.ManagerFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ManagerFilter</filter-name>
<url-pattern>/pages/manager/*</url-pattern>
<url-pattern>/manager/bookServlet</url-pattern>
2、ThreadLocal 的使用
- ThreadLocal 的作用,它可以解决多线程的数据安全问题。
- ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)
ThreadLocal 的特点:
- ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
- 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。
- 每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
- ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。
测试类
public class OrderService {
public void createOrder(){
String name = Thread.currentThread().getName();
System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +ThreadLocalTest.threadLocal.get());
new OrderDao().saveOrder();
}
}
public class OrderDao {
public void saveOrder(){
String name = Thread.currentThread().getName();
System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +ThreadLocalTest.threadLocal.get());
}
}
public class ThreadLocalTest {
// public static Map<String,Object> data = new Hashtable<String,Object>();
public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
private static Random random = new Random();
public static class Task implements Runnable {
@Override
public void run() {
// 在 Run 方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为 key 保存到 map 中
Integer i = random.nextInt(1000);
// 获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("线程["+name+"]生成的随机数是:" + i);
// data.put(name,i);
threadLocal.set(i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new OrderService().createOrder();
// 在 Run 方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
// Object o = data.get(name);
Object o = threadLocal.get();
System.out.println("在线程["+name+"]快结束时取出关联的数据是:" + o);
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++){
new Thread(new Task()).start();
}
}
}
3、使用 Filter 和 ThreadLocal 组合管理事务
3.1、使用 ThreadLocal 来确保所有 dao 操作都在同一个 Connection 连接对象中完成
生成订单和订单项是需要同时完成的,因此使用到事务
JDBCUtils 工具类的修改:
public class JDBCUtils {
private static DruidDataSource dataSource;
private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
static {
try {
Properties properties = new Properties();
//读取 jdbc.properties属性配置文件
InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//从流中加载数据
properties.load(inputStream);
dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取数据库连接池中的连接
//如果返回null就是获取连接失败,有值才成功
public static Connection getconnection(){
Connection conn = conns.get();
//如果不为空,则可确保每次都使用同一个连接
if (conn == null){
try {
conn = dataSource.getConnection();//从数据库连接池中获取连接
conns.set(conn);//保存在ThreadLocal对象中,供后面的JDBC操作使用
conn.setAutoCommit(false);//设置为手动管理事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return conn;
}
/*
* 提交事务,并关闭释放连接
* */
public static void commitAndClose(){
Connection connection = conns.get();
if (connection != null){//如果不等于null,说明之前使用过连接,操作过数据库
try {
connection.commit();//提交事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
connection.close();//关闭连接,释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
// 一定要执行remove操作,否则会出错(因为Tomcat服务器底层使用了线程池技术)
conns.remove();
}
/*
* 回滚事务,并关闭释放连接
* */
public static void rollbackAndClose(){
Connection connection = conns.get();
if (connection != null){//如果不等于null,说明之前使用过连接,操作过数据库
try {
connection.rollback();//回滚事务
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
try {
connection.close();//关闭连接,释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
// 一定要执行remove操作,否则会出错(因为Tomcat服务器底层使用了线程池技术)
conns.remove();
}
// //关闭连接,放回数据库连接池
// public static void close(Connection conn){
// if (conn!=null){
// try {
// conn.close();
// } catch (SQLException throwables) {
// throwables.printStackTrace();
// }
// }
// }
}
修改 BaseDao
主要是把close()方法改成
public class BaseDao {
//使用DbUtils操作数据库
private QueryRunner queryRunner = new QueryRunner();
/**
* update() 方法用来执行:Insert\Update\Delete语句
*
* @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
*/
public int update(String sql,Object...args){
Connection connection = JDBCUtils.getconnection();
try {
return queryRunner.update(connection,sql,args);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException(throwables);
}
}
/**
* @Description 查询返回一个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T> T queryForOne(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException(throwables);
}
}
/**
* @Description 查询返回多个JavaBean的sql语句
* @Param type 返回的对象类型
* @Param sql 执行的sql语句
* @Param args sql对应的参数值
* @Param <T> 返回的类型的泛型
* @return
*/
public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
Connection con = JDBCUtils.getconnection();
try {
return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException(throwables);
}
}
/**
* 执行返回一行一列的sql语句
* @param sql 执行的sql语句
* @param args sql对应的参数值
* @return
*/
public Object queryForSingleValue(String sql,Object...args){
Connection conn = JDBCUtils.getconnection();
try {
return queryRunner.query(conn,sql,new ScalarHandler(),args);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException(throwables);
}
}
}
OrderService中加上
即在OrderServiceImpl类中的createOrder方法执行时,遇到错误跳出在OrderService类中捕获并处理,处理中加上回滚事务,则可达到事务操作的目的,使订单和订单项共存亡。
3.2、使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理。
因为每一个Service方法都有用到事务的情况
在
中的异常一定要抛出,不能捕获,这样才会出到该类中捕获错误从而回滚事务。
3.3、将所有异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面。
在 web.xml 中我们可以通过错误页面配置来进行管理。
错误页面自行配置
error404.jsp
error500.jsp