1.文件上传进度
package com.hxuner.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.ProgressListener;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
public class UploadServlet extends HttpServlet {
public void doGet( final HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.如果是一个文件上传表单,不能使用传统方法获取请求
//String username=request.getParameter("username");
//System.out.println("username="+username); //输出null
//String addr=request.getParameter("addr");
//System.out.println("addr="+addr); //输出null
//2.获取请求实体中所有内容
/*
ServletInputStream sis=request.getInputStream();
byte[] array=new byte[10];
//使用输入流读取一次内容,读取的内容存入array中,len的值是读取字节的长度
int len=sis.read(array);
while(len!=-1){
String str=new String(array,0,len);
System.out.println(str);
len=sis.read(array);
}
sis.close();
输出-----------------------------265001916915724
Content-Disposition: form-data; name="username"
å¼ ä¸
-----------------------------265001916915724
Content-Disposition: form-data; name="addr"
èå¸
-----------------------------265001916915724
Content-Disposition: form-data; name="file"; filename="176.20.190.64.txt"
Content-Type: text/plain
ÎÒÊÇÒ»¸öºÜ´ÏÃ÷µÄÈË
-----------------------------265001916915724--
*/
//获取ServletContext对象,解决路径难问题
//sc.getRealPath("虚拟路径")->返回该资源的绝对路径
//该方法默认从WebRoot文件夹下开始找
ServletContext sc=this.getServletContext();
//使用传统的方式不能解决文件上传表单的乱码问题
//request.setCharacterEncoding("utf-8");
//------------正确使用commons.fileupload.jar 这个包依靠commons-io进行操作 来接受上传文件--------------------------
//1.构造工厂DiskFileItemFactory(FileItem的工厂)时,指定内存缓冲区大小和临时文件存放位置。
//int sizeThreshold 内存缓冲区的大小
//File repository 临时文件存放位置
//文件上传时需要将请求的实体内容全部读取后才能做处理,此时需要将实体内容缓冲起来 内存缓冲快但是耗费内存 文件缓冲慢,但是可以存放大量数据
//所以此处提供了两个选项 if(数据大小小于内存缓冲区的大小sizeThreshold){使用内存做缓冲 速度快}
// else if(文件大小超过了内存缓冲区的大小){在repository指定的位置下创建临时文件来缓冲数据}
DiskFileItemFactory factory=new DiskFileItemFactory(1024, new File(sc.getRealPath("/temp")));
//2.使用DiskFileItemFactory 对象创建ServletFileUpload对象,进行通用配置。
ServletFileUpload fileUpload=new ServletFileUpload(factory);
//2.1判断当前表单是否是一个文件上传表单 enctype为multipart/form-data类型
//boolean isMultipartContent(HttpServletRequest request)
if(!fileUpload.isMultipartContent(request)){
throw new RuntimeException("请使用正确的文件上传表单");
}
//2.2设置单个文件的最大大小 setFileSizeMax(long fileSizeMax)
//fileUpload.setFileSizeMax(1024*1024); //1MB
//2.3设置单次上传的所有文件的总大小设置 setSizeMax(long sizeMax)
//fileUpload.setSizeMax(1024*1024*5); //5MB
//2.4---注意3:设置字符集,解决文件名的乱码问题---
fileUpload.setHeaderEncoding("utf-8");
//-------特别注意5---文件上传进度---------------
/*
* 文件上传进度1.fileUpload.setProgressListener
fileUpload允许程序员提供一个ProgressListener的实现类,调用set方法时,是绑定程序员提供的监听器的实现类的对象
当fileUpload发现文件上传的进度发生变化时,会主动调用其绑定的进度监听的update(pBytesRead,pContentLength,pItems)方法,将当前上传进度的相关信息传给update方法
程序员可以重写该方法,添加自己需要执行的逻辑,在该方法中,计算出当前上传进度的百分比,pBytesRead/pContentLength
将该百分比存入Session作用域中,供UploadProgressServlet来使用
*/
//为fileUpload绑定一个上传进度的监听器
fileUpload.setProgressListener(new ProgressListener() {
//当fileUpload发现文件上传进度出现变化,会调用绑定的进度监听器的update方法,将当前的数据发给该方法
private long starttime=System.currentTimeMillis(); //文件上传开始时间
@Override
public void update(long pBytesRead, long pContentLength, int pItems) {
//pBytesRead ->目前已经上传的字节数
//pContentLength->文件总长度
//pItems ->当前读取的Input编号
//System.out.println("已经读取="+pBytesRead/1024+"kb,文件总长度="+pContentLength/1024+"kb,当前读取的Input编号="+pItems);
//文件读取的进度 已经读取的数量/总长度 *1.0变double,不是整除 *100变百分比
//99.9853->*100 9998.53->round取整 9998->/100=99.98
double progress=pBytesRead*100/(pContentLength*1.0);
progress=Math.round(progress*100)/100.0;
//System.out.println(progress+"%");
//UploadServlet获取文件上传进度,存入session作用域,共享给UploadProgressServlet
request.getSession().setAttribute("progress", progress+"%");
//当前上传到的速度 目前已经上传的字节数/已用时间
long endtime=System.currentTimeMillis();//文件结束时间
double costtime=(endtime-starttime)/1000.0; //ms->s
double speed=costtime==0?0:(pBytesRead/1024/costtime);
// System.out.println("当前上传速度"+speed+"kb/s"); //kb/s
//预计完成时间 (总长度-已读)/速度
}
});
//3.实际读取输入流中的内容,封装成FileItem(对表单中每一个input进行封装)
//List<FileItem> parseRequest(HttpServletRequest request)
try {
List<FileItem> list=fileUpload.parseRequest(request);
//对FileItem集合进行操作,获取表单中的数据
if(list!=null){
for(FileItem fileItem:list){
//判断当前FileItem是不是一个普通字段项 如果返回true表示这是一个普通字段项 返回false表示是一个文件上传项
//boolean isFormField()
if(fileItem.isFormField()){ //是普通的input输入框的内容
//如果是普通字段项
//String getFieldName() //获取字段项的名称
//String getString() //获取字段项的值
//String getString(String encode) //获取字段项的值
//获取input的name
String name=fileItem.getFieldName();
//获取input的value
//String value=fileItem.getString();
String value=fileItem.getString("utf-8"); //---注意1:通知工具类使用utf-8进行解码,解决提交参数乱码问题---
System.out.println("name="+name+",value="+value);
}else{ //是文件上传项
//String getName() //获取文件名
//InputStream getInputStream() //获取文件内容的流
//delete() //删除临时文件
//获取上传的文件的文件名
String fileName=fileItem.getName();
//-----------特别注意1 ie浏览器bug-------------
//ie浏览器的部分版本,在上传文件时,会使用文件的完整路径作为文件名
//a.txt c:users\administra\desktop\a.txt
if(fileName.contains("\\")){ //文件名不允许存在\
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);//文件名从最后一个\+1截取到最后
}
//--------特别注意3 文件名重复-------
//多个上传名称相同时 文件会发生覆盖
//解决方案:应该想办法让文件名 尽量不要重复 - 在文件名的前面拼接UUID来保证文件名绝对不会重复
fileName=UUID.randomUUID()+"_"+fileName; //数据库里保存了该filename与用户的对应关系
//-------特别注意4:上传文件目录下文件过多----------
//一个文件夹下文件过多会造 访问缓慢,甚至有可能无法访问
//解决方案:所以应该想办法将这些文件分目录存储,利用文件名的hascode的16进制表示生成对应的目录。
String hsStr=Integer.toHexString(fileName.hashCode());
//补足8位
while(hsStr.length()<8){
hsStr="0"+hsStr;
}
//生成中间路径
String midPath="/";
for(int i=0;i<hsStr.length();i++){
midPath=midPath+hsStr.charAt(i)+"/";
}
// 用本变量保存实际存储的路径
// sc.getRealPath方法返回的路径会去掉最后的 /
String savePath=sc.getRealPath("/WEB-INF/upload"+midPath);
// 在服务器上创建对应的文件夹
new File(savePath).mkdirs();
//获取上传的文件的输入流in->read
InputStream is=fileItem.getInputStream();
FileOutputStream fos=null;
try {
//输入流 out ->Write
fos=new FileOutputStream(savePath+"/"+fileName);
/*
* ----------- 特别注意2:文件上传保存位置问题---------------------
上传一个这个index.jsp到upload里,然后访问浏览器/upload/index.jsp就可以通过上传的jsp写的代码显示所有文件内容
所以不让用户通过浏览器直接访问其上传的文件。
文件上传保存的位置一定不能被外界直接访问 防止用户浏览器访问 下载资源 或执行jsp恶意代码
要么保存在WEB-INF下保护起来
要么放在本地磁盘其他位置,保证通过浏览器无法直接访问
*/
byte[] array=new byte[100];
int len=is.read(array);
while(len!=-1){
fos.write(array,0,len);
len=is.read(array);
}
} catch (Exception e) {
// TODO: handle exception
}finally{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
//---注意2:在关流之后,要注意删除临时文件,需要在关流之后调用---
fileItem.delete();
}
}
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>文件上传</title>
<%--文件上传进度3:File.jsp
文档就绪事件:给form表单添加一个了onSubmit事件
在事件中开启周期性计时器,每个100ms,向服务器发送一个AJAX请求
使用返回的上传进度给页面的in_div的宽度赋值,实现上传进度条的效果
--%>
<style type="text/css">
#out_div{
border:1px solid gray;
width:150px;
height:20px;
}
#in_div{
width:0%;
height:20px;
background: blue;
}
</style>
<script type="text/javascript" src="/day017JavaWeb_ListenerAndFile/js/jquery-1.4.2.js"></script>
<script type="text/javascript">
<%--异步刷新Ajax,每隔一段时间局部刷新,而不是普通请求,普通请求要刷新整个页面 --%>
$(function(){
var inter;
//表单提交触发该方法
$("#f").submit(function(){
//启动一个周期性计时器,每隔设定时间调用一次function() function()里面发送ajax请求
inter=window.setInterval(function(){
var url="/day017JavaWeb_ListenerAndFile/UploadProgressServlet";
// function()里面发送ajax请求
$.get(url,function(result){
//服务器返回数据的个数 xxx.xx%
$("#in_div").width(result);
});
//关闭周期性计时器
if(result=="100.0%"){
window.clearInterval(inter);
}
}, 100);
});
/*
var i=1;
//开启一个周期性计时器,每隔设定时间调用一次function()
var inter=window.setInterval(function(){
i++;
//关闭周期性计时器
if(i==5){
window.clearInterval(inter);
}
}, 1000);
*/
});
</script>
</head>
<body>
<%--
提供一个带有文件上传项的表单
文件上传的输入框必须有name属性才能被上传
文件上传的表单必须是post提交
文件上传的表单必须设置enctype=multipart/form-data
--%>
<form id="f" action="${app}/UploadServlet" method="post" enctype="multipart/form-data"> <%--文件上传的表单必须是post提交 文件上传的表单必须设置enctype=multipart/form-data --%>
用户名<input type="text" name="username"><br>
地址<input type="text" name="addr"><br>
文件<input type="file" name="file"><br> <%-- 文件上传的输入框必须有name属性才能被上传--%>
<input type="submit" value="提交"><br>
</form>
<div id="out_div">
<div id="in_div">
</div>
</div>
特别注意2:<br>
上传一个这个index.jsp到upload里,然后访问浏览器/upload/index.jsp就可以通过上传的jsp写的代码显示所有文件内容<br>
所以不让用户通过浏览器直接访问其上传的文件。<br>
文件上传保存的位置一定不能被外界直接访问 防止用户浏览器访问 下载资源 或执行jsp恶意代码<br>
要么保存在WEB-INF下保护起来 <br>
要么放在本地磁盘其他位置,保证通过浏览器无法直接访问<br>
</body>
</html>
package com.hxuner.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UploadProgressServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*
文件上传进度2:UploadProgressServlet
负责接收客户端浏览器发来的AJAX请求,从Session作用域中获取当前文件上传的实际进度,返回给客户端浏览器
*/
//接收ajax请求,获取当前文件上传的进度
//两个Servlet共享数据,利用作用域session
//UploadServlet获取文件上传进度,存入session作用域,共享给UploadProgressServlet
//request.getSession().setAttribute("progress", progress+"%");
String progress=(String) request.getSession().getAttribute("progress");
//将当前进度返回给ajax
if(progress==null){
progress="0%";
}
//0% 1.11% 100.0%
response.getWriter().write(progress);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
2.添加商品用例
2.1 ManageAddProdServlet
package com.hxuner.backend.web;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.management.RuntimeErrorException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import com.hxuner.domain.Prod;
import com.hxuner.factory.BaseFactory;
import com.hxuner.service.ProdService;
/*
* ManageAddProdServlet:
* 1.接收请求,获取请求参数
* 1.1基于commons-fileupload处理请求
* 1.2普通表单参数,Map<String,String>->paramMap
* 1.3上传的商品图片,直接将图片存入WEB-INF/upload文件夹中
*
* 2.表单验证(略)
* 3.调用service执行逻辑
* 4.根据执行结果转发对应的视图
*
*/
public class ManageAddProdServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取ServletContext对象
ServletContext sc=this.getServletContext();
//获取项目配置的字符集编码
String encode=sc.getInitParameter("encode");
//1.获取请求参数
String uploadPath="/WEB-INF/upload";
String tempPath="/WEB-INF/temp";
// 用来临时保存所有请求参数的map集合
// key-参数的名称 value-参数的值
Map<String,String> paramMap=new HashMap<String, String>();
//获取FileItem工厂
//1.1构造工厂DiskFileItemFactory(FileItem的工厂)时,指定内存缓冲区大小和临时文件存放位置。
//int sizeThreshold 内存缓冲区的大小
//File repository 临时文件存放位置
DiskFileItemFactory factory=new DiskFileItemFactory(1024*1024, new File(sc.getRealPath(tempPath)));
//1.2使用DiskFileItemFactory 对象创建ServletFileUpload对象,进行通用配置。
ServletFileUpload fileUpload=new ServletFileUpload(factory);
//1.2.1判断当前表单是否是一个文件上传表单 enctype为multipart/form-data类型
//boolean isMultipartContent(HttpServletRequest request)
if(!fileUpload.isMultipartContent(request)){
throw new RuntimeException("请使用正确的文件上传表单");
}
//1.2.2设置单个文件的最大大小 setFileSizeMax(long fileSizeMax)
fileUpload.setFileSizeMax(1024*1024); //1MB
//1.2.3设置单次上传的所有文件的总大小设置 setSizeMax(long sizeMax)
fileUpload.setSizeMax(1024*1024*5); //5MB
//1.2.4---注意3:设置字符集,解决文件名的乱码问题---
fileUpload.setHeaderEncoding(encode);
//3.实际读取输入流中的内容,封装成FileItem(对表单中每一个input进行封装)
//List<FileItem> parseRequest(HttpServletRequest request)
try {
List<FileItem> list=fileUpload.parseRequest(request);
if(list!=null){
for(FileItem fileItem:list){
//判断当前FileItem是不是一个普通字段项 如果返回true表示这是一个普通字段项 返回false表示是一个文件上传项
//boolean isFormField()
if(fileItem.isFormField()){ //是普通的input输入框的内容
//如果是普通字段项
//String getFieldName() //获取字段项的名称
//String getString() //获取字段项的值
//String getString(String encode) //获取字段项的值
//获取input的name
String name=fileItem.getFieldName();
//获取input的value
//String value=fileItem.getString();
String value=fileItem.getString(encode); //---注意1:通知工具类使用utf-8进行解码,解决提交参数乱码问题---
//将参数存入map集合中
paramMap.put(name, value); //在循环
}else{
//是文件上传项
//String getName() //获取文件名
//InputStream getInputStream() //获取文件内容的流
//delete() //删除临时文件
//获取上传的文件的文件名
String fileName=fileItem.getName();
//-----------特别注意1 ie浏览器bug-------------
//ie浏览器的部分版本,在上传文件时,会使用文件的完整路径作为文件名
//a.txt c:users\administra\desktop\a.txt
if(fileName.contains("\\")){ //文件名不允许存在\
fileName=fileName.substring(fileName.lastIndexOf("\\")+1);//文件名从最后一个\+1截取到最后
}
//--------特别注意3 文件名重复-------
//多个上传名称相同时 文件会发生覆盖
//解决方案:应该想办法让文件名 尽量不要重复 - 在文件名的前面拼接UUID来保证文件名绝对不会重复
fileName=UUID.randomUUID()+"_"+fileName; //数据库里保存了该filename与用户的对应关系
//-------特别注意4:上传文件目录下文件过多----------
//一个文件夹下文件过多会造 访问缓慢,甚至有可能无法访问
//解决方案:所以应该想办法将这些文件分目录存储,利用文件名的hascode的16进制表示生成对应的目录。
String hsStr=Integer.toHexString(fileName.hashCode());
//补足8位
while(hsStr.length()<8){
hsStr="0"+hsStr;
}
//生成中间路径
String midPath="/";
for(int i=0;i<hsStr.length();i++){
midPath=midPath+hsStr.charAt(i)+"/";
}
// 获取图片的url,供用户后期访问该图片使用
// /WEB-INF/upload/a/b/c/d/e/1/2/3/UUID_name.jpg
String imgurl=uploadPath+midPath+fileName;
paramMap.put("imgurl", imgurl);
// 用本变量保存实际存储的路径
// sc.getRealPath方法返回的路径会去掉最后的 /
String savePath=sc.getRealPath(uploadPath+midPath);
// 在服务器上创建对应的文件夹
new File(savePath).mkdirs();
//获取上传的文件的输入流in->read
InputStream is=fileItem.getInputStream();
FileOutputStream fos=null;
try {
//输入流 out ->Write
fos=new FileOutputStream(savePath+"/"+fileName);
/*
* ----------- 特别注意2:文件上传保存位置问题---------------------
上传一个这个index.jsp到upload里,然后访问浏览器/upload/index.jsp就可以通过上传的jsp写的代码显示所有文件内容
所以不让用户通过浏览器直接访问其上传的文件。
文件上传保存的位置一定不能被外界直接访问 防止用户浏览器访问 下载资源 或执行jsp恶意代码
要么保存在WEB-INF下保护起来
要么放在本地磁盘其他位置,保证通过浏览器无法直接访问
*/
byte[] array=new byte[100];
int len=is.read(array);
while(len!=-1){
fos.write(array,0,len);
len=is.read(array);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}finally{
if(is!=null){
is.close();
}
if(fos!=null){
fos.close();
}
//---注意2:在关流之后,要注意删除临时文件,需要在关流之后调用---
fileItem.delete();
}
}
}
}
} catch (FileUploadException e) {
throw new RuntimeException(e.getMessage());
}
//2.表单验证(略)
//3.调用service执行逻辑
// 创建一个Prod实例,封装表单数据
Prod prod=new Prod();
// 将表单中获取到的数据添加到prod中
prod.setName(paramMap.get("name"));
prod.setPrice(Double.parseDouble(paramMap.get("price")));
prod.setCname(paramMap.get("cname"));
prod.setPnum(Integer.parseInt(paramMap.get("pnum")));
prod.setImgurl(paramMap.get("imgurl"));
prod.setDescription(paramMap.get("description"));
System.out.println(prod);//测试一下
ProdService service=BaseFactory.getFactory().getInstance(ProdService.class);
boolean flag=service.addProd(prod);
//4.根据执行的结果转发对应的视图
if(flag){
response.getWriter().write("上传成功");
response.setHeader("refresh", "2;url="+request.getContextPath()+"/backend/_right.jsp");
}else{
response.getWriter().write("上传失败");
response.setHeader("refresh", "2;url="+request.getContextPath()+"/backend/manageAddProd.jsp");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
2.2 interface ProdService
package com.hxuner.service;
import com.hxuner.domain.Prod;
public interface ProdService {
/**
* 添加商品的方法
* @param prod 封装了商品的JAVABean
* @return true-添加成功 false-添加失败
*/
boolean addProd(Prod prod);
}
ProdServiceImpl implements ProdService
package com.hxuner.service;
import com.hxuner.dao.ProdDao;
import com.hxuner.domain.Prod;
import com.hxuner.domain.ProdCategory;
import com.hxuner.exception.MsgException;
import com.hxuner.factory.BaseFactory;
public class ProdServiceImpl implements ProdService {
private ProdDao prodDao=BaseFactory.getFactory().getInstance(ProdDao.class);
//prod缺少了cid,表单提交只有cname,根据cname获取cid,写入prod使其完整。再插入数据库
@Override
public boolean addProd(Prod prod) {
//1.先使用cname查prob_category表,查看是否有数据
// 1.1有数据,返回id
// 1.2没有数据,先在prob_categroy表中添加一行数据
// 再进行一次查询获取cid
//2.使用cid给prob赋值
//3.添加商品信息到prob表
//1.先使用cname查prob_category表,查看是否有数据
ProdCategory pc=null;
//1.1有数据,返回id
try {
pc=prodDao.getPCByCname(prod.getCname());
} catch (MsgException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
//1.2没有数据,先在prob_categroy表中添加一行数据
if(pc==null){
//创建一个ProdCategory的对象,封装想数据库中插入的信息。
ProdCategory pc2=new ProdCategory(-1,prod.getCname());
boolean flag=prodDao.insertProdCategory(pc2);
if(!flag){
//商品种类添加失败,则商品也无法继续添加
return false;
}
//再进行一次查询获取cid
try {
pc=prodDao.getPCByCname(prod.getCname());
} catch (MsgException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//------------注意如果在这里写int i=10/0; 出现异常,数据库中插入了商品种类表,但是对应的商品未插入---
//2.使用cid给prob赋值
prod.setCid(pc.getId());
//3.添加商品信息到prob表
return prodDao.insertPord(prod);
}
}
2.3 interface ProdDao
package com.hxuner.dao;
import com.hxuner.domain.Prod;
import com.hxuner.domain.ProdCategory;
import com.hxuner.exception.MsgException;
public interface ProdDao {
/**
* 根据商品种类名称查询商品种类的方法 prob_category表根据cname查询cid,返回商品种类实体
* @param cname 商品种类名称
* @return 封装了商品种类信息的JavaBean 或 null
* @throws 封装了错误信息的异常对象
*/
ProdCategory getPCByCname(String cname) throws MsgException;
/**
* 向数据库添加商品种类的方法 prob_category表插入商品种类cname,id
* @param pc 封装了商品种类信息的JavaBean
* @return true-添加成功 false-添加失败
*/
boolean insertProdCategory(ProdCategory pc);
/**
* 向数据库内的商品表添加商品 prob表插入prob商品
* @param prod 封装了商品信息的JavaBean
* @return true-添加成功 false-添加失败
*/
boolean insertPord(Prod prod);
}
ProdDaoImpl implements ProdDao
package com.hxuner.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.hxuner.domain.Prod;
import com.hxuner.domain.ProdCategory;
import com.hxuner.exception.MsgException;
import com.hxuner.util.JDBCUtils;
//-------------------写一下!!!!-------------------
public class ProdDaoImpl implements ProdDao {
//根据商品种类名称查询商品种类的方法 prob_category表根据cname查询cid,返回商品种类实体
@Override
public ProdCategory getPCByCname(String cname) throws MsgException {
String sql="select * from prob_category where cname=?";
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=JDBCUtils.getConn();
ps=conn.prepareStatement(sql);
ps.setString(1, cname);
rs=ps.executeQuery();
if(rs.next()){
//如果查询到数据,则封装成pc对象,返回给Service
ProdCategory pc=new ProdCategory();
pc.setId(rs.getInt("id"));
pc.setCname(rs.getString("cname"));
return pc;
}
} catch (Exception e) {
e.printStackTrace();
throw new MsgException("添加商品种类出现异常");
}finally{
JDBCUtils.close(conn, ps, rs);
}
return null;
}
//向数据库添加商品种类的方法 prob_category表插入商品种类cname,id
@Override
public boolean insertProdCategory(ProdCategory pc) {
String sql="insert into prob_category values(null,?)";
Connection conn=null;
PreparedStatement ps=null;
try {
conn=JDBCUtils.getConn();
ps=conn.prepareStatement(sql);
ps.setString(1, pc.getCname());
int i=ps.executeUpdate();
if(i>0){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.close(conn, ps, null);
}
return false;
}
//向数据库内的商品表添加商品 prob表插入prob商品
@Override
public boolean insertPord(Prod prod) {
//cname商品种类名称,该字段不属于商品表,但是属于前台表单提交数据。因此添加属性来封装数据
//cname不存在在prod商品表中
String sql="insert into prob values(null,?,?,?,?,?,?)";
Connection conn=null;
PreparedStatement ps=null;
try {
conn=JDBCUtils.getConn();
ps=conn.prepareStatement(sql);
// String double int int String String
ps.setString(1, prod.getName());
ps.setDouble(2, prod.getPrice());
ps.setInt(3, prod.getCid());
ps.setInt(4, prod.getPnum());
ps.setString(5, prod.getImgurl());
ps.setString(6, prod.getDescription());
int i=ps.executeUpdate();
if(i>0){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.close(conn, ps, null);
}
return false;
}
}
4.prod商品
package com.hxuner.domain;
public class Prod {
private int id;
private String name;
private double price;
private int cid;
//商品种类名称,该字段不属于商品表,但是属于前台表单提交数据。因此添加属性来封装数据
private String cname;
private int pnum;
private String imgurl;
private String description;
public Prod(int id, String name, double price, int cid, String cname,
int pnum, String imgurl, String description) {
this.id = id;
this.name = name;
this.price = price;
this.cid = cid;
this.cname = cname;
this.pnum = pnum;
this.imgurl = imgurl;
this.description = description;
}
public Prod() {
// TODO Auto-generated constructor stub
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getCid() {
return cid;
}
public void setCid(int cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public int getPnum() {
return pnum;
}
public void setPnum(int pnum) {
this.pnum = pnum;
}
public String getImgurl() {
return imgurl;
}
public void setImgurl(String imgurl) {
this.imgurl = imgurl;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "Prod [id=" + id + ", name=" + name + ", price=" + price
+ ", cid=" + cid + ", cname=" + cname + ", pnum=" + pnum
+ ", imgurl=" + imgurl + ", description=" + description + "]";
}
}
prod_category
package com.hxuner.domain;
public class ProdCategory {
private int id;
private String cname;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public ProdCategory() {
}
public ProdCategory(int id, String cname) {
this.id = id;
this.cname = cname;
}
@Override
public String toString() {
return "ProdCategory [id=" + id + ", cname=" + cname + "]";
}
}
5.config.properties
ProdService=com.hxuner.service.ProdServiceImpl
ProdDao=com.hxuner.dao.ProdDaoImpl
2.事务
package com.hxuner.shiwu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
/*
事务
1.概念:事务指的是逻辑上的一组操作,要么都成功,要么都不成功
2.如何操作事务:
2.1数据库中
开启事务 start transaction;
事务提交 commit;
事务回滚 rollback;
2.2JDBC
开启事务 conn.setAutoCommit(false)
提交事务 conn.commit();
回滚事务 conn.rollback();
设定存档点 conn.setSavePoint();
回滚到存档点 conn.rollback(sp)
注意,回滚到存档点之后,还需要进行一次事务提交
3.事务的四大特性 - ACID
3.1原子性(Atomicity) - 事务中的一组操作是不可分割的一个整体,要么一起成功,要么一起失败。
3.2一致性(Consistency) - 事务前后 无论事务是否成功 数据库应该都保持一个完整性的状态。
数据库中数据完整性:数据库中数据 是业务完整 且约束完整的。
业务完整: 事务的所有操作之前,A账户+B账户是2000元,那么事务操作之后,A账户+B账户还应该是2000元
约束完整: 事务的所有操作结束后,后续即使再出现异常(rollback)不会破坏之前的约束
3.3隔离性(Isolation) - 多个并发事务之间应该互相隔离 互不影响
3.4持久性(Durability) - 一个事务成功 对数据库产生的影响是永久性的 无论发生什么情况 这种影响都不会被取消
4.如何保证事务的隔离性
1.加锁 绝对安全,但是效率低
2.提供了四大隔离级别,使使用者可以在安全性和效率之间进行取舍
read uncommitted 不提供任何隔离性,效率最高
出现脏读,不可重复读,幻读(虚读)问题
read committed 不能读取其他事务未提交的数据
可以解决脏读问题
出现不可重复读,幻读(虚读)问题
repeatable read 不能读取到其他事务已提交的数据
可以解决脏读、不可重复读的问题
会出现幻读(虚读)的问题
serializable 基于锁来实现
可以解决脏读、幻读、不可重复读
从安全性上考虑:
Serializable > Repeatable Read > Read Committed > Read uncommitted
从性能上考虑:
Read uncommitted > Read committed > Repeatable Read > Serializable
mysql数据库默认的隔离级别就是Repeatable Read 3
Oracle数据库默认的隔离级别是Read committed 2
5.隔离性可能造成的问题
5.1.脏读:
一个事务读取到另一个事务未提交的数据
----------------------------
a 1000
b 1000
----------------------------
a:
start transaction;
update account set money=money-100 where name=a;
update account set money=money+100 where name=b;
-----------------------------
b: //b读取到另一个事务未提交的数据
start transaction;
select * from account;
a 900
b 1100
commit;
-----------------------------
a:
rollback; //这里a进行回滚,
-----------------------------
b:
start transaction;
select * from account;
a 1000
b 1000
commit;
------------------------------
5.2.不可重复读:
一个事务多次读取数据库中的同一条记录,多次查询的结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
a 1000 1000 1000
------------------------------
b:
start transaction;
select 活期 from account where name='a'; --- 活期存款:1000元
select 定期 from account where name = 'a'; --- 定期存款:1000元
select 固定 from account where name = 'a'; --- 固定资产:1000元
---------------------------
a:
start transaction;
update account set 活期=活期-1000 where name= 'a';
commit;
---------------------------
注意:查询语句分开,中间有事务,读的是提交事务
select 活期+定期+固定 from account where name='a'; ---总资产:2000元
5.3.虚读(幻读)
有可能出现,有可能不出现:一个事务多次查询整表数据,多次查询时,由于有其他事务增删数据, 造成的查询结果不同(一个事务读取到另一个事务已经提交的数据)
------------------------------
a 1000
b 2000
------------------------------
d:
start transaction;
select sum(money) from account; --- 总存款3000元
select count(*) from account; --- 总账户数2个
注意:不可重复读:一直是1000,2000,看不到后续操作
-----------------
c:
start transaction;
insert into account values ('c',3000);
commit;
-----------------
注意:但是c增添了事务,而d不知道,还在增加insert into account values ('c',3000);,出现错误。
select avg(mone) from account; --- 平均每个账户:2000元
6.操作数据库的隔离级别
6.1查询数据库的隔离级别
select @@tx_isolation;
6.2修改数据库的隔离级别
set [session/global] transaction isolation level xxxxxx;
不写默认就是session,修改的是当前客户端和服务器交互时是使用的隔离级别,并不会影响其他客户端的隔离级别
如果写成global,修改的是数据库默认的隔离级别(即新开客户端时,默认的隔离级别),并不会修改当前客户端和已经开启的客户端的隔离级别
*/
public class TransactionDemo01 {
public static void main(String[] args) {
//1.加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//2.获取连接
String url="jdbc:mysql:///bigdata";
Connection conn=null;
Statement st=null;
//存档点
Savepoint sp=null;
try {
conn=DriverManager.getConnection(url,"root","root");
//1.开启事务
//关闭自动连接后,conn将不会帮我们提交事务,在这个连接上执行的所有sql语句将处在同一事务中,需要我们是手动的进行提交或回滚
conn.setAutoCommit(false);
st=conn.createStatement();
st.executeUpdate("update account set money=money-1000 where name='a'");
st.executeUpdate("update account set money=money+1000 where name='b'");
//设置存档点,即使下面出现异常,上面两条sql语句也要执行
sp = conn.setSavepoint();
st.executeUpdate("update account set money=money-1000 where name='a'");
int i=10/0;
st.executeUpdate("update account set money=money+1000 where name='b'");
//2.提交事务
conn.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
//3.出现异常,事务的回滚
try {
//conn.rollback();
if(sp!=null){
//回滚到存档点
conn.rollback(sp);
//注意,回到回滚点后,回滚点之前的代码虽然没被回滚但是也没提交呢,如果想起作用还要做commit操作.
conn.commit();
}else{
conn.rollback();
}
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}finally{
if(st!=null){
try {
st.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
st=null;
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
conn=null;
}
}
}
}
}