7种分页

     在微软的ASP编程体系中,ADO对象的建立,使得从网页访问数据库成为一件易事,特别是ADO的Recordset对象使得控制数据的输出显示更为方便、自由。而在Visual InterDev6.0(以下简称VI6.0)中,由于Script Object Model(以下简称SOM)、Design-Time Control(以下简称DTC)以及Data Environment Object Model(以下简称DEOM)等对象模型的引入,使网页对数据库的访问设计显得更为方便。
  
    因为主题方面的原因,关于数据库的连接,下文只给出代码和简要注释,而把重点放在如何利用Recordset对象(或控件)实现数据记录的分页显示方面。根据我的理解,分页显示的关键就在于对ADO的Recordset对象或DTC(设计时控件)的Recordset控件的属性和方法的熟练把握上。
  
    这七种分页显示的武器概括起来说分四类:
  
    第一、二种我暂取名叫“纯ASP法”,这也是国内的ASP网站上用得最多的方法,它们的区别仅在实现技巧的不同。这两种方法的实现最易理解,用到的对象概念也最少,对开发环境的要求也最低(只要记事本就行)。可以说,这两种方法的实质还是CGI的编程思想,只是在程序中引入了ADO对象而已。
  
    第四、五种暂取名叫“SOM的DHTML法”。这两种方法要求在VI6.0的环境下,利用微软提出的脚本对象模型(Script Object Model)和DHTML中Table对象的与数据库绑定的新特性(许多书和文章只介绍了DHTML的CSS特性在样式设计中的运用而忽略介绍其数据绑定特性),实现在客户端控制翻页。但它要求用户的浏览器必须是支持DHTML,如:Microsoft Internet Explorer 4.0及以上的版本。
  
    第六种暂取名叫“SOM服务器端法”。要求在VI6.0的环境下开发,它利用微软提出的脚本对象模型(Script Object Model)中的几个DTC控件:Recordset、PageObject、Grid等在服务器端(客户端)实现翻页控制。这是一种激动人心的、全新的编程方法,它把网页看成对象(这种对象模型和传统的DOM----document object model是有区别的:DOM只能控制客户端,而SOM可控制服务器端和客户端),它真正实现了网页的面向对象编程。但遗憾的是,也许是我个人能力有限,这种技术我个人认为还不是很成熟,比如,与浏览器的结合还不是很好,这将在后文详细说明。
  
    第七种暂取名叫“DEOM法”。它也是利用了VI6.0中建立的数据环境对象模型(Data Environment Object Model)建立Recordset对象。这也是在网页编程上比较少见的新方法,与SOM模型相比,自有它的优点,这将在后文详述。
  
    在后面所举的所有例子源代码,都可以直接拷贝使用,你甚至可以不懂其原理,只要把其中的粗斜体字部分换成相应自己的数据库名或字段名就可以了。
  
    在开始详细介绍各种分页方法前,让我们先创建一个数据库:用Office97中的Access自创一个Employee.mdb,其中建一个表emp,只设三个字段:emp ID,last name和first name。为什么这么简单,是因为我们关心的是怎样处理recordset的结果。
  
    第一种:参数直接代入法
  
    这种方法是用手工建立Recordset对象,利用其pagesize(每页指定显示记录数),pagecount(总页码数)和absolutepage(当前页码数)属性来控制分页的输出。分页采用<href>直接带页码参数的方法来控制翻页。网页的名字为emp1.asp。源代码如下:
  
  <%//建立与employee.mdb数据库的连接。
  Set conn = Server.CreateObject("ADODB.Connection")
  conn.Open "driver={Microsoft Access Driver (*.mdb)};dbq=employee.mdb"
  //建立emp表的Recordset对象实例rs。
  Set rs = Server.CreateObject("ADODB.Recordset")
  rs.Open "emp", conn, 3
  
  PageSize = 10 //pagesize属性指定了每页要显示的记录条数
  Page = CLng(Request("Page")) ’string型转化为long型
  If Page < 1 Then Page = 1
  If Page > rs.PageCount Then Page = rs.PageCount
  If Page <> 1 Then
   Response.Write "<A HREF=emp1.asp?Page=1>第一页</A>"
   Response.Write "<A HREF=emp1.asp?Page=" & (Page-1) & ">上一页</A>"
  End If
  If Page <> rs.PageCount Then
   Response.Write "<A HREF=emp1.asp?Page=" & (Page+1) & ">下一页</A>"
   Response.Write "<A HREF=emp1.asp?Page="&rs.PageCount & ">最后一页</A>"
  End If
  Response.write"页码:" & Page & "/" & rs.PageCount & "</font>"
  //每一页的显示
  //显示表头
  Response.Write "<CENTER><TABLE BORDER=1>"
  Response.WRITE "<TR><TD>" & rs.Fields("emp ID").Name & "</TD>"
  Response.WRITE "<TD>" & rs.Fields("last name").Name & "</TD>"
  Response.WRITE "<TD>" & rs.Fields("first name").Name & "</TD></TR>"
  //循环显示每条记录
  rs.AbsolutePage = Page //把页码赋给absolutepage属性从而知当前页的首条记录号
  For iPage = 1 To rs.PageSize //
  Response.WRITE "<TR><TD>" & rs.Fields("emp ID").Value & "</TD>"
  Response.WRITE "<TD>" & rs.Fields("first name").Value & "</TD>"
  Response.WRITE "<TD>" & rs.Fields("last name").Value & "</TD></TR>"
  rs.MoveNext
  If rs.EOF Then Exit For
  Next
  Response.Write "</TABLE></CENTER>"%>
  
    第二种:表单传送参数法
  
    这种方法在创建Recordset对象时与第一种相同,只是在翻页控制时,采用<input>和case语句配合来实现翻页。网页的名字为:emp2.asp。此方法在编程逻辑上有个缺点:就是在按过“上页”或“下页”钮后,再在浏览器上按刷新按钮时,会自动翻页。源代码如下:
  
  if Pagenum = "" Then Pagenum = 1 //从第一页开始显示
  //建立数据库连接和Recordset对象实例rs。
  与第一种方法相同,此处略过。
  
  RS.Pagesize = 10 ’ 设置一页中显示的记录条数为10条
  // 确定翻页的动作
  Select Case Request("NAV")
  Case ""
   session("Pagenum") = 1
  case "First" ’ First Record
   session("Pagenum") = 1
  case "Prev" ’ Previous Record
   if session("Pagenum") > 1 then
    session("Pagenum") = session("Pagenum") - 1
   End If
  case "Next" ’ Next Record
   if session("Pagenum")< RS.PageCount then
    session("Pagenum") = session("Pagenum") + 1
   End if
  case "Last" ’ Last Record
   session("Pagenum") = RS.PageCount
  End Select
  RS.Absolutepage = Clng(session("Pagenum")) //确定当前页的第一条记录号
  // 显示当前页
  同第一种方法,此处略过。
  // Nav 翻页按钮设置
  <form method="GET" action="emp2.asp">
  <input type="submit" name="NAV" Value="首页">
  <input type="submit" value="上页" name="NAV">
  <input type="submit" value="下页" name="NAV">
  <input type="submit" value="末页" name="NAV"></form>
  
    第三种:用Grid控件设计分页
  
    所有的方法中,这种方法最容易。你只需拖DTC中的Recordset控件和Grid控件到asp网页中就行了。而且,你还能选择是在服务器平台还是在客户端平台控制翻页。缺点就是你必须用它给定的格式显示,而不能自己自由控制表格的显示格式。
  
    方法如下:
  
    在VI6.0中建一个工程emp.vip。再在工程中添加一个asp网页:eMP3.asp。
  
    第一步:选VI6.0菜单条上的“add data connect…”,按开发工具的导航提示,你就可以很容易地建立与Employee.mdb数据库的连接。从DTC工具栏中拖一个Recordset控件到网页中,并设置其属性。具体如图:
  
    当你拖控件到网页中时,VI6.0会自动提示你“是否使用Scripting object model”,按yes。
  
    第三步:从DTC工具栏中拖一个Grid控件到网页中,然后单击鼠标右键,设置其属性,如:选在第二步中创建的Recordset控件名,选择emp表中的字段,每页显示多少条记录以及显示格式等。非常简单方便,只要照着导航提示做就行了。
  
    第四种:DHTML法一。
  
    数据记录显示在一个HTML表中。它利用DHTML中表的数据绑定特性来控制记录的分页显示。缺点就是你的翻页方法将被限制为一种特定的方式:只能“上页”和“下页”而不能“首页”和“末页”。由于是在客户端控制翻页,所以,这种和第五种方法是速度最快的,但遗憾的是它只能在支持DHTML的浏览器上使用。
  
    在DHTML中,<TABLE>的DATASRC属性可使表格绑定到一个数据源,另一个属性DATAPAGESIZE可指定一页一次显示的记录数。
  
    我们来看下面的例子:
  
    第一步:拖Recordset控件到新建的网页emp4.htm中,设置其属性,方法同第三种,此处略。
  
    第二步:输入下面的代码:
  
  <TABLE ID="Table1" DATASRC="#Recordset1_RDS" DATAPAGESIZE=5> //假定前面设定Recordset控件名为Recordset1。每页显示5条记录。
  <THEAD>
  <TH ALIGN="left" WIDTH=150>Emp ID</TH> //输出表头
  <TH ALIGN="left" WIDTH=200>Last Name</TH>
  <TH ALIGN="left" WIDTH=200>First Name</TH>
  </THEAD>
  <TR>
  <TD><DIV DATAFLD="Emp ID"></DIV></TD> //输出表内容
  <TD><DIV DATAFLD="Last Name"></DIV></TD>
  <TD><DIV DATAFLD="First Name"></DIV></TD>
  </TR>
  </TABLE>
  
    第三步:然后,增加一对DTCs Button按钮控件来做翻页导航,一个命名为“btnPrevious”(上一页),一个命名为“btnNext”(下一页)。它们相应的脚本如下:
  
  <SCRIPT LANGUAGE=VBScript>
  Function btnPrevious_onclick()
  Table1.previousPage()
  End Function
  
  Function btnNext_onclick()
  Table1.nextPage()
  End Function
  </SCRIPT>

第五种:DHTML法二
  
    这种方法是对第四种方法的完善。采用手工编写脚本的方法,使我们能做“首页”,“末页”翻页导航按钮,并能确定每条记录的位置(记录号)。由于篇幅的关系,我在下面只介绍一个具体例子,并给出简要说明。其它关于DHTML和Recordset控件的一些属性和方法请读者自行参照相关书籍。这里需要提请注意的是,Recordset控件与第一、二种方法中介绍的ADO的Recordset对象有些不同:Recordset控件没有直接给出pagesize和pagecount等属性,需要用下面介绍的方法来计算。
  
    第一步:拖Recordset控件到新建的网页emp5.htm中,名字为Recordset1,设置其属性,方法同第三种,此处略。
  
    第二步:定义三个全局变量和编写Recordset1的ondatasetcomplete(数据设置完成时)脚本。
  
  Dim gCurrentPageNumber //当前页号
  Dim gMaxPageNumber //最大页数
  Dim gRecordsPERPage //每页显示记录数
  gRecordsPerPage = 5 // 设置每页显示记录数为5条记录。
  
  Function Recordset1_ondatasetcomplete()
  totalRecordCount = Recordset1.getCount() //总的记录条数
  gMaxPageNumber = Int(totalRecordCount / gRecordsPerPage) //获得最大页数
  If (totalRecordCount Mod gRecordsPerPage) > 0 then
  gMaxPageNumber = gMaxPageNumber + 1
  End If
  End Function
  
    第三步:创建翻页导航按钮。
  
  Function btnFirst_onclick() ’ 翻到首页
   gCurrentPageNumber = 1
   DisplayData()
  End Function
  
  Function btnPrevious_onclick() ’ 翻到上一页
   if gCurrentPageNumber > 1 Then
    gCurrentPageNumber = gCurrentPageNumber - 1
    DisplayData()
   End If
  End Function
  
  Function btnNext_onclick() ’ 翻到下一页
   if gCurrentPageNumber < gMaxPageNumber Then
    gCurrentPageNumber = gCurrentPageNumber + 1
    DisplayData()
   End If
  End Function
  
  Function btnLast_onclick() ’翻到末页
   gCurrentPageNumber = gMaxPageNumber
   DisplayData()
  End Function
  
    第四步:编写显示每一页的函数。其中使用了许多DHTML的属性和方法,请读者自行参考相关书籍。
  
  Sub DisplayData()
  startRecord = ((gCurrentPageNumber - 1) * gRecordsPerPage) + 1 //计算每一页开始显示的记录号数(位置,第几条)
  rowCtr = 1
  lblPageNumber.innerHTML = gCurrentPageNumber & "/" & gMaxPageNumber
  For recordPtr = startRecord To (startRecord + gRecordsPerPage - 1) //循环显示一页的各条记录
   If recordPtr > Recordset1.getCount() Then //显示空表
    Table1.rows(rowCtr).cells(0).innerHTML = "<P> </P>"
    Table1.rows(rowCtr).cells(1).innerHTML = "<P> </P>"
    Table1.rows(rowCtr).cells(2).innerHTML = "<P> </P>"
    Table1.rows(rowCtr).cells(3).innerHTML = "<P> </P>"
   Else //具体显示每一页
    Recordset1.moveAbsolute(recordPtr) //移动记录指针。
    empID = Recordset1.fields.getValue("emp ID")
    empLName = Recordset1.fields.getValue("first name")
    empFName = Recordset1.fields.getValue("last name")
  
    Table1.rows(rowCtr).cells(0).innerText = recordPtr ’ Counter
    Table1.rows(rowCtr).cells(1).innerText = empID
    Table1.rows(rowCtr).cells(2).innerText = empLName
    Table1.rows(rowCtr).cells(3).innerText = empFName
   End If
   rowCtr = rowCtr + 1
  Next
  End Sub
  
    另外,我们还需要在window对象的onload事件中编写如下脚本:
  
  For rowCtr = 1 to gRecordsPerPage
   Table1.insertRow(rowCtr) ’ 插一新列
   For cellCtr = 0 to 3
    Table1.rows(rowCtr).insertCell()
   Next
  Next
  
    第六种:服务器端控制翻页方法。
  
    如果我们在服务器端对数据进行分页形成HTML语句后再输出到客户端,就不会存在浏览器不支持DHTML的问题了。可是用服务器端法使得我们每次翻页时,都得让Recordset控件重新产生一次,因此速度肯定要比用DHTML的方法慢。但如果服务器足够快的话,这点慢客户是察觉不到的。
  
    下面的例子中,我将介绍一个新的DTC控件:PageObject。这个控件使被指定的网页成为一个网页对象,用户在此网页的服务器脚本中组织的子程序和函数可被看作是该网页对象的方法。它提供了管理状态信息的一种先进的方法:网页对象有一些属性(变量),用户可以定义这些属性的生存期。因为以上这些特性,使我们在编制翻页的脚本时非常方便。
  
    但这种方法的缺点是:当你按了“上页”或“下页”按钮后,再浏览器上的按刷新按钮,网页会自动翻页。另外,如果按了浏览器上的“回退”按钮后,再按翻页按钮,可能会出现一次乱翻。这都是因为网页对象属性(全局变量)造成的。
  
    第一步:拖Recordset控件到新建的网页emp6.ASP中,名字为Recordset1,设置其属性,方法同第三种,此处略。
  
    第二步:拖PageObject控件到网页中,取名叫emplist。然后右键单击此控件打开属性页并设置MaxPageNumber,RecordsPerPage,CurrrentPageNumber三个属性(全局变量)。VI6.0可用get和set方法来读写它们的值,具体用法请查阅相关资料。
  
    第三步:编写Recordset1的ondatasetcomplete事件。
  
  Function Recordset1_ondatasetcomplete()
  recordsPerPage = 5
  empList.setRecordsPerPage(recordsPerPage)//设置网页对象每页记录条数属性为5
  totalRecordCount = Recordset1.getCount()//获得记录集的总条数
  mpn = Int(totalRecordCount / recordsPerPage) //计算出mpn为总页数
  If (totalRecordCount Mod recordsPerPage) > 0 then
  mpn = mpn + 1
  End If
  empList.setMaxPageNumber(mpn)
  End Function
  
    第四步:拖四个button控件到网页中,编写翻页控制脚本。我们主要是通过改变网页对象的CurrentPageNumber属性的值来实现翻页。
  
  Function btnFirst_onclick()’ 翻到首页
   empList.setCurrentPageNumber(1)
  End Function
  
  Function btnPrevious_onclick()’ 翻到上一页
   cpn = empList.getCurrentPageNumber()
   if cpn > 1 Then
    empList.setCurrentPageNumber(cpn - 1)
   End If
  End Function
  
  Function btnNext_onclick()’ 翻到下一页
   cpn = empList.getCurrentPageNumber()
   if cpn < empList.getMaxPageNumber() then
    empList.setCurrentPageNumber(cpn + 1)
   End If
  End Function
  
  Function btnLast_onclick() ’ 翻到末页
   empList.setCurrentPageNumber( empList.getMaxPageNumber() )
  End Function
  
    为保证首次进入该页时,显示的是第一页,我们还得编写该网页对象的onEnter事件。
  
  Function empList_onEnter()
   If empList.firstEntered Then
    empList.setCurrentPageNumber(1)
   End If
  End Function
  
    第五步:编写显示每一页的脚本。
  
  <HR><TABLE BORDER=0><TR>//显示表头
  <TH ALIGN="left" WIDTH=35></TH>
  <TH ALIGN="left" WIDTH=150>Emp ID</TH>
  <TH ALIGN="left" WIDTH=200>Last Name</TH>
  <TH ALIGN="left" WIDTH=200>First Name</TH></TR>
  <%
  pageNumber = empList.getCurrentPageNumber()//计算翻页所需的各种参数,同DHTML法二
  recordsPerPage = empList.getRecordsPerPage()
  startRecord = ((pageNumber - 1) * recordsPerPage) + 1
  lastRecord = Recordset1.getCount()
  For recordPtr = startRecord To (startRecord + recordsPerPage - 1)%>
  <%If Recordset1.EOF = True Then%>
  <TR>
  <TD> </TD>
  <TD> </TD>
  <TD> </TD>
  <TD> </TD>
  </TR>
  <%Else%>
  <%Recordset1.moveAbsolute(recordPtr)%>
  <TR>
  <% If recordPtr <= lastRecord Then %>
  <TD><%=recordptr%></TD>
  <%Else%>
  <TD> </TD>
  <% End If %>
  <TD><%=Recordset1.fields.getValue("emp ID")%></TD>
  <TD><%=Recordset1.fields.getValue("last name")%></TD>
  <TD><%=Recordset1.fields.getValue("first name")%></TD>
  </TR>
  <%End If%>
  <%Next%>
  </TABLE><HR>
  
    第七种:Data Environment Object Model(数据环境对象模型)法
  
    Data Environment对象模型把ADO对象模型及它的对象----“Connection”,“Command”,“Recordset”,“Field”以及“Parameter”对象----抽象到一个更加容易的表单中。Data Environment Object Model把命令显露为方法。用户可以调用这些方法,这些方法会执行这些命令并返回所得到的记录集。关于DEOM对象模型详细资料请参考相关书籍。我们来看下面网页emp7.asp的例子:
  
    第一步:在VI6.0的“project Explorer”窗口中的工程项目上右击鼠标并从弹出式菜单选择“Add Data Connection”。根据VI给出的导航提示建立一个到数据库的连接之后,用户就添加了一个实现从ASP应用程序访问数据库的数据命令。同时,你将会在“Project Explorer”窗口中的global.asa文件下方看到一个“Data Environment”对象。
  
    第二步:右击“Data Environment”对象然后从弹出式菜单中选择“Add Data Command”选项,添加一个数据命令Command1。根据VI6.0的导航提示,你可以在Command1 Properties弹出窗口的Genetal页中选SQL Statement,输入:select * from emp。按OK返回。
  
    第三步:你创建了这个数据命令后,就已经为该Data Environment对象创建了一个方法,然后就可以从脚本中调用这个方法,而且该方法将会给用户返回一个记录集。
  
  thisPage.createDE() //在SOM模式下,thisPage表示当前网页对象,createDE()方法创建了DE对象。
  DE.Command1//执行DE对象的命令,后面可代参数,做有条件查询时很有用。
  Set rs=DE.rsCommand1//DE.rscommand1使得rs对象完全等同于一个ADO的Recordset对象。
  
    第四步:因为rs为ADO对象,所以,以下的实现翻页代码完全参照以上介绍的几种方法,此处略过。
  
    其它还有如FrontPage2000的数据库导航中实现的方法等,因与本主题无关,此处略。
  
    综上所述,前面介绍的每种方法都包含了很多新的技术,由于篇幅的关系,无法深入。本文只是想通过实现翻页这一具体的例子来介绍ASP网页编程的多种方法;让大家亲身体验一下VI6.0在编制网页中的强大功能;了解和熟悉微软在网页编程中提出的ADO、DHTML、DTC控件、SOM对象模型和DEOM对象模型的使用方法;希望能给大家在编制网页时提供更多的选择和参考。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
0. 下载: 本程序可自由修改, 自由分发, 可在http://download.csdn.net/user/lgg201下载 1. 分页的需求 信息的操纵和检索是当下互联网和企业信息系统承担的主要责任. 信息检索是从大量的数据中找到符合条件的数据以用户界面展现给用户. 符合条件的数据通常会有成千上万条, 而用户的单次信息接受量是很小的, 因此, 如果一次将所有符合用户条件的数据展现给用户, 对于多数场景, 其中大部分数据都是冗余的. 信息检索完成后, 是需要经过传输(从存储介质到应用程序)和相关计算(业务逻辑)的, 因此, 我们需要一种分段的信息检索机制来降低这种冗余. 分页应运而生. 2. 分页的发展 基本的分页程序, 将数据按照每页记录数(page_size)将数据分为ceil(total_record / page_size)页, 第一次为用户展现第一段的数据, 后续的交互过程中, 用户可以选择到某一页对数据进行审阅. 后来, 主要是在微博应用出现后, 由于其信息变化很快, 而其特性为基于时间线增加数据, 这样, 基本的分页程序不能再满足需求了: a) 当获取下一页时, 数据集可能已经发生了很多变化, 翻页随时都可能导致数据重复或跳跃; b) 此类应用采用很多采用一屏展示多段数据的用户界面, 更加加重了数据重复/跳跃对用户体验的影响. 因此, 程序员们开始使用since_id的方式, 将下一次获取数据的点记录下来, 已减轻上述弊端. 在同一个用户界面, 通过用户阅读行为自动获取下一段/上一段数据的确比点击"下一页"按钮的用户体验要好, 但同样有弊端: a) 当用户已经到第100页时, 他要回到刚才感兴趣的第5页的信息时, 并不是很容易, 这其实是一条设计应用的规则, 我们不能让用户界面的单页屏数过多, 这样会降低用户体验; b) 单从数据角度看, 我们多次读取之间的间隔时间足够让数据发生一些变化, 在一次只展示一屏时, 我们很难发现这些问题(因此不影响用户体验), 然而当一页展示100屏数据时, 这种变化会被放大, 此时, 数据重复/跳跃的问题就会再次出现; c) 从程序的角度看, 将大量的数据放置在同一个用户界面, 必然导致用户界面的程序逻辑受到影响. 基于以上考虑, 目前应用已经开始对分页进行修正, 将一页所展示的屏数进行的限制, 同时加入了页码的概念, 另外也结合since_id的方式, 以达到用户体验最优, 同时保证数据逻辑的正确性(降低误差). 3. 分页的讨论 感谢xp/jp/zq/lw四位同事的讨论, 基于多次讨论, 我们分析了分页程序的本质. 主要的结论点如下: 1) 分页的目的是为了分段读取数据 2) 能够进行分页的数据一定是有序的, 哪怕他是依赖数据库存储顺序. (这一点换一种说法更容易理解: 当数据集没有发生变化时, 同样的输入, 多次执行, 得到的输出顺序保持不变) 3) 所有的分段式数据读取, 要完全保证数据集的一致性, 必须保证数据集顺序的一致性, 即快照 4) 传统的分页, 分段式分页(每页内分为多段)归根结底是对数据集做一次切割, 映射到mysql的sql语法上, 就是根据输入求得limit子句, 适用场景为数据集变化频率低 5) since_id类分页, 其本质是假定已有数据无变化, 将数据集的某一个点的id(在数据集中可以绝对定位该数据的相关字段)提供给用户侧, 每次携带该id读取相应位置的数据, 以此模拟快照, 使用场景为数据集历史数据变化频率低, 新增数据频繁 6) 如果存在一个快照系统, 能够为每一个会话发起时的数据集产生一份快照数据, 那么一切问题都迎刃而解 7) 在没有快照系统的时候, 我们可以用since_id的方式限定数据范围, 模拟快照系统, 可以解决大多数问题 8) 要使用since_id方式模拟快照, 其数据集排序规则必须有能够唯一标识其每一个数据的字段(可能是复合的) 4. 实现思路 1) 提供SQL的转换函数 2) 支持分段式分页(page, page_ping, ping, ping_size), 传统分页(page, page_size), 原始分页(offset-count), since_id分页(prev_id, next_id) 3) 分段式分页, 传统分页, 原始分页在底层均转换为原始分页处理 5. 实现定义 ping_to_offset 输入: page #请求页码, 范围: [1, total_page], 超过范围以边界计, 即0修正为1, total_page + 1修正为total_page ping #请求段号, 范围: [1, page_ping], 超过范围以边界计, 即0修正为1, page_ping + 1修正为page_ping page_ping #每页分段数, 范围: [1, 无穷] count #要获取的记录数, 当前应用场景含义为: 每段记录数, 范围: [1, 无穷] total_record #总记录数, 范围: [1, 无穷] 输出: offset #偏移量 count #读取条数 offset_to_ping 输入: offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] page_ping #每页分段数, 范围: [1, 无穷] count #读取条数, 范围: [1, 无穷] 输出: page #请求页码 ping #请求段号 page_ping #每页分段数 count #要获取的记录数, 当前应用场景含义为: 每段记录数 page_to_offset 输入: page #请求页码, 范围: [1, total_page], 超过范围以边界计, 即0修正为1, total_page + 1修正为total_page total_record #总记录数, 范围: [1, 无穷] count #要获取的记录数, 当前应用场景含义为: 每页条数, 范围: [1, 无穷] 输出: offset #偏移量 count #读取条数 offset_to_page 输入: offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #读取条数, 范围: [1, 无穷] 输出: page #请求页码 count #要获取的记录数, 当前应用场景含义为: 每页条数 sql_parser #将符合mysql语法规范的SQL语句解析得到各个组件 输入: sql #要解析的sql语句 输出: sql_components #SQL解析后的字段 sql_restore #将SQL语句组件集转换为SQL语句 输入: sql_components #要还原的SQL语句组件集 输出: sql #还原后的SQL语句 sql_to_count #将符合mysql语法规范的SELECT语句转换为获取计数 输入: sql_components #要转换为查询计数的SQL语句组件集 alias #计数字段的别名 输出: sql_components #转换后的查询计数SQL语句组件集 sql_add_offset 输入: sql_components #要增加偏移的SQL语句组件集, 不允许存在LIMIT组件 offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #要获取的记录数, 范围: [1, 无穷] 输出: sql_components #已增加LIMIT组件的SQL语句组件集 sql_add_since #增加since_id式的范围 输入: sql_components #要增加范围限定的SQL语句组件集 prev_id #标记上一次请求得到的数据左边界 next_id #标记上一次请求得到的数据右边界 输出: sql_components #增加since_id模拟快照的范围限定后的SQL语句组件集 datas_boundary #获取当前数据集的边界 输入: sql_components #要读取的数据集对应的SQL语句组件集 datas #结果数据集 输出: prev_id #当前数据集左边界 next_id #当前数据集右边界 mysql_paginate_query #执行分页支持的SQL语句 输入: sql #要执行的业务SQL语句 offset #偏移量(必须按照count对齐, 即可以被count整除), 范围: [0, 无穷] count #读取条数, 范围: [1, 无穷] prev_id #标记上一次请求得到的数据左边界 next_id #标记上一次请求得到的数据右边界 输出: datas #查询结果集 offset #偏移量 count #读取条数 prev_id #当前数据集的左边界 next_id #当前数据集的右边界 6. 实现的执行流程 分段式分页应用(page, ping, page_ping, count): total_record = sql_to_count(sql); (offset, count) = ping_to_offset(page, ping, page_ping, count, total_record) (datas, offset, count) = mysql_paginate_query(sql, offset, count, NULL, NULL); (page, ping, page_ping, total_record, count) = offset_to_ping(offset, page_ping, count, total_record); return (datas, page, ping, page_ping, total_record, count); 传统分页应用(page, count): total_record = sql_to_count(sql); (offset, count) = page_to_offset(page, count, total_record) (datas, offset, count) = mysql_paginate_query(sql, offset, count, NULL, NULL); (page, total_record, count) = offset_to_page(offset, count, total_record); return (datas, page, total_record, count); since_id分页应用(count, prev_id, next_id): total_record = sql_to_count(sql); (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, NULL, count, prev_id, next_id); return (count, prev_id, next_id); 复合型分段式分页应用(page, ping, page_ping, count, prev_id, next_id): total_record = sql_to_count(sql); (offset, count) = ping_to_offset(page, ping, page_ping, count, total_record) (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, offset, count, prev_id, next_id); (page, ping, page_ping, total_record, count) = offset_to_ping(offset, page_ping, count, total_record); return (datas, page, ping, page_ping, total_record, count, prev_id, next_id); 复合型传统分页应用(page, count, prev_id, next_id): total_record = sql_to_count(sql); (offset, count) = page_to_offset(page, count, total_record) (datas, offset, count, prev_id, next_id) = mysql_paginate_query(sql, offset, count, prev_id, next_id); (page, total_record, count) = offset_to_page(offset, count, total_record); return (datas, page, total_record, count, prev_id, next_id); mysql_paginate_query(sql, offset, count, prev_id, next_id) need_offset = is_null(offset); need_since = is_null(prev_id) || is_null(next_id); sql_components = sql_parser(sql); if ( need_offset ) : sql_components = sql_add_offset(sql_components, offset, count); endif if ( need_since ) : sql_components = sql_add_since(sql_components, prev_id, next_id); endif sql = sql_restore(sql_components); datas = mysql_execute(sql); (prev_id, next_id) = datas_boundary(sql_components, datas); ret = (datas); if ( need_offset ) : append(ret, offset, count); endif if ( need_since ) : append(ret, prev_id, next_id); endif return (ret); 7. 测试点 1) 传统分页 2) 分段分页 3) 原始分页 4) since_id分页 5) 复合型传统分页 6) 复合型分段分页 7) 复合型原始分页 8. 测试数据构建 DROP DATABASE IF EXISTS `paginate_test`; CREATE DATABASE IF NOT EXISTS `paginate_test`; USE `paginate_test`; DROP TABLE IF EXISTS `feed`; CREATE TABLE IF NOT EXISTS `feed` ( `feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', `ctime` INT NOT NULL COMMENT '微博创建时间', `content` CHAR(20) NOT NULL DEFAULT '' COMMENT '微博内容', `transpond_count` INT NOT NULL DEFAULT 0 COMMENT '微博转发数' ) COMMENT '微博表'; DROP TABLE IF EXISTS `comment`; CREATE TABLE IF NOT EXISTS `comment` ( `comment_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '评论ID', `content` CHAR(20) NOT NULL DEFAULT '' COMMENT '评论内容', `feed_id` INT NOT NUL COMMENT '被评论微博ID' ) COMMENT '评论表'; DROP TABLE IF EXISTS `hot`; CREATE TABLE IF NOT EXISTS `hot` ( `feed_id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '微博ID', `hot` INT NOT NULL DEFAULT 0 COMMENT '微博热度' ) COMMENT '热点微博表'; 9. 测试用例: 1) 搜索最热微博(SELECT f.feed_id, f.content, h.hot FROM feed AS f JOIN hot AS h ON f.feed_id = h.feed_id ORDER BY hhot DESC, f.feed_id DESC) 2) 搜索热评微博(SELECT f.feed_id, f.content, COUNT(c.*) AS count FROM feed AS f JOIN comment AS c ON f.feed_id = c.feed_id GROUP BY c.feed_id ORDER BY count DESC, f.feed_id DESC) 3) 搜索热转微博(SELECT feed_id, content, transpond_count FROM feed ORDER BY transpond_count DESC, feed_id DESC) 4) 上面3种场景均测试7个测试点 10. 文件列表 readme.txt 当前您正在阅读的开发文档 page.lib.php 分页程序库 test_base.php 单元测试基础函数 test_convert.php 不同分页之间的转换单元测试 test_parse.php SQL语句解析测试 test_page.php 分页测试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值