【左京淳的JAVA WEB学习笔记】第十一章 显示列表、图片、商品细节

显示列表

访问案例网站时默认调用index.jsp,在此页面设定转向MainSvl。
http://localhost:8080/BookShop
新建index.jsp

<%request.getRequestDispatcher("/MainSvl").forward(request, response);%>

新建MainSvl(控制层)

@WebServlet("/MainSvl")
public class MainSvl extends HttpServlet {
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      BookBiz biz = new BookBiz();
      try {
        List<TBook> books = biz.getAllBooks();
        request.setAttribute("books", books);
        request.getRequestDispatcher("/WEB-INF/main/main.jsp").forward(request, response);
      }catch(Exception e){
        request.setAttribute("msg", "网路异常,请跟网站管理员联系。");
        request.getRequestDispatcher("/error.jsp").forward(request, response);
      }
    }
  (余略)
}

新建BookBiz(服务层)

public class BookBiz {
  public List<TBook> getAllBooks() throws Exception{
    IBookDao dao = new BookDaoMysql();
    try {
      return dao.getAllBooks();
    }finally{
      dao.closeConnection();
    }
  }
}

新建BookDaoMysql(持久层:负责DB访问)

public class BookDaoMysql extends BaseDao{
  public List<TBook> getAllBooks() throws Exception{
    List<TBook> books = null;
    String sql = "select isbn,name,press,price,pdate from tbook order by isbn";
    //从BaseDao取得connection = DriverManager.getConnection(dbInfo.getUrl(), dbInfo.getUname(), dbInfo.getPwd());
    this.openConnection(); 
    PreparedStatement ps = this.connection.prepareStatement(sql);
    ResultSet rs = ps.executeQuery();
    if(rs != null) {
      books = new ArrayList<TBook>();
      //当rs里面还有下一件资料时
      while(rs.next()) {
        //把取出的资料都保存到TBook物件中
        TBook bk = new TBook();
        bk.setBname(rs.getString("bname"));
        bk.setIsbn(rs.getString("isbn"));
        bk.setPdate(rs.getString("Pdate"));
        bk.setPress(rs.getString("press"));
        bk.setPrice(rs.getString("price"));
        //将TBook物件加入列表
        books.add(bk);
      }
    }
    return books;

记忆重点

  • 使用DriverManager取得connection
  • 使用connection内的prepareStatement(sql)装载sql文
  • 使用connection内的executeQuery()发送sql文并取得resultSet
  • 使用回圈将resultSet的资料保存至javaBean物件中,并将物件加入列表。
  • 由服务层呼叫closeConnection()

在JSP页面显示列表

使用JSTL的<c:forEath>标签即可简单取出列表
var为保存资料用的变数名(可自取),items为资料来源列表的名字。

<table border="1" width=100%>
  <c:forEach var="bk" items="${books }">
    <tr>
      <td rowspan=3>
        <img width=100 height=100 src="<%=basePath%>BookPicSvl?isbn=${bk.isbn }">
      </td>
      <td colspan=2 align=center style="color:red">
        <a href="<%=basePath%>BookDetailSvl?isbn=${bk.isbn }">${bk.bname }</a>
      </td>
    </tr>
    <tr><td>商品价格</td><td>${bk.price }</td></tr>
    <tr><td>出版社</td><td>${bk.press }</td></tr>
  </c:forEach>
</table>

在这个案例中,使用了标签和<%=basePath%>,这两个部分是常用的代码,可以抽取出来放在base.jsp页面中。
使用以下指令进行静态引用

<%@include file ="/WEB-INF/base.jsp" %>

另外一种引用方法为动态引用,不过动态引用就无法取得base页面里的变数。得要在页面静态编译时便建立好变数才能引用。
<jsp:include page="/WEB-INF/base.jsp"></jsp:include>

c标签也有引用页面的import方法

<c:import url="base.jsp"></c:import>

不过这样做的话就必须先加载以下标签库

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

在本案例中标签库的加载交给base页面做了,所以选择include file指令更方便。

显示图片

当用户进行下载图片等比较耗时的处理,可以使用NIO(非阻塞流)技术(一种异步处理技术)。

新建BookPicSvl并开启支援异步模式。

@WebServlet(urlPatterns="/BookPicSvl",asyncSupported=true)
public class BookPicSvl extends HttpServlet {
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
      String isbn = request.getParameter("isbn");
      if(isbn == null) {
        throw new RuntimeException("isbn不能为空");
      }
      BookBiz biz = new BookBiz();
      try {
        byte[] pic = biz.getBookPic(isbn);
        if(pic != null) {
          AsyncContext acontext = request.startAsync();
          ServletOutputStream out = acontext.getResponse().getOutputStream();
          out.setWriteListener(new MyPicWriter(out,acontext,pic));
        }
      }catch(Exception e){
        Log.logger.error(e.getMessage(), e);
        request.setAttribute("msg", "网路异常,请检查。");
        request.getRequestDispatcher("/error.jsp").forward(request, response);
      }
    }
(余略)
}

记忆重点

  • 使用request.startAsync(),取得AsyncContext物件。
  • 使用acontext.getResponse().getOutputStream(),取得ServletOutputStream物件。
  • 使用out.setWriteListener(new MyPicWriter(out,acontext,pic)),将刚取得的AsyncContext物件、ServletOutputStream物件及欲处理资料都丢进WriteListener进行处理。

新建MyPicWriter

public class MyPicWriter implements WriteListener {
  private ServletOutputStream out;
  private AsyncContext ac;
  private byte[] pic;
  
  public MyPicWriter(ServletOutputStream out,AsyncContext ac,byte[] pic) {
    this.ac = ac;
    this.out = out;
    this.pic = pic;
  }
  
  @Override
  public void onError(Throwable t) {
    t.printStackTrace();
  }

  @Override
  public void onWritePossible() throws IOException {
    try {
      if(pic != null && out.isReady()) {
        out.write(pic);
        //out.flush(); --此处不能使用flush
        out.close();
      }
    }catch(Exception e){
      e.printStackTrace();
    }finally {
      ac.complete();
    }
  }
}

记忆重点

当图片资料不为空,且输出流已经准备好时,使用write(pic)将资料写出後关闭输出流。最後关闭异步处理物件。

服务层的动作

public class BookBiz {
  public byte[] getBookPic(String isbn) throws Exception{
    IBookDao dao = new BookDaoMysql();
    try {
      return dao.getBookPic(isbn);
    }finally{
      dao.closeConnection();
    }
  }
}

DAO层的动作

public class BookDaoMysql extends BaseDao{
  public byte[] getBookPic(String isbn) throws Exception{
    byte[] pic = null;
    String sql = "select pic from tbook where isbn = ?";
    this.openConnection(); 
    PreparedStatement ps = this.connection.prepareStatement(sql);
    ps.setString(1, isbn);
    ResultSet rs = ps.executeQuery();
    if(rs != null) {
      while(rs.next()) {
        pic = rs.getBytes("pic");
        break;
      }
    }
    rs.close();
    ps.close();
    return pic;
  }
}

利用特定条件取得资料时,SQL文内会放置"?"占位符。跟取得全部列表时不一样。
当SQL文内有占位符时,依照其数量,使用PreparedStatement物件的setString(index, value)将值代入;
例如ps.setString(1, isbn);,意思是将isbn变数内的值代入第一个占位符内。

使用while回圈来确保至少有一行资料,然後将其读取。
疑问:之前的教材中没有关闭rs和ps,不知道为什麽。

jsp页面的设置

在main.jsp和BookDetail.jsp中,使用标签来请求图片的数据流。

<img src="<%=basePath%>BookPicSvl?isbn=${bk.isbn}"/>

商品细节页面

在首页列表中点击任一商品之後,跳转到商品细节页面。连结长得像下面这样
http://localhost:8080/BookShop/BookDetailSvl?isbn=is001

新建BookDetailSvl

@WebServlet("/BookDetailSvl")
public class BookDetailSvl extends HttpServlet {
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      String isbn = request.getParameter("isbn");
      if(isbn == null) {
        throw new RuntimeException("isbn不能为空");
      }      
      BookBiz biz = new BookBiz();
      try {
        TBook book = biz.getBookDetail(isbn);
        request.setAttribute("book", book);
        request.getRequestDispatcher("/WEB-INF/main/BookDetail.jsp").forward(request, response);
      }catch(Exception e){
        Log.logger.error(e.getMessage(),e);
        request.setAttribute("msg", "网路异常,请跟网站管理员联系。");
        request.getRequestDispatcher("/error.jsp").forward(request, response);
      }
    }
}

记忆重点

  • Servlet负责呼叫service层取得资料。
  • 将资料存进request物件里。
  • 带着资料,转发到目标页面。

服务层
新增getBookDetail(isbn)方法

  public TBook getBookDetail(String isbn) throws Exception{
    IBookDao dao = new BookDaoMysql();
    try {
      return dao.getBookDetail(isbn);
    }finally{
      dao.closeConnection();
    }
  }

DAO层
新增getBookDetail(String isbn)方法

  public TBook getBookDetail(String isbn) throws Exception{
    TBook book = null;
    String sql = "select isbn,bname,press,price,pdate from tbook where isbn = ?";
    this.openConnection(); 
    PreparedStatement ps = this.connection.prepareStatement(sql);
    ps.setString(1, isbn);
    ResultSet rs = ps.executeQuery();
    if(rs != null) {
      while(rs.next()) {
        book.setBname(rs.getString("bname"));
        book.setIsbn(rs.getString("isbn"));
        book.setPdate(rs.getString("Pdate"));
        book.setPress(rs.getString("press"));
        book.setPrice(rs.getString("price"));
        break;
      }
    }
    return book;
  }

BookDetail.jsp页面

(前略)
<table border="1" width=100%>
  <tr>
    <td rowspan=3>
      <img width=100 height=100 src="<%=basePath%>BookPicSvl?isbn=${book.isbn }">
    </td>
    <td colspan=2 align=center style="color:red">
      ${book.bname }
    </td>
  </tr>
  <tr><td>商品价格</td><td>${book.price }</td></tr>
  <tr><td>出版社</td><td>${book.press }</td></tr>
  <tr><td height=300 colspan=3>图书简介</td></tr>
  <tr><td colspan=3 align="center">
    <a href="<%=basePath%>user/ShopCarAddSvl?isbn=${book.isbn }">加入购物车</a>&nbsp;
    <a href="<%=basePath%>MainSvl">返回</a>
    </td>
  </tr>
</table>

<<:  资产分类准则(asset classification guideline)

>>:  风险的决策应在投资评估过程中行使

[JS] You Don't Know JavaScript [Scope & Closures] - Using Closures?

前言 目前为止我们都专注在解释辞法范围,以及他会对程序中的变量与使用产生什麽影响,本章节会将角度转移...

Day 16 for-each

在写for回圈的时候,每次都需要输入for回圈的条件式,宣告int I, i<某数, i++ ...

[DAY25] Boxenn 小结

将几篇 Boxenn 相关文章整理成分类目录(范例 GitHub repository 建置中,完成...

Day4. 一起精通 Rails Array,处理更复杂的问题

接下来Day4-6的用法,都是由Ruby的Enumerable。Enumerable 是Ruby相当...

理解网际网路协定(二):浮动 IP、固定 IP、虚拟 IP,这麽多种 IP 都是什麽?

理解了 IP 位置的组成,我们接着来看看一些常被提到的相关名词:浮动、固定及虚拟 IP 位置。 浮动...