JSP/Servlet开发——第六章 JSP开发业务应用

1、 大容量的数据显示的缺点:

  当数据量较多时,用户需要拖动页面才能浏览更多信息;

  数据定位不便;

2、分页显示:

既能显示多条数据,又不需要拖动页面,是数据更加清晰直观,页面不再冗长,也不受数据量的限制;

实现分页的方式下面两种:

  一、例如,将所有查询结果以集合等形式保存在内存中,翻页时从中取出一页所需的数据显示:

      ◆这种方法有两个主要的缺点:  1、用户看到的可能是过期数据;

                     2、如果数据量非常大,查询一次数据集会耗费很长时间,并且存储的数据也会占用大量内存开销。

  二、每次翻页时只从数据库中检索出本页需要的数据。

            虽然每次翻页都查询数据库,但查询出的记录数相对较少,总体开销不大,再配以连接池技术以及其他查询优化。可以达到比高的效率。

实现分页显示的步骤:

  1、确定每页显示的数据数量;

       根据实际的页面设计,确定在数据列表中每次显示多少条记录 . 即每次从数据库中需要查询多少条记录用于页面显示,通常这个数量可以在开发时定义好,也可以由用户来选择;

  2、计算分页显示所需的总页数;

       既然要进行分页显示,还需清楚按照每页显示的记录数量总共会产生多少页数据,在页面中示的记录数量是已知的,而数据库中符合展示条件的记录总数是未知的;

  3、编写SQL查询语句,实现数据查询;

  4、在JSP页面中进行分页显示设置;

■详解:

●得到总页数的步骤:

  通过查询获取符合展示条件的总记录数,可以借助count()聚合函数实现;

     ▲select count(1) from 表名   效率高于  select count(*) from 表名

eg:定义新闻接口的代码:

public interface NewsDao {

           // 获得新闻总数

          public int getTotalCount() throws SQLException;

         }

 

eg:新闻接口实现类中获取数据库中记录总数的代码:

public class NewsDaoImpl extends BaseDao implements NewsDao {

   // 获得所有新闻的数量

    public int getTotalCount() throws SQLException {

        ResultSet rs = null;

        String sql = "SELECT COUNT(`nid`) FROM `news`";

        int count = -1;

        try {

            rs = this.executeQuery(sql);

            rs.next();

            count = rs.getInt(1);

        } catch (SQLException e) {

            e.printStackTrace();

            throw e;

        } finally {

            DatabaseUtil.closeAll(null, null, rs);

        }

        return count;

  }

}

  有了需要展示的记录总数后,就可以根据每页显示的记录数计算共需要划分为多少页。基于方便代码管理的考虑,将有关分页的数据封装到一个Page 类中 ,其中包括每页显示的数据量、数据的总数量、显示的总页数、当前页码、每页显示的数据集合。

eg:org.news.util包中创建Page类:

package org.news.util;

import java.util.List;

import org.news.entity.News;

public class Page {

    // 总页数

    private int totalPageCount = 0;

    // 页面大小,即每页显示记录数

    private int pageSize = 5;

    // 记录总数

    private int totalCount;

    // 当前页码

    private int currPageNo = 1;

    // 每页新闻集合

    private List<News> newsList;

    public int getCurrPageNo() {

        if (totalPageCount == 0)

            return 0;

        return currPageNo;

    }

    public void setCurrPageNo(int currPageNo) {

        if (currPageNo > 0)

            this.currPageNo = currPageNo;

    }

    public int getPageSize() {

        return pageSize;

    }

    public void setPageSize(int pageSize) {

        if (pageSize > 0)

            this.pageSize = pageSize;

    }

    public int getTotalCount() {

        return totalCount;

    }

    public void setTotalCount(int totalCount) {

        if (totalCount > 0) {

            this.totalCount = totalCount;

            // 计算总页数

            totalPageCount = this.totalCount % pageSize == 0 ? (this.totalCount / pageSize) : (this.totalCount / pageSize + 1);

        }

    }

    public int getTotalPageCount() {

        return totalPageCount;

    }

    public void setTotalPageCount(int totalPageCount) {

        this.totalPageCount = totalPageCount;

    }

    public List<News> getNewsList() {

        return newsList;

    }

    public void setNewsList(List<News> newsList) {

        this.newsList = newsList;

    }

}

分析:在上面的示例中:设置记录总数的 setTotalCount )方法中,根据记录总数和每页显示记录数计算出总数,使用了三元运算符 ’? :" 进行处理。如果记录总数能被每页显示记录数整除,则总页数为两的商;如果不能被整除,则余出的记录数单独列为一页,所以总页数为两者的商再加一;

编写SQL语句:不同数据库厂商实现分页的SQL语句之间存在差异,因此在实际应用中根据数据库的不同,需要修改相应的SQL语句;

  ◆编写 SOL 语实现数据分页显示的关键是如何编写 SQL 查询语句,MySQL数据库中,可以使用 LIMIT 子句现分页需求;

      eg:每页显示三条记录,若要显示第一页的记录,则SQL语句:

              SELECT `nid`, `ntitle`, `ncreateDate` FROM `news` LIMIT 0, 3

      SQL语句分析:

        ▲在这段 SQL 语句中 , LIMIT 子句的两个参数分别代表起始行偏移量和最大返回行数。最大返回数相当于每页显示的记录数,是一个固定值。而每页数据起始行的偏移量是动态的;

        ▲应该如何确定:假设每页显示三条数据,取第一页的数据时,不需要偏移。即起始行偏移量是 0,第二页时则需越过第一页数据,即偏移 3 条记录后再开始提取;而第三页则需越过前两页数据,即偏移 2*3记录;

        ▲可以从中总结出一个规律: 起始行偏移量=(当前页页码-1) *每页显示的记录数;

             所以上面的代码可以该为:

                SELECT `nid`, `ntitle`, `ncreateDate` FROM `news` LIMIT (当前页页码-1) *每页显示的记录数,每页显示的记录数

            eg:下面通过访问数据库来获得每页显示的新闻集合,代码修改如下:

eg:定义新闻接口的代码:

public interface NewsDao {

     // 获得新闻总数

 public int getTotalCount() throws SQLException;

   // 分页获得新闻

 public List<News> getPageNewsList(int pageNo, int pageSize) throws SQLException;

  }

eg:新闻接口实现类中获取数据库中记录总数的代码:

public class NewsDaoImpl extends BaseDao implements NewsDao {

   // 获得所有新闻的数量

    public int getTotalCount() throws SQLException {

        ResultSet rs = null;

        String sql = "SELECT COUNT(`nid`) FROM `news`";

        int count = -1;

        try {

            rs = this.executeQuery(sql);

            rs.next();

            count = rs.getInt(1);

        } catch (SQLException e) {

            e.printStackTrace();

            throw e;

        } finally {

            DatabaseUtil.closeAll(null, null, rs);

        }

        return count;

}

// 分页获得新闻

public List<News> getPageNewsList(int pageNo, int pageSize)  throws SQLException {

        List<News> list = new ArrayList<News>();

        ResultSet rs = null;

        String sql = "SELECT `nid`, `ntid`, `ntitle`, `nauthor`,"

                + " `ncreateDate`, `nsummary`, `tname` FROM `NEWS`, `TOPIC`"

                + " WHERE `NEWS`.`ntid` = `TOPIC`.`tid`"

                + " ORDER BY `ncreateDate` DESC LIMIT ?, ?";

        try {

            rs = this.executeQuery(sql, (pageNo-1)*pageSize, pageSize);

            News news = null;

            while (rs.next()) {

                news = new News();

                news.setNid(rs.getInt("nid"));

                news.setNtid(rs.getInt("ntid"));

                news.setNtitle(rs.getString("ntitle"));

                news.setNauthor(rs.getString("nauthor"));

                news.setNcreatedate(rs.getDate("ncreateDate"));

                news.setNsummary(rs.getString("nsummary"));

                news.setNtname(rs.getString("tname"));

                list.add(news);

            }

        } catch (SQLException e) {

            e.printStackTrace();

            throw e;

        } finally {

            DatabaseUtil.closeAll(null, null, rs);

        }

        return list;

  }

}

●在上面的代码中,将查询结果进行了降序排列,下面,添加测试分页的功能,

eg:将每页的新闻信息显示在控制台上:

public class PageTest {

    @Test

    public void pageTest() {

        Connection conn = null;

        try {

            conn = DatabaseUtil.getConnection();

            NewsDao newsDao = new NewsDaoImpl(conn);

            int totalCount = newsDao.getTotalCount();

            Page page=new Page();

            page.setCurrPageNo(3);              //设置当前页面

            page.setPageSize(5);                //设置每页条数

            page.setTotalCount(totalCount);     //设置总数量

            System.out.println("新闻总数量是:" + page.getTotalCount());

            System.out.println("每页条数是:" + page.getPageSize());

            System.out.println("总页数:" + page.getTotalPageCount());

            System.out.println("当前是第" + page.getCurrPageNo() + "页:");

            List<News> newsList = newsDao . getPageNewsList(page.getCurrPageNo(), page.getPageSize());

            page.setNewsList(newsList);

            for (News news : page.getNewsList()) {

                System.out.println(news.getNid() + "\t" + news.getNtitle() + "\t" + news.getNcreatedate());

            }

        } catch (SQLException e) {

            e.printStackTrace();

        } finally {

            DatabaseUtil.closeAll(conn, null, null);

        }

    }

}

显示结果:

              

分页功能测试后,将获取用户数据、显示数据的代码分别拆分至JSP页面中实现:

 首先分析在 JSP 中如何进行分页的设置:

   (1)、确定当前页:当页面初次打开时,应该显示的是第一页的内容。但是如果页面是通过用户点击分页执查询后显示的结果,当前页面不一定是第一页,所以我们需要在请求时传递一个pageIndex变量来表示当前页的页码,如果这个变量不存在,则默认当前页为第一页,否则当前页为pageIndex变量的值;

   (2)、分页的设置:有了当前页,就可以通过当前页页码来确定首页、上一页和下一页以及末的页码;

    注:在设置分页时,需要将对应的页码作为请求参数 pegelndex 的值进行传递;

   (3)、首页与末页的控制。当在 JSP 获取 pagelndex 变量时,将其与首页和末页进行比较判断。如果 pagelndex 变量的值小于1,则将值修改为1,如果 pagelndex变量的值大于末页 (即总页数),则将值修改为末页页码。 从而避免页码出现-1或者大于总页数的情况;

 

3、用 Commons-FileUpload 组件实现文件上传:

Commons是Apache 开放源代码组织的一个 Java 子项目,该项目主要涉及一些开发中常用的模块,如文件上传、命令行处理、数据库连接等;FileUpload就是其中的一个用于处理 HTTP 文件传的子项目

组件具有以下几个特点:

  使用简单:Commons-FileUpload 组件可以方便地嵌入 JSP 文件中 , 在 JSP文件中仅编写堆代码即可完成文件的上传功能,十分方便;

  能够全程控制上传内容:使用 Commons-FileUpload组件提供的对象及操作方法,可以获全部上传文件的信息,包括文件名称、类型、大小等,方便操作;

  能够对上传文件的大小,类型进行控制:为了避免在上传过程中出现异常数据,在Commons-FileUpload组建中,专门提供了相应的方法用于对上传文件进行控制;

获取Commons-FileUpload组建的步骤:

  (1) 登录网站http://commons.apache.org/fileupload下裁 Commons-FileUpload 组件,即commons-fileupload-1.2.2-bin.zip, 将下载的文件解压,得到commons-fileupload-1.2.2文件夹。其中,commons-fileupload-1.2.2/lib/ commons-fileupload-1.2.2.jar即为Commons-FileUpload组件类库。commons-fileupload-1.2.2\site\apidocs为Commons-FileUpload组件的API文档,可以访问index.html查阅相关的类和接口;

  ( 2 ) 登录网站 http://commons.apache.org/io下载Commons-IO组件,即 commons-io-2.4-bin.zip。该组件用干处理文件上传所依赖的 I/O操作。将下载的commons-io-2.4-bin.zip文件解压,得到commons-io-2.4文件夹。其中commons-io-2.4.jar文件即为Commons-IO组件类库,commons-io-2.4\docs为Commons-IO组件的API文档,可以访问index.html相关的类和接口;

●设置表单的enctype属性

  文件上传时,需要在表单属性中添加 enctype 属性,该属性用于设置表单提交数据的编码方式,由于文件传至服务器时语言一般文本类型的编码不同,需要使用 multipart/form-data 的编码方式。

    ◆设置该属性的方法:<form enctype="multipart/form-data" method="post">  //上传文件时form标签的method属性取值必须为post,不能为get;

    ▲表单的enctype的属性的三个值:

        ☉application/x-www-form- urlencoded:默认值,该属性主要用于处理少量文本数据的传递,在向服务器发送大量的文件包含非ASCII字符的文本或 二进制数据时效率很低;

        ☉multipart/form-data上传二进制数据,只有使用了multipart/form-data才能完整地递文件数据,进行上传操作;

        text/plain : 主要用干向服务器传递大量文本数椐,比较适用干电子邮件的应用;

使用File 控件选择文件:

  eg:在表单中添加 File 控代码:、

<body>

   <form action="doupload.jsp" enctype="multipart/form-data" method="post">

               <p>姓名:<input type="text" name="user"></p>

               <p>选择图片:<input type="file" name="nfile"></p>

               <p><input type="submit" value="提交"></p>

   </form>

</body>

●Commons-FileUpload组件的API:

  ◆在使用Commons-FileUpload组件之前:环境准备:在项目中引入commons-fileupload-1.2.2.jar和commons-io-2.4.jar文件,添加完成jar之后,在JSP文件中还需要将Commons-FileUpload组件所使用的类库导入JSP文件中,代码:<%@ page import="org.apache.commons.fileupload.*"%>

   ◆ServletFileUpload类:用于实现文件上传操作;

        ☆ServletFileUpload类的常用方法:

             方法名称

方法描述

public void setSizeMax (long  sizeMax)

设置请求信息实体内容的最大允许的字节数

public  List  parseRequest (HttpServletRequest  req)

解析form表单中的每个字符的数据,返回一个FileItem对象集合

public static final boolean isMultipartContent(HttpServletRequest req)

判断请求信息中的内容 是否是multipart/form-data类型

public  void  setHeaderEncoding (String  encoding)

设置转换时所使用的字符集编码

   ◆FileItem接口:用于封装单个表单字段元素的数据,一个字段表单元素对应一个FileItem实例,在应用程序中使用的是其实现类DiskFileltem;

    ☆FileItem接口的常用方法:

方法名称

方法描述

public  boolean  isFormField( )

判断FileItem对象封装的数据类型(普通表单字段返回true,文件表单字段返回false);

public  String  getName( )

获得文件上传字段中的文件名(普通表单字段返回null);

public  String  getFieldName( )

返回表单字段元素的name属性值;

public  void  write( )

将FileItem对象中保存的主体内容保存到指定的文件中;

public  String  getString( )

将FileItem对象中保存的主体内容以一个字符串返回。其重载方法

public  String  getString(String encoding)中的参数用指定的字符集编码方式;

public  long  getSize( )

返回单个上传文件的字节数;

 ◆FileItemFactory接口与实现类:创建ServletFileUpload实例需要依赖FileItemFactory工厂接口,DiskFileItemFactory是FileItemFactory接口的实现类;

      ☆DiskFileItemFactory类的常用方法:

   方法名称

方法描述

public void  setSizeThreshold(int sizeThreshold)

设置内存缓冲区的大小

public void  setRepositoryPath(String path)

设置临时文件存放的目录

 

●Commons-FileUpload组件实现文件上传的功能:

<%@ page language="java" pageEncoding="UTF-8"%>

<%@page import="java.io.*,java.util.*"%>

<%@page import="org.apache.commons.fileupload.*"%>

<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>

<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>上传处理页面</title>

</head>

<body>

<%

     request.setCharacterEncoding("utf-8");

     String uploadFileName = ""; //上传的文件名

     String fieldName = "";  //表单字段元素的name属性值

     //请求信息中的内容是否是multipart类型

     boolean isMultipart = ServletFileUpload.isMultipartContent(request);

     //上传文件的存储路径(服务器文件系统上的绝对文件路径)

     String uploadFilePath = request.getSession().getServletContext().getRealPath("upload/" );

     if (isMultipart) {

           FileItemFactory factory = new DiskFileItemFactory();

           ServletFileUpload upload = new ServletFileUpload(factory);

           try {

                 //解析form表单中所有文件

                 List<FileItem> items = upload.parseRequest(request);

                 Iterator<FileItem> iter = items.iterator();

                 while (iter.hasNext()) {   //依次处理每个文件

                       FileItem item = (FileItem) iter.next();

                       if (item.isFormField()){  //普通表单字段

                            fieldName = item.getFieldName();   //表单字段的name属性值

                            if (fieldName.equals("user")){

                                  //输出表单字段的值

                                  out.print(item.getString("UTF-8")+"上传了文件。<br/>");

                            }

                       }else{  //文件表单字段

                            String fileName = item.getName();

                            if (fileName != null && !fileName.equals("")) {

                                  File fullFile = new File(item.getName());

                                  File saveFile = new File(uploadFilePath, fullFile.getName());

                                  item.write(saveFile);

                                  uploadFileName = fullFile.getName();

                                  out.print("上传成功后的文件名是:"+uploadFileName);               

                            }

                       }

                 }

           } catch (Exception e) {

                 e.printStackTrace();

           }

     }

%>

</body>

</html>

分析:上面的的代码完成了一个文件上传的功能,对于其中的关键步骤总结如下:

  在JSP 文件中使用 page 指令导入Commons-FileUpload组件所需的类;

  判断请求信息中的内容是否是 multipart 类型 , 如果是则进行处理;

  通过FileltemFactory 厂对象实例化 ServletFileUpload对象;

  通过ServletFileUpload对象的parseRequest()将表单中字段解析成Fileltem 对象的集合;

  通过迭代依次处理每个Filellem对象,如果是普通字段,通过 getString()方法得到相应表单字符的值,该值与表单字段的中的“name”属性对应,如果是文件字段,则通过 File 的构造方法构建一个指定路径名和文件名的文件,并通过Fileltem 对象的 write()方法将上传文件的内容保存到文件中;

 编写上传文件处理页的实现步骤

  1.        创建FileItemFactory对象

  2.        创建ServletFileUpload对象 : 通过构造方法:ServletFileUpload( FileItemFactory )

  3.        解析form表单提交的所有表单元素数据

    如果是普通表单元素:

         获取该元素的名和值使用

  •   如果是文件数据
    • 获取文件名的等参数
    • 保存文件数据到服务器

4、 用 Commons-FileUpload组件控制文件上传:

控制文件上传的类型和大小:

eg

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>

<%@page import="java.io.*,java.util.*,org.apache.commons.fileupload.FileItem"%>

<%@page import="org.apache.commons.fileupload.*"%>

<%@page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>

<%@page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>上传处理页面</title>

</head>

<body>

<%

     request.setCharacterEncoding("utf-8");

     String uploadFileName = ""; //上传的文件名

     String fieldName = "";  //表单字段元素的name属性值

     //请求信息中的内容是否是multipart类型

     boolean isMultipart = ServletFileUpload.isMultipartContent(request);

     //上传文件的存储路径(服务器文件系统上的绝对文件路径)

     String uploadFilePath = request.getSession().getServletContext().getRealPath("upload/" );

     //创建临时文件目录路径

     File tempPatchFile=new File("d:\\temp\\buffer\\");

     if(!tempPatchFile.exists())  //判断文件或目录是否存在

           tempPatchFile.mkdirs();   //创建指定的目录,包括所有必需但不存在的父目录

     if (isMultipart) {

           DiskFileItemFactory factory=new DiskFileItemFactory();

           //设置缓冲区大小4kb

           factory.setSizeThreshold(4096);  

           //设置上传文件用到临时文件存放路径

           factory.setRepository(tempPatchFile);  

           ServletFileUpload upload = new ServletFileUpload(factory);

           //设置单个文件的最大限制

           upload.setSizeMax(1024*30);  

           try {

                 //解析form表单中所有文件

                 List<FileItem> items = upload.parseRequest(request);

                 Iterator<FileItem> iter = items.iterator();

                 while (iter.hasNext()) {   //依次处理每个文件

                       FileItem item = (FileItem) iter.next();

                       if (!item.isFormField()){  //文件表单字段

                            String fileName = item.getName();

                            //通过Arrays类的asList()方法创建固定长度的集合

                            List<String> filType=Arrays.asList("gif","bmp","jpg");

                            String ext=fileName.substring(fileName.lastIndexOf(".")+1);

                            if(!filType.contains(ext))  //判断文件类型是否在允许范围内

                                        out.print("上传失败,文件类型只能是gif、bmp、jpg");

                            else{

                                  if (fileName != null && !fileName.equals("")) {

                                        File fullFile = new File(item.getName());

                                        File saveFile = new File(uploadFilePath, fullFile.getName());

                                        item.write(saveFile);

                                        uploadFileName = fullFile.getName();

                                        out.print("上传成功后的文件名是:"+uploadFileName+",文件大小是:"+item.getSize()+"bytes!");

                                  }          

                            }

                       }

                 }

           }catch(FileUploadBase.SizeLimitExceededException ex){

                 out.print("上传失败,文件太大,单个文件的最大限制是:"+upload.getSizeMax()+"bytes!");  

           }catch (Exception e) {

                 e.printStackTrace();

           }

     }

%>

</body>

</html>

分析:对控制文件类型进行分析:

  用到了 Arnnys 类,此类包含用于操作数组 (如排序和搜索 ) 的各种方法 , 通过Arrays类的asList()方法创建固定长度的集合,也就是得到允许文件类型的集合,然后通过集合的 contains()方法匹配上传文件的扩展名来判断文件类型是否在允许范围内。如图:

          

分析:对控制文件大小进行分析:

  如果上传的文件大小超出了设置的要求,系统会返回错误信息;如图:

         

   ◆在上面的示例代码中:创建临时文件目录路径,通过 DiskFileltemFactory 对象的 setSizeThreshold ( ) 方法设置缓冲区大小,当上传文件大小超过缓冲区大小。则临时存储在通过 DiskFileltemFactary 对象的setReposilory ( ) 方法设置的临时文件目录路径。同时通过 ServletFileUpload 对象的 setSizeMax()限制了一个完整请求的最大字节数,如果超出设置的字节数,则会抛出一个 FileUploadBase.SizeLimitExceededException 类型的异常,并通过异常处理提示错误信息;

FileUploadBase.SizeLimitExceededException是一个静态内部类,内部类是指定义在个类内部的类。内部类作为外部类的一个成员,并且依附于外部类而存在,这里FileUploadBase 就是外部类,而 SizeLimitExceededException 就是定义在FileUploadBase内部的由static修饰的类;

 

posted @ 2018-09-02 14:15  纪元A梦  Views(685)  Comments(0Edit  收藏  举报