1.事務
1.我們可以具體分析下隔離性產生的細節:
如果兩個線程併發修改,必然產生多線程併發安全問題,必須隔離開
如果兩個線程併發查詢,必然沒有問題,不需要隔離
如果一個線程修改,一個線程查詢,在不同的應用場景下有可能有問題,有可能沒問題。安全性要求高->互斥,安全性要求不高->不互斥
2.爲了實現上面描述的情況,數據庫設計者提供了兩種鎖
2.1共享鎖 共享鎖和共享鎖可以共存,共享鎖和排他鎖不能共存
2.2排它鎖 排他鎖和共享鎖不能共存,排他鎖和排他鎖也不能共存
2.3可能的死鎖 當兩邊都是Serializable隔離級別時
兩邊都先進行查詢 再嘗試進行修改 則互相等待對方釋放共享鎖 都無法接着執行 造成了死鎖
死鎖的解決有兩種辦法:避免死鎖 解決死鎖
mysql沒有避免死鎖 嘗試檢測死鎖 發現死鎖後,如果有一方已經阻塞,而另一方的操作會導致死鎖,另一方rollback
3.鎖與數據庫操作的關係
3.1非Serializable隔離級別下做查詢不加任何鎖
3.2在Serializable隔離級別下做查詢加共享鎖
3.3任何級別下,增刪改查都加排他鎖
注意:不加鎖的操作和任何鎖都不互斥。
4.
a(非Serializable) b(非Serializable) 互斥 原因
查詢 查詢 不互斥 a:不加鎖 b:不加鎖
修改 查詢 不互斥 a:排他鎖 b:不加鎖
查詢 修改 不互斥 a:不加鎖 b:排他鎖
修改 修改 互斥 a:排他鎖 b:排他鎖
注意:兩個都不是Serializable,互斥一個
兩個都不是Serializable,僅在兩個都修改的時候互斥。
a(Serializable) b(非Serializable) 互斥 原因
查詢 查詢 不互斥 a:共享鎖 b:不加鎖
修改 查詢 不互斥 a:排他鎖 b:不加鎖
查詢 修改 互斥 a:共享鎖 b:排他鎖
修改 修改 互斥 a:排他鎖 b:排他鎖
注意:兩個一個是Serializable,一個不是Serializable,互斥兩個
兩個一個是Serializable,一個不是Serializable, 當兩個都在修改和 Serializable查詢,不是Serializable修改的時候互斥
a(Serializable) b(Serializable) 互斥 原因
查詢 查詢 不互斥 a:共享鎖 b:共享鎖
修改 查詢 互斥 a:排他鎖 b:共享鎖
查詢 修改 互斥 a:共享鎖 b:排他鎖
修改 修改 互斥 a:排他鎖 b:排他鎖
注意:兩個都是Serializable,互斥三個
兩個都是Serializable,僅在兩個查詢的時候不互斥。
2.ThreadLocal
package com.hxuner.thread;
//模擬連接對象
public class Myconn {
}
package com.hxuner.thread;
public class MyRunn1 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
ThreadLocal01.getInstance().startTransaction(); //當前線程開啓事務
try {
Thread.sleep(2000); //線程阻塞2秒,模擬進行其他操作
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ThreadLocal01.getInstance().commit(); //當前線程提交事務
}
}
package com.hxuner.thread;
import java.sql.Connection;
import java.sql.SQLException;
public class ThreadLocal01 {
//單例
//1.私有的構造器
private ThreadLocal01(){}
//2.私有的靜態的本類唯一實例
private static ThreadLocal01 tm=new ThreadLocal01();
//3.公有的靜態的返回本類唯一實例的方法
public static ThreadLocal01 getInstance(){
return tm;
}
//唯一的連接對象conn
private Myconn conn; //多個線程操作會出現問題 ,
//第一個線程調用這個方法,獲取連接對象@123
//第二個線程也調用這個方法,獲取連接對象@789
//由於只有一個,整個conn都變爲@789,而線程1不知道,線程1和線程2提交的都是@789,出現併發問題
/*
出現問題的原因:
單例模式+唯一的實例, 由於是唯一的引用,多線程進來會改
解決:
ThreadLocal:利用線程本地變量來解決
*/
/**
* 開啓事務startTransaction()
*/
public void startTransaction(){
conn=new Myconn(); //開啓事務的時候獲取唯一的連接池對象
System.out.println("線程"+Thread.currentThread().getName()+"開啓事務,使用的連接對象是"+conn);
}
/**
* 提交事務commit()
*/
public void commit() {
System.out.println("線程"+Thread.currentThread().getName()+"提交了事務,使用的連接對象是"+conn);
}
}
package com.hxuner.thread;
public class zTest {
public static void main(String[] args) {
/*
只需要Myconn()連接對象,MyRunn1線程,ThreadLocal01的conn管理類(唯一的連接對象conn private Myconn conn;)
Thread t1=new Thread(new MyRunn1());
Thread t2=new Thread(new MyRunn1());
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
輸出:
線程Thread-0開啓事務,使用的連接對象是com.hxuner.thread.Myconn@55f33675
線程Thread-1開啓事務,使用的連接對象是com.hxuner.thread.Myconn@525483cd
線程Thread-0提交了事務,使用的連接對象是com.hxuner.thread.Myconn@525483cd
線程Thread-1提交了事務,使用的連接對象是com.hxuner.thread.Myconn@525483cd
原因:
由於只有一個,整個conn都變爲@525483cd,而線程1不知道,線程1和線程2提交的都是@525483cd,出現併發問題
*/
}
}
----------------------------------------------------ThreadLocal------------------------------------------------------------------------------
package com.hxuner.thread;
//模擬連接對象
public class Myconn {
}
package com.hxuner.thread;
public class MyRunn2 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
ThreadLocal02.getInstance().startTransaction(); //當前線程開啓事務
try {
Thread.sleep(2000); //線程阻塞2秒,模擬進行其他操作
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ThreadLocal02.getInstance().commit(); //當前線程提交事務
}
}
package com.hxuner.thread;
import java.sql.Connection;
import java.sql.SQLException;
/*
* 1.ThreadLocal:線程本地變量
1.1Thread類中有一個map集合,是一個實例成員
1.2每一個線程對象有一個屬於自己的map集合,每個線程如果希望使用自己的變量,可以將變量存入自己的那個map集合中。
2.線程併發問題:存在於多個線程併發操作同一個變量(實例成員)
解決:
通過線程本地變量,每個線程操作的是保存在自身的map集合中的變量
所以多線程併發操作的不再是同一個變量,進而解決了線程安全問題。
但是!!!,線程Thread自身的那個map集合,不能通過線程對象直接訪問。
只能通過Java提供的一個工具類來訪問:ThreadLocal
3.ThreadLocal
3.1概念
---------------------線程對象有一個自己的Map集合ThreadLocalMap,Map集合ThreadLocalMap存放{ThreadLocal,value}鍵值對----------------------------------------
ThreadLocal是用來操作每個線程對象自己的那個Map集合的工具類
每個線程對象,都有一個實例成員 ThreadLocalMap
ThreadLocalMap,是一個特殊的Map集合,專門用於保存線程的本地變量,該集合不能通過線程對象來直接訪問,只能通過ThreadLocal來訪問
3.2API
3.1 ThreadLocal<T> tl=new ThreadLocal(); 用於訪問每個線程對象自己的那個map集合的工具類
T代表存入map集合的value的類型
3.2
tl.set(obj); --> 獲取當前線程 得到當前線程內部的map 向map中存儲(tl,obj)的鍵值對
set(obj){
Thread t=Thread.currentThread(); //1.獲取當前線程對象
ThreadLocalMap map = getMap(t); //2.獲取當前線程對象身上的threadLocalMap->map集合 1.如有,則直接拿來使用 2.如爲null,則爲其創建一個map集合
map.set(this, value); //3.將tl作爲key,將傳入的參數作爲值,存入map集合中
}
3.3
tl.get(); --> 獲取當前線程 得到當前線程內部的map 從map中查找tl對應的值 返回
get(){
Thread t = Thread.currentThread(); //1.獲取當前線程對象
ThreadLocalMap map = getMap(t); //2.獲取當前線程對象身上的threadLocalMap->map集合
map.getEntry(this); //3.使用自身(tl對象)作爲key,從map集合中查詢對應的value
}
因此,對於每個線程來說,每個tl對象,僅可以存1個值
但是這個tl對象,可以在多個線程間公用
如果一個線程要存儲多個本地變量,則需要創建多個tl對象
*/
public class ThreadLocal02 {
//單例
//1.私有的構造器
private ThreadLocal02(){}
//2.私有的靜態的本類唯一實例
private static ThreadLocal02 tm=new ThreadLocal02();
//3.公有的靜態的返回本類唯一實例的方法
public static ThreadLocal02 getInstance(){
return tm;
}
//private Myconn conn;//多個線程操作會出現問題
private ThreadLocal<Myconn> t1=new ThreadLocal<Myconn>();// ThreadLocal,用於訪問每個線程對象自己的那個map集合ThreadLocalMap的工具類
// 泛型是需要保存在每個線程自身的map集合中的value的類型
// 每個ThreadLocal對應Thread的map集合中的一個key
/**
* 開啓事務startTransaction()
*/
public void startTransaction(){
//創建當前線程使用的連接對象
Myconn conn=new Myconn(); //開啓事務的時候獲取唯一的連接池對象
//Thread t=Thread.currentThread();
//t.getMap() Thread對象內置了一個Map來存取消息,但是這個map外界無法直接操作
//需要通過ThreadLocal來實現對Thread中的Map進行數據的存取
//將當前線程使用的連接對象存入線程自身的map對象
t1.set(conn); //key=ThreadLocal當前線程,value=conn
System.out.println("線程"+Thread.currentThread().getName()+"開啓事務操作的連接對象conn是"+conn);
}
/**
* 提交事務commit()
*/
public void commit() {
//從線程自己的map集合中獲取之前使用的conn對象
Myconn conn=t1.get();
System.out.println("線程"+Thread.currentThread().getName()+"提交事務操作的連接對象conn是"+conn);
}
}
package com.hxuner.thread;
public class zTest {
public static void main(String[] args) {
//只需要Myconn()連接對象,MyRunn2線程,ThreadLocal02的conn管理類(private ThreadLocal<Myconn> t1=new ThreadLocal<Myconn>();每個ThreadLocal對應Thread的map集合中的一個key)
Thread t1=new Thread(new MyRunn2());
Thread t2=new Thread(new MyRunn2());
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
/*
* 輸出
線程Thread-0開啓事務操作的連接對象conn是com.hxuner.thread.Myconn@55f33675
線程Thread-1開啓事務操作的連接對象conn是com.hxuner.thread.Myconn@525483cd
線程Thread-0提交事務操作的連接對象conn是com.hxuner.thread.Myconn@55f33675
線程Thread-1提交事務操作的連接對象conn是com.hxuner.thread.Myconn@525483cd
通過線程本地變量,每個線程操作的是保存在自身的map集合中的變量
所以多線程併發操作的不再是同一個變量,進而解決了線程安全問題。
*/
}
}
---------------------------------EasyMall-----------------------------事務耦合類------------------------------------------------
package com.hxuner.util;
import java.sql.Connection;
import java.sql.SQLException;
/*
EasyMall商品模塊實現 - 添加商品 - 事務控制
1.事務的引出:
在ProdServiceImpl插入商品和插入商品種類表之間出現異常int i=10/0;數據庫中插入了商品種類表,但是對應的商品未插入
所以應該是事務級別,要麼都完成,要麼都不完成
2.事務開啓解決方案
ProdServiceImpl中 addProd()增加商品方法中
------------------注意: 在Service中需要操作事務,需要在Service中使用Conn對象,造成和Dao強耦合,引出TransactionManager--------
try{
開啓事務 conn.setAutoCommit(false);
一系列操作{
使用cname查prob_category表,查看是否有數據 getPCByCname()
無數據,插入商品種類表 insertProdcategory()
查詢商品種類表PC getPCByCname()
插入商品 insertProd()
}
提交事務 conn.commit()
}catch(){
回滾事務 conn.rollback()
}
ProdDaoImpl中
getPCByCname{
conn=JDBCUtils.getConn();
-----------------注意:將conn對象返回給ProdServiceImpl,這樣ProdServiceImpl才能實現事務----------------------
}
3.TransactionManager的引出
在Service中需要操作事務,將一個方法中的多個對數據庫的操作放到同一個事務下進行
如果在Service中使用Conn對象,會造成Service和Dao層的強耦合
這種耦合不可避免,因此嘗試使用一個工具類將耦合管理起來,這個工具類就是TransactionManager
開發了TransactionManager 在其中管理Connection
對外提供 1.startTransaction()開啓事務 2.commit()提交事務 3.rollback()回滾事務 4.getConn()獲取連接對象 5.closeConn()關閉連接對象
之後所有和事務 相關的操作都不要直接使用Conn 而是通過TransactionManager來實現管理
解決耦合性的問題 - 本質上是將耦合轉移到了TransactionManager中同一管理
雖然沒有徹底的解決耦合 但是統一管理起來 方便未來開發和維護
*/
/*
TransactionManager
問題1:設計單例還是多例
如果是多例,那麼需要在Service中創建實例new Connection(),而Dao中應該使用同一個實例,這種情況下需要service將conn傳入給Dao,再次造成強耦合。
所以設計爲單例,或者做成靜態工具類(內部的成員變量都是靜態的)
問題2:多線程下的線程安全問題
解決方案:
方案1:在類的內部通過靜態Connection加鎖的方式來管理 - 可以解決問題,但是所有事務排隊,效率非常低
方案2:通過ThreadLocal編碼,實現所有的線程都各自攜帶各自的Connection對象,從而讓管理各自的事務 - 不會有阻塞 效率高
*/
public class TransactionManager {
//單例
//1.私有的構造器
private TransactionManager(){}
//2.私有的靜態的本類唯一實例
private static TransactionManager tm=new TransactionManager();
//3.公有的靜態的返回本類唯一實例的方法
public static TransactionManager getInstance(){
return tm;
}
//private Connection conn; //多個線程操作會出現問題
//第一個線程調用這個方法,獲取連接對象@123
//第二個線程也調用這個方法,獲取連接對象@789
//由於只有一個,整個conn都變爲@789,而線程1不知道,線程1和線程2提交的都是@789,出現併發問題
//ThreadLocal
//---------------------線程對象有一個自己的Map集合ThreadLocalMap,Map集合ThreadLocalMap存放{ThreadLocal,value}鍵值對-------------------------
private ThreadLocal<Connection> tl=new ThreadLocal<Connection>(); //ThreadLocal是操作線程用來保存本地變量的map集合的工具
//每個ThreadLocal對應Thread的map集合ThreadLocalMap中的一個key
/**
* 開啓事務startTransaction()
*/
public void startTransaction(){
Connection conn=JDBCUtils.getConn(); //每個線程獲取屬於自己的開啓事務的時候獲取唯一的連接池對象
try {
conn.setAutoCommit(false); //基於該連接對象開啓事務
tl.set(conn); //將conn保存到當前線程的map集合中,供該線程後續使用
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 提交事務commit()
*/
public void commit() {
Connection conn=tl.get(); //從當前線程對象的map集合中獲取之前保存的連接對象
if(conn!=null){
try {
conn.commit(); //使用該連接對象提交事務
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 回滾事務rollback()
*/
public void rollback(){
Connection conn=tl.get(); //從當前線程對象的map集合中獲取之前保存的連接對象
if(conn!=null){
try {
conn.rollback(); //基於該連接對象回滾事務
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
/**
* 獲取唯一實例連接對象Conn getConn()
*/
public Connection getConn(){
return tl.get(); //返回當前線程對象保存的conn對象
}
/**
* 關閉連接對象 close()
*/
public void closeConn(){
Connection conn=tl.get(); //從當前線程對象的map集合中獲取之前保存的連接對象
if(conn!=null){
try {
conn.close(); //基於該連接對象關閉連接
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.添加商品
3.1ProdListServlet
package com.hxuner.web;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.hxuner.domain.Prod;
import com.hxuner.factory.BaseFactory;
import com.hxuner.service.ProdService;
/*
* 顯示商品邏輯:
* 用戶請求->ProdListServlet->ProdServiceImpl->ProdDaoImpl->返回給ProdListServlet->prodList.jsp
* ProdListServlet
* //1.接受請求參數 無
* //2.表單驗證
* 3.調用service查詢所有產品的數據
* 4.將獲取到的數據存入request作用域
* 5.轉發給ProdList.jsp
*
* ProdService
* List<Prod> listProd(){}查詢所有產品的數據
*
* ProdDao
* List<Prod> listProd(){}查詢所有產品的數據
*
*
* ProdList.jsp
*
* 從request作用域獲取數據並展示
*/
/*
* ProdListServlet
* //1.接受請求參數 無
* //2.表單驗證
* 3.調用service查詢所有產品的數據
* 4.將獲取到的數據存入request作用域
* 5.轉發給ProdList.jsp
*/
public class ProdListServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//調用service對應的方法listAllProd();,獲取所有商品數據
ProdService service=BaseFactory.getFactory().getInstance(ProdService.class);
List<Prod> list=service.listAllProd();
//將商品存入request作用域
request.setAttribute("prods", list);
// 將請求轉發給prodList.jsp
request.getRequestDispatcher("/prodList.jsp").forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3.2ProdService
/**
* 查詢全部商品信息的方法
* @return 封裝了商品數據的javaBean的集合或者是null
*/
List<Prod> listAllProd();
ProdServiceImpl
package com.hxuner.service;
import java.sql.Connection;
import java.util.List;
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;
import com.hxuner.util.TransactionManager;
/*
1.事務的引出:
在插入商品和插入商品種類表之間出現異常int i=10/0;數據庫中插入了商品種類表,但是對應的商品未插入
所以應該是事務級別,要麼都完成,要麼都不完成
2.事務開啓解決方案
ProdServiceImpl中 addProd()增加商品方法中
try{
開啓事務 conn.setAutoCommit(false);
一系列操作{
使用cname查prob_category表,查看是否有數據 getPCByCname()
無數據,插入商品種類表 insertProdcategory()
查詢商品種類表PC getPCByCname()
插入商品 insertProd()
}
提交事務 conn.commit()
}catch(){
回滾事務 conn.rollback()
}
ProdDaoImpl中
getPCByCname{
conn=JDBCUtils.getConn();
-----------------注意:將conn對象返回給ProdServiceImpl,這樣ProdServiceImpl才能實現事務----------------------
}
3.TransactionManager的引出
在Service中需要操作事務,將一個方法中的多個對數據庫的操作放到同一個事務下進行
如果在Service中使用Conn對象,會造成Service和Dao層的強耦合
這種耦合不可避免,因此嘗試使用一個工具類將耦合管理起來,這個工具類就是TransactionManager
*/
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表
boolean flag1=false;//是否增加成功,即返回值
//1.先使用cname查prob_category表,查看是否有數據
try{
ProdCategory pc=null;
//--------開啓事務----------------------
TransactionManager.getInstance().startTransaction();
//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();
}
}
/*
* try{TransactionManager.getInstance().commit();提交事務}
* catch(有異常){TransactionManager.getInstance().rollback();回滾事務}
* finally{關閉conn連接}
*/
//------------注意如果在這裏寫int i=10/0; 出現異常,數據庫中插入了商品種類表,但是對應的商品未插入,---
//-----------所以應該是事務級別,要麼都完成,要麼都不完成------------------------
//2.使用cid給prob賦值
prod.setCid(pc.getId());
//3.添加商品信息到prob表
flag1=prodDao.insertPord(prod);
//--------提交事務-----------------
TransactionManager.getInstance().commit();
}catch(Exception e){
e.printStackTrace();
//--------事務回滾-----------------
TransactionManager.getInstance().rollback();
}finally{
// 關閉連接
TransactionManager.getInstance().closeConn();
}
return flag1;
}
// ProdService List<Prod> listProd(){}查詢所有產品的數據
//查詢全部商品信息的方法
@Override
public List<Prod> listAllProd() {
// TODO Auto-generated method stub
return prodDao.listAllProd();
}
}
3.3ProdDao
/**
* 查詢全部商品信息的方法
* @return 封裝了商品數據的JavaBean的集合 或 null
*/
List<Prod> listAllProd();
ProdDaoService
package com.hxuner.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import com.hxuner.domain.Prod;
import com.hxuner.domain.ProdCategory;
import com.hxuner.exception.MsgException;
import com.hxuner.util.JDBCUtils;
import com.hxuner.util.TransactionManager;
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=TransactionManager.getInstance().getConn();//因爲操作事務,Dao的這個conn對象與Service的conn對象是同一個,所以需要獲取TransactionManager的唯一實例對象conn
//而且由於是同一個Conn,關閉操作在Service事務完成之後進行關閉。
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("添加商品種類出現異常");
}
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=TransactionManager.getInstance().getConn();//因爲操作事務,Dao的這個conn對象與Service的conn對象是同一個,所以需要獲取TransactionManager的唯一實例對象conn
//而且由於是同一個Conn,關閉操作在Service事務完成之後進行關閉。
ps=conn.prepareStatement(sql);
ps.setString(1, pc.getCname());
int i=ps.executeUpdate();
if(i>0){
return true;
}
} catch (Exception e) {
e.printStackTrace();
}
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=TransactionManager.getInstance().getConn(); //因爲操作事務,Dao的這個conn對象與Service的conn對象是同一個,所以需要獲取TransactionManager的唯一實例對象conn
//而且由於是同一個Conn,關閉操作在Service事務完成之後進行關閉。
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();
}
return false;
}
//ProdDao List<Prod> listProd(){}查詢所有產品的數據
@Override
public List<Prod> listAllProd(){
//不只是需要查詢整個商品的信息,還需要知道商品種類名稱cname,因爲prod的javaBean中有pord_categroy的cname和prod數據庫表的所有字段
String sql="select p.*,c.cname from prob p inner join prob_category c on p.cid=c.id";
List<Prod> list=null;
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=JDBCUtils.getConn();
ps=conn.prepareStatement(sql);
rs=ps.executeQuery();
list=new ArrayList<Prod>();
while(rs.next()){
Prod prod=new Prod();
prod.setId(rs.getInt("id"));
prod.setName(rs.getString("name"));
prod.setCname(rs.getString("cname")); //不只是需要查詢整個商品的信息,還需要知道商品種類名稱cname,因爲prod的javaBean中有pord_categroy的cname和prod數據庫表的所有字段
prod.setCid(rs.getInt("cid"));
prod.setPrice(rs.getDouble("price"));
prod.setPnum(rs.getInt("pnum"));
prod.setImgurl(rs.getString("imgurl"));
prod.setDescription(rs.getString("description"));
list.add(prod);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.close(conn, ps, rs);
}
return list;
}
}
3.4ProdList.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=UTF-8" />
<link href="css/prodList.css" rel="stylesheet" type="text/css">
</head>
<body>
<div id="content">
<div id="search_div">
<form method="post" action="#">
<span class="input_span">商品名:<input type="text" name="name"/></span>
<span class="input_span">商品種類:<input type="text" name="category"/></span>
<span class="input_span">商品價格區間:<input type="text" name="minprice"/> - <input type="text" name="maxprice"/></span>
<input type="submit" value="查詢">
</form>
</div>
<%--ProdList.jsp 從request作用域獲取數據並展示 --%>
<c:forEach items="${requestScope.prods}" var="prod" >
<div id="prod_content">
<div id="prod_div">
<img src="${app}/ProdImageServlet?imgurl=${prod.imgurl}"></img>
<%-- <img src="${prod.imgurl }"></img> 由於WEB-INF的圖片不能被瀏覽器訪問,被保護,只能通過response輸出緩衝區獲取 (和獲取驗證碼一樣)
ProdList.jsp src="ProdImageServlet"傳入imgurl
ProdImageServlet 1.獲取請求參數,得到圖片的url 2.從服務器上獲取該商品的圖片 3.通過應答流寫給用戶
--%>
<div id="prod_name_div">
${prod.name }
</div>
<div id="prod_price_div">
¥ ${prod.price} 元
</div>
<div>
<div id="gotocart_div">
<a href="#">加入購物車</a>
</div>
<div id="say_div">
133人評價
</div>
</div>
</div>
</div>
</c:forEach>
<div style="clear: both"></div>
</div>
</body>
</html>
3.5ProdImgurlServlet
package com.hxuner.web;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*
<img src="${prod.imgurl }"></img> 由於WEB-INF的圖片不能被瀏覽器訪問,被保護,只能通過response輸出緩衝區獲取 (和獲取驗證碼一樣)
ProdList.jsp src="ProdImageServlet"傳入imgurl
ProdImageServlet 1.獲取請求參數,得到圖片的url 2.從服務器上獲取該商品的圖片 3.通過應答流寫給用戶
*/
public class ProdImageServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 獲取ServletContext對象
ServletContext sc=this.getServletContext();
// 1. 獲取請求參數 imgurl
String imgurl=request.getParameter("imgurl");
// 2. 打開一個輸入流,從服務器上讀取對應圖片的字節流
FileInputStream fis=null;
ServletOutputStream sos=null;
try {
fis=new FileInputStream(sc.getRealPath(imgurl));
sos=response.getOutputStream(); //獲取應答流response
byte[] array=new byte[100];
int len=fis.read(array);
while(len!=-1){
// 3. 將圖片寫入應答緩衝區,後續web容器會從緩衝區中拿出該內容,添加到應答實體中,返回給瀏覽器
sos.write(array, 0, len);
len=fis.read(array);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(fis!=null){
fis.close();
}
//注意不用關閉response應答流,會自動關閉
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}