JAVA WEB---文件上传进度

 

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;
				}
			}
		}
	}
}

猜你喜欢

转载自blog.csdn.net/WuYunCode/article/details/82083081