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,仅在两个查询的时候不互斥。
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 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 t1=new ThreadLocal();// 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 t1=new ThreadLocal();每个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 tl=new ThreadLocal(); //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.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 listProd(){}查询所有产品的数据
*
* ProdDao
* List 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 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 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 listProd(){}查询所有产品的数据
//查询全部商品信息的方法
@Override
public List listAllProd() {
// TODO Auto-generated method stub
return prodDao.listAllProd();
}
}
3.3ProdDao
/**
* 查询全部商品信息的方法
* @return 封装了商品数据的JavaBean的集合 或 null
*/
List 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 listProd(){}查询所有产品的数据
@Override
public List 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 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();
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" %>
<%--ProdList.jsp 从request作用域获取数据并展示 --%>
<%-- 由于WEB-INF的图片不能被浏览器访问,被保护,只能通过response输出缓冲区获取 (和获取验证码一样)
ProdList.jsp src="ProdImageServlet"传入imgurl
ProdImageServlet 1.获取请求参数,得到图片的url 2.从服务器上获取该商品的图片 3.通过应答流写给用户
--%>
${prod.name }
¥ ${prod.price} 元
133人评价
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;
/*
由于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);
}
}