zoukankan      html  css  js  c++  java
  • 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() + " " + news.getNtitle() + " " + 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.2siteapidocs为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.4docs为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修饰的类;

     

  • 相关阅读:
    DbHelper数据操作类
    获取cpu序列号,硬盘ID,网卡MAC地址
    用户必备资料 103个Windows XP运行命令
    在Web.config配置文件中自定义配置节点
    Microsoft.NET PetShop4架构与技术分析
    数字转英文(货币)大写(vb)
    如何计算dba_tables中的avg_row_len.
    行选移与行链接的介绍
    如何使用动态SQL
    如何导致全表扫描原因
  • 原文地址:https://www.cnblogs.com/HQING/p/9573704.html
Copyright © 2011-2022 走看看