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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章