大数据正式23
文件上传的进度显示
- 效果
- 流程图
- 一次访问(ajax访问)
- 实时更新(window的定时器)
- 一次访问(ajax访问)
- 流程
- 在页面加载后为form表单提交后触发方法
- 注册一个定时器,每隔一秒钟通过ajax访问服务器获取当前上传的进度
- 将最新的百分比显示在页面上
- 技术选型
- jQuery版的ajax【get方式:例子】
$.get("url", function(data){ alert("返回的数据:" + data); });
- jQuery版的定时器
1. everyTime(时间间隔, [计时器名称], 函式名称, [次数限制], [等待函式程序完成]) 2. oneTime(时间间隔, [计时器名称], 呼叫的函式) 3. stopTime ([计时器名称], [函式名称])
- Apache下的jar包:commons-fileupload包、commons-io包
- Session传递进度数据
注意:首次获取的对象可能为null【ajax的速度 > servlet产生进度的速度(首次获取进度的时候)】
- jQuery版的ajax【get方式:例子】
-
用到的js文件
- jquery-1.4.2.js(这个jQuery的js文件即可)
-
jQuery_timer.js(这个不好下载,这里给出代码)
/** * jQuery.timers - Timer abstractions for jQuery * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com) * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/). * Date: 2009/10/16 * * @author Blair Mitchelmore * @version 1.2 * **/ jQuery.fn.extend({ everyTime: function(interval, label, fn, times) { return this.each(function() { jQuery.timer.add(this, interval, label, fn, times); }); }, oneTime: function(interval, label, fn) { return this.each(function() { jQuery.timer.add(this, interval, label, fn, 1); }); }, stopTime: function(label, fn) { return this.each(function() { jQuery.timer.remove(this, label, fn); }); } }); jQuery.extend({ timer: { global: [], guid: 1, dataKey: "jQuery.timer", regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/, powers: { // Yeah this is major overkill... 'ms': 1, 'cs': 10, 'ds': 100, 's': 1000, 'das': 10000, 'hs': 100000, 'ks': 1000000 }, timeParse: function(value) { if (value == undefined || value == null) return null; var result = this.regex.exec(jQuery.trim(value.toString())); if (result[2]) { var num = parseFloat(result[1]); var mult = this.powers[result[2]] || 1; return num * mult; } else { return value; } }, add: function(element, interval, label, fn, times) { var counter = 0; if (jQuery.isFunction(label)) { if (!times) times = fn; fn = label; label = interval; } interval = jQuery.timer.timeParse(interval); if (typeof interval != 'number' || isNaN(interval) || interval < 0) return; if (typeof times != 'number' || isNaN(times) || times < 0) times = 0; times = times || 0; var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {}); if (!timers[label]) timers[label] = {}; fn.timerID = fn.timerID || this.guid++; var handler = function() { if ((++counter > times && times !== 0) || fn.call(element, counter) === false) jQuery.timer.remove(element, label, fn); }; handler.timerID = fn.timerID; if (!timers[label][fn.timerID]) timers[label][fn.timerID] = window.setInterval(handler,interval); this.global.push( element ); }, remove: function(element, label, fn) { var timers = jQuery.data(element, this.dataKey), ret; if ( timers ) { if (!label) { for ( label in timers ) this.remove(element, label, fn); } else if ( timers[label] ) { if ( fn ) { if ( fn.timerID ) { window.clearInterval(timers[label][fn.timerID]); delete timers[label][fn.timerID]; } } else { for ( var fn in timers[label] ) { window.clearInterval(timers[label][fn]); delete timers[label][fn]; } } for ( ret in timers[label] ) break; if ( !ret ) { ret = null; delete timers[label]; } } for ( ret in timers ) break; if ( !ret ) jQuery.removeData(element, this.dataKey); } } } }); jQuery(window).bind("unload", function() { jQuery.each(jQuery.timer.global, function(index, item) { jQuery.timer.remove(item); }); });
-
代码实现
-
页面代码
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>文件上传</title> <style type="text/css"> #div_out { margin-top: 1%; width: 50%; height: 3%; background-color: #ff0000; width: 50%; } #div_in { width: 0%; height: 100%; background-color: #00ff00; } </style> <script type="text/javascript" src="js/jquery-1.4.2.js"></script> <script type="text/javascript" src="js/jQuery_timer.js"></script> <script type="text/javascript"> $(document).ready(function() { //表单提交事件 $("form").submit(function() { //定时器 //每10毫秒执行 $("form").everyTime('1ms', function() { //访问当前进度的servlet $.get("getProgressServlet", function(data) { //设置div的进度为当前文件的上传进度 $("#div_in").width(data); }); }); }); }); </script> </head> <body bgcolor="#ff00ff" text="#00ffff"> <center> <h1>name:</h1> <input type="text" name="name" /> <h1>请选择文件:</h1> <form action="FileUpServlet" method="post" enctype="multipart/form-data"> <input type="file" name="file" /> <input type="submit" value="上传文件" /> <div id="div_out"> <div id="div_in"></div> </div> </form> </center> </body> </html>
-
文件上传处理Servlet(实验--把文件大小的设置给取消了)
package com.peng.ser; 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.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.ProgressListener; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; public class FileUpServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } /* 上传文件逻辑 */ public void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 得到上传文件的保存目录,将上传的文件存放于WEB-INF目录下,不允许外界直接访问,保证上传文件的安全 String savePath = this.getServletContext().getRealPath( "/WEB-INF/upload"); File file = new File(savePath); // 判断上传文件的保存目录是否存在 if (!file.exists() && !file.isDirectory()) { System.out.println(savePath + "目录不存在,需要创建"); // 创建目录 file.mkdir(); } // 消息提示 String message = ""; try { // 使用Apache文件上传组件处理文件上传步骤: // 1、创建一个DiskFileItemFactory工厂 DiskFileItemFactory factory = new DiskFileItemFactory(); // 2、创建一个文件上传解析器 ServletFileUpload upload = new ServletFileUpload(factory); // 解决上传文件名的中文乱码 upload.setHeaderEncoding("UTF-8"); // 设置文件的大小 // upload.setFileSizeMax(2 * 1024 * 102);// 设置单个文件的最大值 // upload.setSizeMax(50 * 1024 * 1024);// 设置总的文件的总的大小 upload.setProgressListener(new ProgressListener() { public void update(long pBytesRead, long pContentLength, int pItems) { request.getSession() .setAttribute( "persent", Math.round(pBytesRead * 10000.0 / pContentLength) / 100); } }); // 3、判断提交上来的数据是否是上传表单的数据 if (!ServletFileUpload.isMultipartContent(request)) { // 按照传统方式获取数据 return; } // 4、使用ServletFileUpload解析器解析上传数据,解析结果返回的是一个List<FileItem>集合,每一个FileItem对应一个Form表单的输入项 List<FileItem> list = upload.parseRequest(request); for (FileItem item : list) { // 如果fileitem中封装的是普通输入项的数据 if (item.isFormField()) { String name = item.getFieldName(); // 解决普通输入项的数据的中文乱码问题 String value = item.getString("UTF-8"); // value = new String(value.getBytes("iso8859-1"),"UTF-8"); System.out.println(name + "=" + value); } else {// 如果fileitem中封装的是上传文件 // 得到上传的文件名称, String filename = item.getName(); System.out.println(filename); if (filename == null || filename.trim().equals("")) { continue; } // 注意:不同的浏览器提交的文件名是不一样的,有些浏览器提交上来的文件名是带有路径的,如: // c:\a\b\1.txt,而有些只是单纯的文件名,如:1.txt // 处理获取到的上传文件的文件名的路径部分,只保留文件名部分 filename = filename .substring(filename.lastIndexOf("\\") + 1); // 获取文件名的UUID String uuid_fileName = UUID.randomUUID().toString() + "_" + filename; // 哈希目录--利用文件的名称生成8层文件夹 String hashCod_8 = Integer.toHexString(uuid_fileName .hashCode()); while (hashCod_8.length() < 8) { hashCod_8 = "0" + hashCod_8; } for (int i = 0; i < hashCod_8.length(); i++) { savePath += "\\" + hashCod_8.charAt(i); } File dir = new File(savePath); dir.mkdirs(); // 获取item中的上传文件的输入流 InputStream in = item.getInputStream(); // 创建一个文件输出流 FileOutputStream out = new FileOutputStream(savePath + "\\" + uuid_fileName); // 创建一个缓冲区 byte buffer[] = new byte[1024]; // 判断输入流中的数据是否已经读完的标识 int len = 0; // 循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 while ((len = in.read(buffer)) != -1) { // 使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + "\\" // + filename)当中 out.write(buffer, 0, len); } // 关闭输入流 in.close(); // 关闭输出流 out.close(); // 删除处理文件上传时生成的临时文件 item.delete(); System.out.println("文件上传成功!"); } } } catch (Exception e) { message = "文件上传失败!"; e.printStackTrace(); } } }
-
ajax获取进度交互Servlet
package com.peng.ser; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; public class getProgressServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String data = request.getSession().getAttribute("persent") == null ? "0%" : request.getSession().getAttribute("persent") + "%"; System.out.println(data); response.getWriter().write(data); } }
-
EasyMall功能继续实现--商品模块
- 设计商品相关的表
- 商品种类表【prod_category】
- 商品种类编号【id】
- 商品种类名称【category】
- 商品表【prod】
- 商品编号【id】
- 商品名称【name】
- 商品种类【prod_category_id】
- 商品单价【price】
- 商品库存【pnum】
- 商品图片imgurl
- 商品描述【description】
- 商品种类表【prod_category】
- 商品添加
- 后台管理--商品管理--商品添加--提供商品添加--提供商品的简单信息--上传商品的图片
- 逻辑
- 上传商品图片
- 将商品信息存入数据库
-
代码
-
后台管理界面
-
backend
-
_left.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML> <html> <head> <style type="text/css"> body{ background-color: #6394EF; text-align: center; } </style> </head> <body> <a href="manageAddProd.jsp" target="right_frame">商品添加</a> </body> </html>
-
_right.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML> <html> <head> </head> <body> EasyMall后台管理 </body> </html>
-
_top.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML> <html> <head> <style type="text/css"> body{ background-color: #4284B5; } </style> </head> <body> EasyMall后台管理平台! </body> </html>
-
manage.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML> <html> <head> </head> <frameset rows="10%,90%"> <frame src=_top.jsp " noresize="noresize" frameborder="0"></frame> <frameset cols="10%,90%"> <frame src="_left.jsp" noresize="noresize" frameborder="0"></frame> <frame src="_right.jsp" noresize="noresize" frameborder="0" name="right_frame"></frame> </frameset> </frameset> <body> </body> </html>
-
manageAddProd.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%> <!DOCTYPE HTML> <html> <head> <style type="text/css"> h1{ text-align: center; } </style> </head> <body> <h1>EasyMall_添加商品</h1> <hr> <form action="../ManageAddProductServlet" method="POST" enctype="multipart/form-data"> <table align="center" border="1" cellspacing="0px" cellpadding="5px"> <tr> <td>商品名称</td> <td><input type="text" name="name"/></td> </tr> <tr> <td>商品单价</td> <td><input type="text" name="price"/></td> </tr> <tr> <td>商品种类</td> <td><input type="text" name="cname"/></td> </tr> <tr> <td>库存数量</td> <td><input type="text" name="pnum"/></td> </tr> <tr> <td>商品图片</td> <td><input type="file" name="fx"/></td> </tr> <tr> <td>描述信息</td> <td> <textarea rows="5" cols="30" name="description"></textarea> </td> </tr> <tr> <td colspan="2"><input type="submit" value="添加商品"/></td> </tr> </table> </form> <hr> </body> </html>
-
-
-
后台逻辑
-
ManageAddProductServlet
package com.easymall.web.backend; 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.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.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import com.easymall.domain.Prod; import com.easymall.domain.ProdCategory; import com.easymall.exception.MsgException; import com.easymall.factory.BasicFactory; import com.easymall.service.ProdService; @SuppressWarnings("serial") public class ManageAddProductServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { this.doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 存放文件上传文件的目录 String savePath = request.getContextPath() + "WEB-INF\\upload"; String tempPath = request.getContextPath() + "WEB-INF\\temp"; // 存放基本信息的键值对 Map<String, Object> basicInfo = new HashMap<String, Object>(); // 获取web应用对象 ServletContext servletContext = this.getServletContext(); // 获取文件上传工厂 DiskFileItemFactory dfif = new DiskFileItemFactory(100, new File( tempPath)); // 获取核心类 ServletFileUpload sfu = new ServletFileUpload(dfif); // 判断是否是multipart/form-data文件上传的方式 if (sfu.isMultipartContent(request)) { // 解决文件名的乱码 sfu.setHeaderEncoding(servletContext.getInitParameter("encode")); // 设定总文件的上限 sfu.setSizeMax(10 * 1024 * 1024); // 设定单个文件的上限 sfu.setFileSizeMax(1024 * 1024); // 获取文件列表 List<FileItem> parseRequest = sfu.parseRequest(request); for (FileItem fileItem : parseRequest) { if (fileItem.isFormField()) { // 普通输入框的处理--将键值对存入map中 basicInfo.put(fileItem.getFieldName(), fileItem.getString("utf-8")); } else { // 文件的上传处理 // ---防止IE名称出错的处理(切分) String fileName = fileItem.getName(); if (fileName.contains("\\")) { fileName = fileName.substring(fileName .lastIndexOf("\\") + 1); } // ---文件重名处理(UUID fileName = UUID.randomUUID().toString() + "_" + fileName; int fileHashCode = fileName.hashCode(); String hexString = Integer.toHexString(fileHashCode); while (hexString.length() < 8) { hexString = "0" + hexString; } for (int i = 0; i < hexString.length(); i++) { savePath += "\\" + hexString.charAt(i); } File tempDir = new File(this.getServletContext() .getRealPath(savePath)); tempDir.mkdirs(); // ---哈希分目录存储上传的文件(hashCode) // 获取item中的上传文件的输入流 InputStream in = fileItem.getInputStream(); // 创建一个文件输出流 FileOutputStream out = new FileOutputStream(new File( this.getServletContext().getRealPath(savePath), fileName)); // 创建一个缓冲区 byte buffer[] = new byte[1024]; // 判断输入流中的数据是否已经读完的标识 int len = 0; // 循环将输入流读入到缓冲区当中,(len=in.read(buffer))>0就表示in里面还有数据 while ((len = in.read(buffer)) != -1) { // 使用FileOutputStream输出流将缓冲区的数据写入到指定的目录(savePath + // "\\" // + filename)当中 out.write(buffer, 0, len); } // 将文件的全路径存入basicInfo basicInfo.put("imgurl", savePath + "\\" + fileName); // 关闭输入流 in.close(); // 关闭输出流 out.close(); // 删除临时文件 } fileItem.delete(); } // 将普通的信息和图片存放的路径存入数据库 ProdService prodService = BasicFactory.getFactory().getObj( ProdService.class); // 存放产品信息 Prod prod = new Prod(); prod.setImgurl((String) (basicInfo.get("imgurl"))); prod.setName((String) basicInfo.get("name")); prod.setPnum(Integer.parseInt((String) basicInfo.get("pnum"))); prod.setPrice(Double.parseDouble((String) basicInfo .get("price"))); prod.setProdCategory(new ProdCategory(null, (String) basicInfo .get("cname")));// 获取商品种类 prod.setDescription((String) basicInfo.get("description")); prodService.addProd(prod); // 成功--定时刷新到相应的界面 response.getWriter().write("添加成功!"); response.setHeader("refresh", "2;url=backend/_right.jsp"); } } catch (MsgException e) { // 在适当的页面展示相应的提示信息 } catch (Exception e) { throw new RuntimeException(e); } } }
-
ProdService
package com.easymall.service; import com.easymall.domain.Prod; public interface ProdService { /** * 添加一个产品 * * @param prod */ public void addProd(Prod prod); }
-
ProdServiceImpl
package com.easymall.service; import com.easymall.dao.ProdDao; import com.easymall.domain.Prod; import com.easymall.domain.ProdCategory; import com.easymall.factory.BasicFactory; public class ProdServiceImpl implements ProdService { private ProdDao prodDao = BasicFactory.getFactory().getObj(ProdDao.class); public void addProd(Prod prod) { String category_id = "-666"; // 检查商品种类是否存在 ProdCategory category = prodDao.findProdCategoryByCategoryName(prod .getProdCategory().getName()); if (category == null) { // 商品种类不存在--则先添加商品种类,然后添加商品 category = prodDao.addCategory(prod.getProdCategory().getName());// 返回值的作用是为了后期容易得到商品种类的ID值 } // 获取商品种类的ID category_id = category.getId(); // 商品种类存在,添加商品 prodDao.addProd(prod, category_id); } }
-
ProdDao
package com.easymall.dao; import com.easymall.domain.Prod; import com.easymall.domain.ProdCategory; public interface ProdDao { /** * 通过商品种类名称查询数据库是否有此种类 * * @param string * @return 找到了? 商品种类的对象:null; */ ProdCategory findProdCategoryByCategoryName(String string); /** * 添加一个商品种类 * * @param category * @return 添加成功对应的商品种类 */ ProdCategory addCategory(String category_name); /** * 添加商品 * * @param prod * 商品 * @param category_id * 商品种类的ID * @return void */ void addProd(Prod prod, String category_id); }
-
ProdDaoImpl
package com.easymall.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import com.easymall.domain.Prod; import com.easymall.domain.ProdCategory; import com.easymall.utils.MySqlUtils; public class ProdDaoImpl implements ProdDao { Connection conn = null; PreparedStatement stat = null; ResultSet rs = null; public ProdCategory findProdCategoryByCategoryName(String name) { try { ProdCategory pc = null; conn = MySqlUtils.getConn(); stat = conn .prepareStatement("select * from prod_category where name=?"); stat.setString(1, name); rs = stat.executeQuery(); while (rs.next()) {// 一个对象这里才对----------嘻嘻 pc = new ProdCategory(); pc.setId(rs.getString("id")); pc.setName(rs.getString("name")); } return pc; } catch (Exception e) { throw new RuntimeException(e); } finally { MySqlUtils.close(conn, stat, rs); } } public ProdCategory addCategory(String category_name) { try { conn = MySqlUtils.getConn(); conn.setAutoCommit(false);// 设置不自动提交 stat = conn .prepareStatement("insert into prod_category(name) values(?)"); stat.setString(1, category_name); stat.executeUpdate(); conn.commit();// 提交事务 ProdCategory pc = null; stat = conn .prepareStatement("select * from prod_category where name=? "); stat.setString(1, category_name); rs = stat.executeQuery(); while (rs.next()) { pc = new ProdCategory(); pc.setId(rs.getString("id")); pc.setName(rs.getString("name")); } return pc; } catch (Exception e) { throw new RuntimeException(e); } finally { MySqlUtils.close(conn, stat, rs); } } public void addProd(Prod prod, String category_id) { try { conn = MySqlUtils.getConn(); stat = conn .prepareStatement("insert into prod(name,price,pnum,img_url,description,prodCategory_id) values(?,?,?,?,?,?)"); stat.setString(1, prod.getName()); stat.setDouble(2, prod.getPrice()); stat.setInt(3, prod.getPnum()); stat.setString(4, prod.getImgurl()); stat.setString(5, prod.getDescription()); stat.setInt(6, Integer.parseInt(category_id)); stat.executeUpdate(); } catch (Exception e) { throw new RuntimeException(e); } finally { MySqlUtils.close(conn, stat, rs); } } }
-
Prod
package com.easymall.domain; public class Prod { private int id; private String name; private double price; private int pnum; private String imgurl; private int prodCategory_id; private ProdCategory prodCategory; private String description; public Prod() { super(); } public Prod(int id, String name, double price, int pnum, String imgurl, int prodCategory_id, ProdCategory prodCategory, String description) { super(); this.id = id; this.name = name; this.price = price; this.pnum = pnum; this.imgurl = imgurl; this.prodCategory_id = prodCategory_id; this.prodCategory = prodCategory; this.description = description; } public int getProdCategory_id() { return prodCategory_id; } public void setProdCategory_id(int prodCategory_id) { this.prodCategory_id = prodCategory_id; } 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 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 ProdCategory getProdCategory() { return prodCategory; } public void setProdCategory(ProdCategory prodCategory) { this.prodCategory = prodCategory; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
-
ProdCategory
package com.easymall.domain; public class ProdCategory { private String id; private String name; public ProdCategory() { super(); } public ProdCategory(String id, String name) { super(); this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
-
配置文件
- config.properties
UserService=com.easymall.service.UserServiceImpl UserDao=com.easymall.dao.UserDaoImpl ProdService=com.easymall.service.ProdServiceImpl ProdDao=com.easymall.dao.ProdDaoImpl
- config.properties
-
-
数据库easymall
- prod
CREATE TABLE `prod` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '产品的id', `name` varchar(64) NOT NULL COMMENT '产品的名称', `price` double(4,0) NOT NULL COMMENT '产品的价格', `pnum` int(8) NOT NULL COMMENT '品产的总量', `img_url` varchar(128) DEFAULT NULL COMMENT '产品的图片的地址', `prodCategory_id` int(8) NOT NULL COMMENT '产品的种类的id', `description` varchar(256) NOT NULL COMMENT '产品的描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
- prod_category
CREATE TABLE `prod_category` ( `id` int(8) NOT NULL AUTO_INCREMENT COMMENT '类种编号', `name` varchar(64) NOT NULL DEFAULT '' COMMENT '种类名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8
- prod
难点--事务
- 数据存储没完成的时候就挂掉了
- 数据库操作时没有全部执行完就给断掉了
- 程序bug
- 断电
- 事务
- 概念:事务是逻辑上的一组操作,要求这一组操作要么同时完成,要么同时不完成
- 关系型数据等,可以保证事务特性
- 数据库的事务
- 银行的转账
1. 转:update account set money-100 where name='person1'; 2. 接:update account set money+100 where name='person2';
- 银行的转账
- 数据库的事务
- 事务的操作
- 数据库手动控制事务
- start transaction;#开始事务
- sql的操作
- commit;#提交事务,提交后,才能对所有的sql才能立即起作用
- rollback;#回滚操作,所有的操作都废弃掉,恢复事务之前的操作
- 银行的转账
start transaction; 1. 转:update account set money-100 where name='person1'; 2. 接:update account set money+100 where name='person2'; commit;
- 银行的转账
- JDBC控制事务
- conn.setAutoCommit(false);#关闭自动提交-----即开启了事务
- conn.commit();#提交事务
- conn.rollBack();#事务回滚
- 事务存储点
- SavePoint sp=conn.setSavepoint();
- 判断sp是否为null并执行相应的操作
捕获到异常{ if(null==sp){ //全部回滚 conn.rollBack(); }else{ //局部回滚 conn.rollBack(sp); } }
- 数据库手动控制事务
事务的四大特性【ACID】
- 原子性【Atomicity】
- 事务中的一组操作是不可分割的整体操作,要么一起成功,要么一起失败
- 一致性【Consistency】
- 事务前后,无论事务成功,数据库都应该保持一个完整性的状态
- 一致性状态到另外一个一致性状态
- 数据库的完整性:数据是业务完整且约束完整的
- 业务完整性:逻辑操作对数据库的操作完都是符合生活的实际
- 数据库约束:主键约束+外键约束+非空约束+唯一性约束+。。。+
- 隔离性【Isolation】
- 多个并发是事务,应该相互隔离,互不影响,互不干扰
- 持久性【Durability】
- 一个事务的成功,对数据的影响是永久性的,无论发生什么情况,这种影响是不会被取消的
- 即使系统遇到故障的情况下也不会丢失事务的操作
讨论一下隔离性
- 问题
- 产生的本质:对同一个数据的同时操作需要选择性隔离
- 问题一:脏读
- 脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
- 问题二:不可重复读
- 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
- 问题三:幻读(虚读)
- 幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
- 四大隔离级别
- read-uncommitted
- 不保证任何的事务特性可能出现:
- 脏读【一个事务读取到了另一个事务未提交的数据】
- 不可重复读【不可读已提交的数据:一条记录】
- 幻读【一个事务读取全表,读到别人修改后的数据:一张整表】
- 脏读【一个事务读取到了另一个事务未提交的数据】
- 不保证任何的事务特性可能出现:
- read-committed
- 保证部分的隔离,可以防止读脏数据但是有下列问题:
- 不可重复读【不可读已提交的数据】
- 幻读【【一个事务读取全表,读到别人修改后的数据:一张整表----有可能出现,概率很低】
- 不可重复读【不可读已提交的数据】
- 保证部分的隔离,可以防止读脏数据但是有下列问题:
- Repeatable-read
- 保证部分的隔离,可以防止读脏数据和重复读问题但是有下列问题:
- 幻读【【一个事务读取全表,读到别人修改后的数据:一张整表----有可能出现,概率很低】
- 幻读【【一个事务读取全表,读到别人修改后的数据:一张整表----有可能出现,概率很低】
- 保证部分的隔离,可以防止读脏数据和重复读问题但是有下列问题:
- Serializable
- 保证完全隔离,利用锁机制
- read-uncommitted
- 完美解决:锁机制
- 但是:数据库相当于工作在单线程的地方,只能供一个人使用,浪费
- 看情况解决:
- 生活中,并不是所有的操作都要求完全的隔离,可以灵活的运用,需要在性能和隔离能力做出权衡
- 查询的时候--没必要隔离
- 重要的数据增删改--有必要隔离
- 生活中,并不是所有的操作都要求完全的隔离,可以灵活的运用,需要在性能和隔离能力做出权衡
隔离级别 | 解释 |
---|---|
Serializable (串行化) | 可避免脏读、不可重复读、幻读的发生 |
Repeatable read (可重复读) | 可避免脏读、不可重复读的发生 |
Read committed (读已提交) | 可避免脏读的发生 |
Read uncommitted (读未提交) | 最低级别,任何情况都无法保证 |
- 设置数据库隔离的隔离级别
- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
- SESSION本客户端
- GLOBAL全局
- 记住:设置数据库的隔离级别一定要是在开启事务之前!
- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
- 查看数据库的隔离级别
- select @@tx_isolation;
- JDBC中设置隔离级别
- 例子
conn.setTransactionIsolation(Connection.常量);
- 注意事项
- 隔离级别的设置只对当前链接有效。对于使用MySQL命令窗口而言,一个窗口就相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC操作数据库来说,一个Connection对象相当于一个链接,而对于Connection对象设置的隔离级别只对该Connection对象有效,与其他链接Connection对象无关。