属性(变量)两边加+号,+号两边添加引号
//获取到当前字节码对象(ServletDemo02.class在内存中对象)
Class clazz = this.getClass();
//获取到AA字节码在内存中对象
Class clazz=AA.class;
//获取AA字节码对象上名称为sum的方法,参数有2个参数
Method md = clazz.getMethod("sum", int.class,int.class);
//执行md方法
md.invoke(new AA(), 123,4);
1、得到字节码对象
2、利用字节码对象查询方法
3、利用invoke()执行方法
BaseServlet继承HttpServlet,并且覆盖了service()函数(该函数首先通过getMethod()函数判断通信方式是get还是post
String method = req.getMethod();,然后利用字符串相等调用相应函数),这里也同样利用该思想,得到客户端传递过来的方法名称,然后利用放射机制调用对应的函数。
乱码问题:若为post,直接request.setCharacterEncoding("utf-8");
若为get,需要取出request的内容,并遍历进行编码。arr[i]=new String(arr[i].getBytes("iso-8859-1"),"utf-8");
我们知道一个模块(如用户模型)需要数据增删改查(4个Servlet),那多个模块(商品,订单,购物车 等)就需要
很多Servlet,所以基于的目的我们创建通用 Servlet。
原理:
从客户端向服务端发起请求,每次都要传递额外的键值对的数据method=””,
服务端获取到method对应的内容之后,通过判断不同的内容调用不同的功能
最常用从客户端向服务端发起请求,调用功能方式:
1_通过表单向服务端发起请求
2_通过链接向服务端发起请求
3_Ajax向服务端发起请求
创建通用Servlet的具体实现方法:
1. 项目下建立index.html
function fn(){
$.post("/BaseServlet/ServletDemo01",{"method":"checkStu","user":"tom"},function(data){
alert(data);
});
}
2.在项目下建立cn.itcast.servlet___>ServletDemo
利用equals()方法判断传递过来的方法名称,然后执行相应方法。
弊端:如果模块下功能较多,每个模块都有if(){}else{}语句过多,能否抽取公共部分,避免在每个模块都进行if,else判断(那么如何解决这个弊端呢,发现该部分if,else语句都实现同一个功能,判断方法名称,然后执行相应方法,那么就将该部分抽取出来。答案自然是反射,我们通过class.getMethod()获取该类下的所有方法,然后判断该类是否存在方法,如果有就执行,没有就执行默认方法。)
理解BaseServlet执行流程
每个servlet必然会执行 init()----->service()----->destory()3个函数。每个servlet都只执行一次init()和destory()方法,而每次请求都会执行service()方法,查看源码发现service中每次都会判断请求方法(get还是post),那么我们借鉴这种方法,重写serivice方法,每次在service方法中判断请求参数method对应的方法,然后调用对应的方法, 但是源码中方法都是固定的(get还是post),我们的方法名称是变化的,那么如何动态的获取每次请求得到方法名称呢?反射啊,利用class.getMethod()获取该类下的所有方法,然后判断该类是否存在方法,如果有就执行,没有就执行默认方法。
抽取工具类BaseServlet
package cn.itcast.store.web.base;
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 {
@Override
public void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// localhost:8080/store/productServlet?method=addProduct
String method = req.getParameter("method");
if (null == method || "".equals(method) || method.trim().equals("")) {
method = "execute";
}
// 注意:此处的this代表的是子类的对象(因为该类最终会被具体的servlet继承)
// System.out.println(this);
// 子类对象字节码对象
Class clazz = this.getClass();
try {
// 查找子类对象对应的字节码中的名称为method的方法.这个方法的参数类型是:HttpServletRequest.class,HttpServletResponse.class
Method md = clazz.getMethod(method, HttpServletRequest.class, HttpServletResponse.class);
if(null!=md){
String jspPath = (String) md.invoke(this, req, resp); //转发路径
if (null != jspPath) {
req.getRequestDispatcher(jspPath).forward(req, resp);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 默认方法
public String execute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, Exception {
return null;
}
}
按照经典的web--->service--->Dao搭建
1、创建web项目
2、创建包结构
3、导入jar包
4、导入工具类
UUIDUtil CookUtil
5、导入配置文件
C3P0配置文件,修改参数(dbname,user,pass)
JDBCUtils:设置main方法,打印方法getConnection()返回值
这里我们需要掌握如何创建数据库连接池(思想),创建一个list,在创建连接池对象时就提前创建好 多个连接,调用时移除list首元素conn,用完后需要归还该conn,但是Connection接口没有该函数,实现类也没有,但是注意到右close()函数 ,我们是否可以利用该函数呢? 首先我们想到的是修改源码(显然不现实),然后我们可以继承实现类,但是我们不知道实现类,这时候(无法修改源码和继承父类)我们就可以利用装饰器和动态代理来实现增强父类的功能(归根结底就是对原有方法增强,类似于Spring 的AOP),所以需要创建装饰器类(实现Connection接口,方便实现多态,即利用接口引用就可以调用装饰器的方法),该类构造函数需要传递源码中Connection实现类的对象,即装饰器类要包含被装饰的对象(精华所在),然后我们在装饰器类中重写close()方法。将使用完毕的conn添加到list末尾。
上述功能要求有常用的工具(DBCP和C3P0), 但是该工具包智能实现简化数据库的连接,不能简化数据库的CURD。当然我们肯定有这种工具包,那就是DBUtil。
DBUtil思想:将CUD统一用update去更新,R用query()去更新。
我们在使用DBUtil时只需要传入一个连接池对象(静态变量),然后我们声明一个查询实例对象:
QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());(给qr一个连接对象)
qr.query(sql, new BeanListHandler
这里我们就可以利用conn去查询了,查询结果怎么返回呢?由于是通用查询,所以我们不清楚返回的到底是什么对象,所以泛型便派上用场了。查看源码,我们发现底层库提供了一个ResultSetHandler接口,该接口中有一个handler()方法,来实现对查询结果的处理,那么怎么处理呢?ResultSetHandler有很多实现类BeanListHandler,BeanHandler 等,实现类中通过 重写handler()方法实现各种返回结果的处理,那么为什么要传入的字节码对象参数,而不是实例化参数呢?我猜测是因为当返回结果是多个对象时,实例对象只能接受一个结果对象,而用字节码对象(一般和反射结合)我们可以通过反射 实例化对象。
6_导入编码过滤器(EncodingFilter,解决中文乱码问题)(其实也可以利用动态代理)
思想:利用过滤器,我们队所有request请求都进行拦截,转化编码后再放行。
过滤器中的request上的3个方法进行增强
req.getParameterValues(); req.getParameterMap(); req.getParameter();
手动对过滤器进行配置
过滤器会执行doFilter()函数,所以在doFilter()中对request的参数进行编码转换,再放行。
//2.放行
chain.doFilter(new MyRequest(request), response);
//发送过来的所有数据都在request请求中, new 新建类MyRequest,在实例化MyRequest时传入(原request),而doFilter中 传递的是MyRequest对象,所以我们在MyRequest需要重写 req.getParameterValues(); req.getParameterMap(); req.getParameter();,当在servlet中调用这3个函数时就会调用这重写的3个函数,这不是装饰器模式吗????哈哈哈。。。
class MyRequest extends HttpServletRequestWrapper{
private HttpServletRequest request;
private boolean flag=true;
public MyRequest(HttpServletRequest request) {
super(request);
this.request=request;
}
@Override
public String getParameter(String name) {
if(name==null || name.trim().length()==0){
return null;
}
String[] values = getParameterValues(name);
if(values==null || values.length==0){
return null;
}
return values[0];
}
@Override
/**
* hobby=[eat,drink]
*/
public String[] getParameterValues(String name) {
if(name==null || name.trim().length()==0){
return null;
}
Map
if(map==null || map.size()==0){
return null;
}
return map.get(name);
}
@Override
/**
* map{ username=[tom],password=[123],hobby=[eat,drink]}
*/
public Map
/**
* 首先判断请求方式
* 若为post request.setchar...(utf-8)
* 若为get 将map中的值遍历编码就可以了
*/
String method = request.getMethod();
if("post".equalsIgnoreCase(method)){
try {
request.setCharacterEncoding("utf-8");
return request.getParameterMap();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else if("get".equalsIgnoreCase(method)){
Map
if(flag){
for (String key:map.keySet()) {
String[] arr = map.get(key);
//继续遍历数组
for(int i=0;i
try {
arr[i]=new String(arr[i].getBytes("iso-8859-1"),"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
flag=false; //这里修改为false有什么作用???
}
//需要遍历map 修改value的每一个数据的编码
return map;
}
return super.getParameterMap();
}
}
7_导入BaseServlet ,不需要配置
8_导入JSP页面
9_创建了用户模块相关程序
UserSerlvet UserService UserServiceImp UserDao UserDaoImp User
开发约定
为了项目管理方便,不会从客户端直接发起请求到JSP页面.
先请求到Servlet,在由Servlet转发到JSP页面.
1_ /jsp/index.jsp 修改连接
2_UserServlet__>registUI
return “/jsp/regist.jsp”;
1_准备工作(表单属性设置,链接拼接,Ajax编写)
准备工作实现完毕之后,可以从客户端向服务端发起请求
2_Servlet
明确要实现的功能,中文注释,代码
3_service_dao
4_jsp页面
PS:为了方便断点调试,eclise===>window==>preferences===>java===>debug
1_准备工作 /jsp/regist.jsp
设置表单各种属性 action method
2_UserServlet__>userRegist
接收表单参数
(这里可以自己手动调用set函数为User赋值,也可以调用BeanUtils工具类(MyBeanUtils.populate(user, map);其思想是利用反射完成属性注入),但是要注册时间转换器)
// 1_创建时间类型的转换器
DateConverter dt = new DateConverter();
// 2_设置转换的格式
dt.setPattern("yyyy-MM-dd");
// 3_注册转换器
ConvertUtils.register(dt, java.util.Date.class);
调用业务层注册功能,其实就是向数据库中插入用户信息。
注册成功,向用户邮箱发送信息,跳转到提示页面
String url="http://localhost:8080/store_v5/UserServlet?method=active&code="+emailMsg;
String content="
注册失败,跳转到提示页面
3_依次实现service,dao
4_ /jsp/info.jsp获取提示信息(
UserService userService = new UserServiceImp();
try {
userService.userRegist(user);
//发送邮件
MailUtils.sendMail(user.getEmail(), user.getCode());
//注册成功,向用户邮箱发送信息,跳转到提示页面
request.setAttribute("msg", "用户注册成功,请激活!!");
} catch (Exception e) {
e.printStackTrace();
//注册失败,跳转到提示页面
request.setAttribute("msg", "用户注册失败,请重新注册!!");
}
注册页面是form形式
注册没有实现确认密码
1.2步骤实现(由于链接就是访问UserServlet下的某个函数,并附带验证码,所以不用经过前端,直接到后端Servlet)
String url="http://localhost:8080/store_v5/UserServlet?method=active&code="+emailMsg;
1_准备工作(忽略)
用户点击邮箱中的激活链接,向服务端发送method=active&code=234123adf22234
2_UserServlet___>active
获取到激活码
调用service功能,对账户进行激活操作
根据验证码(code)去查询用户,如果用户存在,则激活成功,然后修改 用户状态(state=1,并且将code改为null,发送更新语句更新数据库(这是可以更新单个字段或整个字段,为了通用性,我们更新整个字段)。
进行信息提示(成功,失败)
3_service _ dao
4_/jsp/login.jsp
获取到注册成功的提示信息
5_由于info.jsp已经实现过,不需要再设置info.jsp中内容
//获取激活码
String code = request.getParameter("code");
//调用业务层激活功能
UserService userService = new UserServiceImp();
boolean flag = userService.userActive(code);
if(flag==true) {
//用户激活成功,想request放入提示信息,转发到登录页面
request.setAttribute("msg", "用户激活成功,请登录!");
return "/jsp/login.jsp";
}else{
//用户激活失败,想request放入提示信息,转发到提示页面
request.setAttribute("msg", "用户激活失败,请重新激活!");
return "/jsp/info.jsp";
}
三、登录功能
*_ /jsp/index.jsp 修改登录链接
*_UserServlet___>loginUI
return “/jsp/login.jsp”;
1_准备工作 /jsp/login.jsp
设置form标签action,method
设置表单下input标签的name属性
2_User_userLogin
*_获取数据
*_调用业务层功能
//此处:可以利用异常在模块之间传递数据
UserDao userDao = new UserDaoImp();
User uu = userDao.userLogin(user);
if(uu==null) {
throw new RuntimeException("密码有误!"); //账号不存在利用Ajax在登录页面上解决
}else if(uu.getState()==0){
throw new RuntimeException("用户未激活!");
}else{
return uu;
}
*_成功,session存放用户信息,重定向到首页
//1.获取用户信息
User user = new User();
MyBeanUtils.populate(user, request.getParameterMap());
//2.调用业务层登录功能
UserService userService = new UserServiceImp();
User user2 = null;
try {
user2 = userService.userLogin(user);
//用户登录成功,将用户信息放入session中 (因为后续会在右上角显示账户等信息,放在request中会随着下一次请求而消失)
request.getSession().setAttribute("loginUser", user2);
response.sendRedirect("/store_v5/index.jsp"); //这里是重定向
return null;
} catch (Exception e) {
//用户登录失败
String msg=e.getMessage();
System.out.println(msg);
//向request放入失败的信息
request.setAttribute("msg", msg);
return "/jsp/login.jsp";
}
*_失败request放入失败信息,转发到登录页面
3_service_dao
PS: service:自定义异常向servlet传递2种数据(密码不存在,用户未激活)
4_ /jsp/index.jsp 获取到了用户信息
1.1 原理
1.2步骤实现
1_准备工作
/jsp/index.jsp 修改连接
2_UserServlet___>logOut
清除session
重新定向到首页
return null;
//清除session
request.getSession().invalidate();
//重新定向到首页
response.sendRedirect("/store_v5/index.jsp");
return null;
步骤
1_复制info.jsp___>header.jsp
2_打开/jsp/index.jsp ,将页面部分的导航和菜单区域复制到header.jsp
在header.jsp通过tag导入标签库
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
3_打开其他的所有页面进行替换
原理分析
1.2 步骤实现
*_准备工作(忽略) http://localhost:8080/store_v5/
*_webContent/index.jsp
*_创建分类模块的相关程序
CategoryServlet CategoryService CategoryServiceImp CategoryDao
CategoryDaoImp Category
*_创建IndexServlet
调用业务层获取全部分类数据
将全部分类信息放入request
//执行CategoryService,得到所有分类
/*CategoryService categoryService = new CategoryServiceImp();
List
//将分类保存到request中 ,不能忘session中放,因为一个人500个字符,一亿人数据量爆炸
request.setAttribute("allCats", list);*/
转发到真实的首页/jsp/index.jsp
*_CategoryService
*_CategoryDao
*_/jsp/header.jsp 获取全部分类信息
PS: 由于获取分类要遍历数据,需要用到c:forEach标签,需要导入标签库
弊端:当访问首页时可以看到全部分类信息,但是如果访问其它页面,看不到分类信息.
版本1:在浏览器输入http://localhost:8080/store_v6/,默认访问webContent/index.jsp,然后跳转到/jsp/index.jsp中,
1.修改
2.在IndexServlet中,由于没有传递method参数,所以执行默认方法execute,在execute中调用业务层CategoryService中的findAllCats()方法,返回集合list,然后将其保存到request中(不能保存在session中,每个用户在session中有独立区域,用sessionId标识 ,容易导致数据爆炸),跳转/jsp/index.jsp,最后在header.jsp中显示。
原理
步骤实现
1_/jsp/header.jsp
当页面加载完毕之后,向服务端发起Ajax请求,服务端经过处理,
将所有分类信息以JSON格式的数据返回,客户端获取到返回的所有分类,
绑定在页面的显示分类区域
页面底部
$.post(url,{},function(data){},”json”){}
2_CategoryServlet__>getAllCats
//调用业务层获取全部分类
//将全部分类转换为JSON格式的数据
//将全部分类信息响应到客户端
3_调试
观察本次请求,响应网络传输情况,
目的:排除2端错误
4_实现/jsp/header.jsp中AJAX代码的剩余部分
$(function(){
//向服务端CategoryServlet__>gteAllCats发起ajax请求,服务端经过处理,
//将所有分类信息以JSON格式的数据返回,获取到返回的所有分类动态绑定在页面的显示分类区域
var url="/store_v5/CategoryServlet";
var obj={"method":"findAllCats"};
$.post(url,obj,function(data){
//alert(data);
//获取到服务端响应会的数据,经过观察data中存放的是一个JSON格式数组,遍历数组,动态的显示分类区域代码
$.each(data,function(i,obj){
var li="
$("#myUL").append(li);
//$(id) 这样写只有一个可能,id是变量,不然就报错
///$(#id)这个是,比如你页面有
//你可以alert 下。alert($("#aa").html()),这样就获取了他的html
});
},"json");
});
弊端:如果用户频繁的访问包含分类信息的页面,每次都要去DB中取获取分类信息,影响性能.
原理分析
步骤实现
1_导入jar包
2_导入JedisUtils工具类(修改参数127.0.0.1)
3_启动windows版本的redis
4_实现CategoryServlet__>findAllCats
*_在redis中获取全部分类信息
*_如果无法获取分类信息,
查询DB中的分类,转换为JSON格式字符串,
将JSON格式字符串向redis缓存一份,之后将JSON格式数据响应到客户端
*_如果可以获取到分类信息
直接响应即可
//1.在redis中获取全部分类信息
Jedis jedis = JedisUtils.getJedis();
String jsonStr = jedis.get("allCats");
if(null==jsonStr || "".equals(jsonStr)) {
//1.调用业务层获取全部分类
CategoryService categoryService = new CategoryServiceImp();
List
//2.将全部分类转换为JSON格式的数据
jsonStr = JSONArray.fromObject(list).toString();
System.out.println("redis缓存中没有数据");
jedis.set("allCats", jsonStr);
}else {
System.out.println("redis缓存中有数据");
}
//3.将全部分类信息响应到客户端
//告诉浏览器本次响应的数据是JSON格式的字符串
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(jsonStr);
return null;
版本2:
原理:页面加载完,利用Ajax向服务端发一个请求,服务端返回json格式数据,然后动态绑定分类信息。
由于request只在本次请求中有效,当跳转页面后不再显示分类信息,所以当页面加载完毕之后,向服务端发起Ajax请求,服务端经过处理,向服务端CategoryServlet__>findAllCats发起ajax请求,服务端经过处理,将所有分类信息以JSON格式的数据返回,获取到返回的所有分类动态绑定在页面的显示分类区域
1.将全部分类转换为JSON格式的数据
String jsonStr = JSONArray.fromObject(list).toString();
//3.将全部分类信息响应到客户端
//告诉浏览器本次响应的数据是JSON格式的字符串
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(jsonStr);
return null; //不需要转发
在客户端:
将所有分类动态绑定在页面的显示分类区域:
版本3:
由于所有页面均需要显示分类信息,那么每次都需要去访问数据库,影响性能。所以将分类信息加载到redis数据库(redis数据库将数据保存在内存中,效率快),
//1.在redis中获取全部分类信息
//Jedis jedis = JedisUtils.getJedis();
Jedis jedis = JedisUtils.getJedis();
String jsonStr = jedis.get("allCats");
if(jsonStr==null || "".equals(jsonStr) || jsonStr.trim().equals("")) {
CategoryService categoryService = new CategoryServiceImpl();
List
//2.将全部分类转换为JSON格式的数据
jsonStr = JSONArray.fromObject(list).toString();
System.out.println("redis缓存中没有数据");
jedis.set("allCats", jsonStr);
}else {
System.out.println("redis缓存中有数据");
}
在IndexServlet(首页servlet)中并没有传递方法,所以执行默认execute方法。该方法中查询所有分类,返回分类集合,然后显示在页面。
1.1 分析SQL语句编写
#查询商品表中最新的9件商品信息
SELECT * FROM product WHERE pflag=0 ORDER BY pdate DESC LIMIT 0 ,9
#查询商品表中最热,最新的9件商品信息
SELECT * FROM product WHERE pflag=0 AND is_hot=1 ORDER BY pdate DESC LIMIT 0 ,9
1.2 步骤实现
1_准备工作(忽略)
2_IndexServlet___>execute
//调用业务层查询最新商品,查询最热商品,返回2个集合
//将2个集合放入到request
//转发到真实的首页
3_建立商品模块相关程序
ProductServlet ProductService ProductServiceImp ProductDao ProductDaoImp Product
4_调用service,dao
5_/jsp/index.jsp 获取最新/最热9件商品信息
1_准备工作 /jsp/index.jsp修改连接
2_ProductServlet___>findProductByPid
获取商品pid
根据商品pid查询商品信息
将获取到的商品放入request
转发到/jsp/product_info.jsp
3_ProductService___>ProductDao
4_/jsp/product_info.jsp
1.1 分析SQL语句实现
#查看类别cid为1的商品的信息带有分页
SELECT * FROM product WHERE cid = 1 LIMIT ? ,?
#统计类别为1的商品的数量
SELECT COUNT(*) FROM product WHERE cid = 1
1.2 原理
1.3 步骤实现
1_准备工作 /jsp/header.jsp
2_ProductServlet_____>findProductsByCidWithPage
获取cid,num
调用业务层功能:以分页形式查询当前类别下商品信息
返回PageModel对象(1_当前页商品信息2_分页3_url)
将PageModel对象放入request
转发到/jsp/product_list.jsp
//1.的到cid,num
String cid = request.getParameter("cid");
int curNum = Integer.parseInt(request.getParameter("num"));
//调用业务层功能:以分页形式查询当前类别下商品信息
ProductService productService = new ProductServiceImp();
//参数是当前商品类别和当前访问页码
PageModel pm = productService.findProductsByCidWithPage(cid, curNum);
//返回PageModel对象(1_当前页商品信息2_分页3_url)
//3.将信息存放到request中
request.setAttribute("page", pm);
//4.跳转到/jsp/product_info.jsp
return "/jsp/product_list.jsp";
3_ProductService
创建PageModel对象 目的:计算分页参数
关联集合
关联url
//1_创建PageModel对象 目的:计算分页参数
//统计当前分类下商品个数 select count(*) from product where cid=?
int totalRecords=productDao.findTotalRecords(cid);
PageModel pm=new PageModel(curNum,totalRecords,12);
//2_关联集合 select * from product where cid =? limit ? ,?
List
System.out.println("product list:"+list);
pm.setList(list);
pm.setUrl("ProductServlet?method=findProductsByCidWithPage&cid="+cid);
return pm;
4_ProductDao
1_统计当前类别下商品个数
2_统计当前类别的当前页中的商品信息
5_/jsp/product_list.jsp
获取了当前类别下的当前页中的分类数据和分页参数
约定: 1_当前页: num
2_ 向request放入PageModel属性名称page
分页:抽取分页模型
因为要分页显示,所以我们先抽取分页模型(类),
分页模型类:
属性:
private int currentPageNum,
private int pageSize,
private int totalRecoreds,
//以上3个参数作为构造函数参数
private int totalPageNum,
private int startIndex,
private int prePageNum,
private int nextPageNum,
private int startPage,
private int endPage,
private List list,
private String url,
分页具体步骤:
首先在header.jsp页面中的Ajax部分设置跳转的servlet(ProductServlet)的findProductsByCidWithPage()方法,
传递的参数:cid,num;
ProductServiceImpl:
1.查询该类商品的总数
2.创建PageModel对象
3.查询该类商品详情,并保存在list中
4.将list属性保存在pageModel中
5.设置下一次跳转url(注意带上cid)
pm.setUrl("/ProductServlet?method=findProductsByCidWithPage&cid="+cid);
页面跳转:
req.setAttribute("page", pm);
return "/jsp/product_list.jsp";
抽取pagefile.jsp
1.1 购物项:(图片路径,商品名称,商品价格,这类商品购买的数量,这类商品总价小计)
class CartItem{
private Product product; //携带图片路径,商品名称,商品价格
private int num; //当前类别商品的数量
private double subTotal; //小计,当前这类商品总共价格 经过计算获取到
}
1.2购物车(个数不确定的购物项)
class Cart{
//List list=new ArrayList(); 个数不确定的购物项 需要遍历商品,然后比较商品id,O(n),不可取
//Map map=new HashMap(); 个数不确定的购物项 直接用商品id做key,每次只需查看是否包含该key,O(1)
double total; //总计/积分 :可以经过计算获取到
方法:清空购物车
方法:移除购物车上的购物项
方法:添加商品到购物车
}
总结:
购物项(商品对象,数量,小计) 小计经过计算获取
购物车(2个属性3个方法)
2个属性: Map
Total:总计
3个方法
清空购物车 移除购物项 添加购物项到购物车
总计是经过计算获取到的
1.3购物车内存分析
1.4添加购物项到购物车原理分析
1.5步骤实现
1_准备工作
*_/jsp/product_list.jsp 修改连接
*_/jsp/product_info.jsp
自己设置form表单,设置form method,action
设置隐藏域向服务端传递商品pid
PS:如果一个表单中有多个按钮,点击不同的按钮提交到不同路径
var form= document.getElementById(“formId”);
form.submit();
2_CartServlet____>addCartgItemToCart
从session获取购物车
如果获取不到,创建购物车对象,放在session中
如果获取到,使用即可
获取到商品id,数量
通过商品id查询都商品对象
获取到待购买的购物项
调用购物车上的方法
重定向到/jsp/cart.jsp
//从session获取购物车
Cart cart = (Cart)request.getSession().getAttribute("cart");
if(cart==null) {
//如果获取不到,创建购物车对象,放在session中
cart = new Cart();
request.getSession().setAttribute("cart", cart);
}
//如果获取到,使用即可
//获取到商品id,数量
String pid=request.getParameter("pid");
int num=Integer.parseInt(request.getParameter("quantity"));
//通过商品id查询都商品对象
ProductService productService = new ProductServiceImp();
Product product = productService.findProductByPid(pid);
//取到待购买的购物项
CartItem cartItem = new CartItem();
cartItem.setNum(num);
cartItem.setProduct(product);
//调用购物车上的方法
cart.addCartItemToCar(cartItem);
//重定向到/jsp/cart.jsp
response.sendRedirect("/store_v5/jsp/cart.jsp");
System.out.println("CartServlet:");
return null;
3_ /jsp/cart.jsp 获取购物车上商品信息
重定向/转发
区别:1_ 一次还是两次请求响应 request域数据丢失
2_路径显示问题
3_第二次访问位置
1_准备工作
为购物车上的删除链接绑定了点击事件
$(function(){
//页面加载完毕之后获取到class的值为delete元素,为其绑定点击事件
$(".delete").click(function(){
if(confirm("确认删除?")){
//获取到被删除商品pid
var pid=this.id;
window.loaction.href="/store_v5/CartServlet?method=removeCartItem&id="+pid;
}
});
});
2_CartServlet___>removeCartItem
获取待删除商品pid
获取到购物车
调用购物车删除购物项方法
重定向到/jsp/cart.jsp
1_准备工作
/jsp/cart.jsp 修改连接
2_ CartServlet___>clearCart
获取购物车
调用购物车的清空方法
重定向到/jsp/cart.jsp页面
//获取购物车
Cart cart=(Cart)req.getSession().getAttribute("cart");
//调用购物车上的清空购物车方法
cart.clearCart();
//重新定向到/jsp/cart.jsp
resp.sendRedirect("/store_v5/jsp/cart.jsp");
return null;
未实现:
只是将购物车存储在了session中,我们尝试将其存储在数据库中,数据库字段包括:uid(用户id), pid, 小计,数量。
通过小计来计算总计(要添加函数)
然后将结果保存到request中。
如果添加商品到购物车后,不能直接跳转到cart.jsp,要先调用函数将数据保存到数据库中,然后将数据库中的购物车数据取出来来,并封装到cart中,然后将其保存在request中。
class Cart
private String oid; //订单编号
private Date ordertime; //下单时间
private double total; //总计
private int state; //状态
private String address; //收货人地址
private String name; //收货人姓名
private String telephone; //收货人电话
// private String uid;
// 1_程序对象和对象发生关系,而不是对象和对象的属性发生关系
// 2_设计Order目的:让order携带订单上的数据向service,dao传递,user对象是可以携带更多的数据
private User user;
// 程序中体现订单对象和订单项之间关系,我们再项目中的部分功能中有类似的需求:查询订单的同时还需要获取订单下所有的订单项
private List
订单项模块:
class OrderItem
private String itemid; //id
private int quantity; //数量
private double total; //小计
//1_对象对应对象
//2_product,order携带更多的数据
private Product product; //商品id :pid
private Order order; //订单id:该订单项属于哪次订单
保存订单:
在将商品添加到购物车后,点击提交订单,然后跳转到OrderServlet?method=saveOrder
1.saveOrder()
在刚方法中拿到购物车中的属性,并遍历购物项的同时,创建订单项
for(CartItem item: cart.getCartItems()) {
OrderItem orderItem = new OrderItem();
orderItem.setItemid(UUIDUtils.getId());
orderItem.setProduct(item.getProduct());
orderItem.setQuantity(item.getNum());
orderItem.setTotal(item.getSubTotal());
//设置当前的订单项属于哪个订单:程序的角度体检订单项和订单对应关系
orderItem.setOrder(order);
order.getList().add(orderItem);
}
然后保存订单,
2.保存订单
分别保存订单和订单想,注意这是一个事务,必须同时成功或者失败
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
orderDao.saveOrder(conn, order); //保存订单,注意传递同一个conn
for(OrderItem orderItem: order.getList()) {
orderDao.saveOrder(conn, orderItem); //保存订单项
}
conn.commit();
}catch(Exception e) {
conn.rollback();
}
//dao
qr.update(conn, sql, params);
4.最后跳转到oder_info.jsp
//清空购物车
cart.clearCart(); //一定呀清空
//将订单放入request
request.setAttribute("order", order);
查询订单:
在header.jsp修改/OrderServlet?method=findMyOrdersWithPage&num=1(注意:一定要带上当前页数num属性)
1.获得user,因为后续查询orders表需要,
2.在PageModel
①.获取该用户所有订单数量:orderDao.getTotalRecords(user);
②.创建PageModel对象,new PageModel
③.为pm绑定list属性:其中list中保存的是Order对象
查询该用户的部分订单:List
遍历得到订单项:
//因为要显示商品详细信息,而商品详细信息只存在与product表,所以要联合查询
//但是没有任何对象可以装下查询回来的值(是多个类的属性),所以用mapListHandler()存储
//查询当前订单下的订单项
List
// 由于BeanUtils将字符串"1992-3-3"向user对象的setBithday();方法传递参数有问题,手动向BeanUtils注册一个时间类型转换器
// 1_创建时间类型的转换器
DateConverter dt = new DateConverter();
// 2_设置转换的格式
dt.setPattern("yyyy-MM-dd");
// 3_注册转换器
ConvertUtils.register(dt, java.util.Date.class);
//将map中属于orderItem的数据自动填充到orderItem对象上
BeanUtils.populate(product, map);
BeanUtils.populate(orderItem, map);
orderItem.setProduct(product);
order.getList().add(orderItem);
④.pm.setUrl("OrderServlet?method=findMyOrdersWithPage");
⑤.转发
request.setAttribute("page", pm);
return "/jsp/order_list.jsp";
查询订单详情(点击付款的时候要传递oid)
OrderServlet?method=findOrderByOid&oid=${o.oid}">付款
和上面类似,return "/jsp/order_info.jsp";
最后的付款
后台管理模块:
这里主要涉及到DTree树、FrameSet标签的使用(main属性)
1、添加分类的时候在service中要将redis中的allCats删除(清理缓存,使得数据统一)
2、添加产品的时候要上传图片(要查询所有分类,因为session中没有分类信息,所以先查询将其放到request中):
1_表单method 必须为post
2_提供file组件
3_设置form标签的enctype属性为multipart/form-data
如果没有设置enctype浏览器是无法将文件自身传递到服务端
POST /TestUpload/ServletDemo01 HTTP/1.1
请求头
请求头
请求头
content-Type: multipart/form-data; boundary=---------------------------289271763422208
Content-Length: 12517
-----------------------------289271763422208
Content-Disposition: form-data; name="username"
11111
-----------------------------289271763422208
Content-Disposition: form-data; name="password"
22222
-----------------------------289271763422208
content-Disposition: form-data; name="userhead"; filename="11.bmp"
content-Type: image/bmp
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
BMV/二进制乱码数据,代表图片自身自身自身自身自身自身自身自身自身自身自身自身自身
-----------------------------289271763422208--
PS:如果设置了表单form标签的enctype属性之后,请求体部分的内容的格式发生更改
传统的方式:
username=tom&password=1234&userhead=11.bmp
结论:
1_如果设置了mutipart/form-data,在服务端是无法通过request.getParameter(name);
获取数据
2_可以通过request.getInputStream();获取请求体部分的数据
手动实现上传可行性是可以的.
思路:
1_导入commons-fileupload-1.2.1.rar之后
2_执行很简单的3行语句,
3_获取到一个集合(
将每对分割线中间的内容封装在FileItem对象上.
4_遍历集合
5_如果当前的FileItem对象是普通项
将普通项上name属性的值作为键,将获取到的内容作为值,放入MAP中
{username<==>tom,password<==>1234}
6_如果当前的FileItem对象是上传项
通过FileItem获取到输入流对象,通过输入流可以获取到图片二进制数据
在服务端创建一个空文件(后缀必须和上传到服务端的文件名后缀一致)
建立和空文件对应的输出流
将输入流中的数据刷到输出流中
释放资源
向map中存入一个键值对的数据 userhead<===> /image/11.bmp
{username<==>tom,password<==>1234,userhead<===>image/11.bmp}
7_利用BeanUtils将MAP中的数据填充到user对象上
8_调用servcie_dao将user上携带的数据存入数据仓库,重定向到查询全部商品信息路径
问题:
1_如果文件重名发生覆盖问题
UUID
2_同目录下文件/目录过多,性能问题
在images下最多创建16个目录,任意一个目录进入之后最多创建16个目录,
最多创建8层目录.