我正在设计一个多层数据库驱动的Web应用程序 - SQL关系数据库,用于中间服务层的Java,用于UI的Web。这种语言并不重要。
中间服务层执行数据库的实际查询。用户界面只是询问某些数据,并且没有任何数据支持它的概念。
问题是如何处理大型数据集?用户界面要求提供数据,但结果可能很大,可能太大而无法容纳在内存中。例如,街道标志应用程序可能具有以下服务层:
StreetSign getStreetSign(int identifier)
Collection<StreetSign> getStreetSigns(Street street)
Collection<StreetSign> getStreetSigns(LatLonBox box)
UI图层要求让所有街道标志符合某些标准。根据标准,结果集可能很大。 UI层可能会将结果划分为单独的页面(对于浏览器)或只是将它们全部呈现(为Goolge Earth提供服务)。潜在的巨大结果集可能是性能和资源问题(内存不足)。
一种解决方案不是返回完全加载的对象(StreetSign对象)。而是返回某种结果集或迭代器,它们懒洋洋地加载每个单独的对象。
另一种解决方案是更改服务API以返回所请求数据的子集:
Collection<StreetSign> getStreetSigns(LatLonBox box, int pageNumber, int resultsPerPage)
当然,UI仍然可以请求一个巨大的结果集:
getStreetSigns(box, 1, 1000000000)
我很好奇这个场景的标准行业设计模式是什么?
答案 0 :(得分:6)
第一个问题应该是:
¿用户需要或能够管理这么多数据?
虽然结果集应该被分页,但如果它的潜在大小如此巨大,答案将“可能不会”,因此UI不应该尝试显示它。
我参与了医疗保健系统的J2EE项目,该项目涉及大量存储数据,数百万患者,访问,表格等,一般规则是不要为任何用户显示超过100或200行搜索,建议用户这些标准可以产生更多他能理解的信息。
实现此方法的方式因项目而异,可以强制UI在启动之前向服务层询问查询的大小,或者如果有可能从服务层抛出异常,则可以结果集增长太多(但是这种方式将服务层与UI的有限实现相结合)。
小心!这并不意味着服务层上的每个方法如果其结果大小超过100就必须抛出异常,这个一般规则仅适用于直接向用户显示的结果集,这是将控件放在UI中的更好理由而是在服务层上。
答案 1 :(得分:2)
我在这种情况下看到的最常见的模式是某种寻呼,通常在服务器端完成,以减少通过线路发送的信息量。
这是一个使用表变量(通常比临时表更快)的SQL Server 2000示例以及街道标志示例:
CREATE PROCEDURE GetPagedStreetSigns
(
@Page int = 1,
@PageSize int = 10
)
AS
SET NOCOUNT ON
-- This memory-variable table will control paging
DECLARE @TempTable TABLE (RowNumber int identity, StreetSignId int)
INSERT INTO @TempTable
(
StreetSignId
)
SELECT [Id]
FROM StreetSign
ORDER BY [Id]
-- select only those rows belonging to the requested page
SELECT SS.*
FROM StreetSign SS
INNER JOIN @TempTable TT ON TT.StreetSignId = SS.[Id]
WHERE TT.RowNumber BETWEEN ((@Page - 1) * @PageSize + 1)
AND (@Page * @PageSize)
在SQL Server 2005中,您可以使用Common Table Expressions和新的SQL排名功能更加聪明。但一般主题是您使用服务器仅返回属于当前页面的信息。
请注意,如果您允许最终用户对他/她正在看到的数据应用即时过滤器,这种方法会变得混乱。
答案 2 :(得分:1)
我会说,如果潜在的大量数据存在,那么就去寻呼路由。
您仍然可以设置一个不希望它们过去的MAX。
E.G。因此,页面大小为15,30,50 ......
答案 3 :(得分:1)
在使用像你这样的本土行包装类(显然)时要警惕的一件事就是在没有你(开发人员)意识到的情况下对数据库进行额外调用的代码。例如,您可以调用一个返回Person对象集合的方法,并认为引擎盖下的唯一内容是单个“SELECT * FROM PERSONS”调用。实际上,您调用的方法可能遍历返回的Person对象集合,并进行额外的DB调用以填充每个Person的Orders集合。
正如您所说,您的一个解决方案是不返回完全加载的对象,因此您可能已经意识到这个潜在的问题。我倾向于避免使用行包装器的原因之一是它们总是难以调整应用程序并最小化数据库流量的大小和频率。
答案 4 :(得分:0)
在ASP.NET中,我将使用服务器端分页,您只能从数据存储中检索用户请求的数据页。这与检索整个结果集,将其放入内存并根据请求对其进行分页相反。
答案 5 :(得分:0)
JSF或JavaServerFaces具有用于将大型结果集分块到浏览器的小部件。它可以按照您的建议进行参数化。我不会以任何方式称之为“标准的行业设计模式”,但值得一看其他人如何解决问题。
答案 6 :(得分:0)
当我处理这类问题时,我通常会将发送到浏览器的数据(或瘦/胖客户端,无论哪种情况更适合您的情况)进行分块,而不管实际满足某些数据的数据总大小标准,一次只能在任何UI中使用一小部分。
我住在微软世界,所以我的主要环境是带有SQL Server的ASP.Net。这里有两篇关于分页的文章(提到了一些分页结果集的技术)可能会有所帮助:
Paging through lots of data efficiently (and in an Ajax way) with ASP.NET 2.0 Efficient Data Paging with the ASP.NET 2.0 DataList Control and ObjectDataSource
微软最近发布的另一个机制是他们的想法“Dynamic Data” - 您可以查看其中的内容,以获得有关他们如何处理此问题的一些指导。
答案 7 :(得分:0)
我在两种不同的产品上做过类似的事情。在一种情况下,数据源可选地是分页的 - 对于java,实现类似于:
的Pageable接口public interface Pageable
{
public void setStartIndex( int index );
public int getStartIndex();
public int getRowsPerPage() throws Exception;
public void setRowsPerPage( int rowsPerPage );
}
数据源实现了项的get()的另一种方法,并且分页数据源的实现只返回当前页面。因此,您可以设置起始索引,并在控制器中抓取一个页面。
要考虑的一件事是缓存游标服务器端。对于一个网络应用程序,你将不得不使它们过期,但它们确实有助于提高性能。
答案 8 :(得分:0)
fedora digital repository项目返回带有result-set-id的最大结果数。然后通过在后续查询中请求提供result-set-id的下一个块来获得结果的其余部分。只要您不想在查询之外进行任何搜索或排序,它就可以正常工作。
答案 9 :(得分:0)
从数据检索层,标准设计模式有两个方法接口,一个用于所有,一个用于块大小。
如果您愿意,可以对在其上进行分页的组件进行分层。