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