如何防止一次加载完整的数据库递归树?
How to prevent loading a full database recursive tree at once?
我正在尝试使用 PrimeFaces 库中的 Tree 组件,但由于数据量很大,我不得不延迟加载它。我按照教程进行操作,(http://blog.disy.net/primefaces-lazy-tree/) 但加载需要 15 秒(而不是 23 秒)。我是否在本教程或我的代码中遗漏了什么?我不能让它那样,因为 15 秒对用户来说太长了。
代码:
public interface TreePodeFindWithParent {
List<VArboParObjectifsParents> findActiviteWithParent(Integer parentId);
}
延迟加载中的方法class
public class PodeLazyTreeNode extends DefaultTreeNode
{
...
...
public PodeLazyTreeNode(VArboParObjectifsParents data, TreePodeService service)
{
super(vArboParObjectifsParents.class.getSimpleName(), data, null);
this.service = service;
}
...
private void ensureChildrenFetched()
{
if (!childrenFetched)
{
childrenFetched = true;
if ((VArboParObjectifsParents)getData() != null)
{
Integer parentId = ((VArboParObjectifsParents)getData()).getIdRoot();
List<PodeLazyTreeNode> childNodes = service.findActiviteWithParent(parentId).stream().map(item
-> new PodeLazyTreeNode(item, service)).collect(Collectors.toList());
super.getChildren().addAll(childNodes);
}
}
}
服务中的方法
public class TreePodeService implements Serializable, TreePodeWithParent
{
...
...
@Inject
private PodeArboParObjectifsParentsDao podeArboObjParentDao;
...
@Override
public List<VArboParObjectifsParents> findActiviteWithParent(Integer parentId) {
// TODO Auto-generated method stub
return podeArboObjParentDao.findByIdParent(parentId);
}
DAO(请求通过 Apache DeltaSpike 的数据模块完成):
@Repository(forEntity=VArboParObjectifsParents.class)
public interface PodeArboParObjectifsParentsDao extends EntityRepository<VArboParObjectifsParents, Integer>
{
List<VArboParObjectifsParents> findByIdParent(Integer idParent);
List<VArboParObjetcifsParents> findByIdTypeActivite(Integer idType);
}
视图中方法的调用:
@PostConstruct
public void initView()
{
initArbo();
}
public void initArbo()
{
List<VArboParObjectifsParents> vArbos = treePodeService.getPodeArboObjParentDao().findByIdTypeActivite(1);
this.root = new PodeLazyTreeNode(null, treePodeService);
for (int i = 0; i < vArbos.size(); i++)
{
root.getChildren().add(new PodeLazyTreeNode(vArbos.get(i), treePodeService));
}
}
UI:
<p:tree value="#{testView.root}" var="_node" >
<p:treeNode type="VArboParObjectifsParents">
<h:outputText value="#{_node}"/>
</p:treeNode>
</p:tree>
问题原因
来自 PrimeFaces Showcase:
Tree has two modes, in client mode all nodes are available at client side whereas in ajax mode only expanded nodes are available.
这意味着在树可见之前,树的所有节点都加载到浏览器中。您拥有的节点越多,加载整棵树所需的服务调用就越多。页面上也提示你linked:
n+1 Problem
Our implementation has so far been very simple. However, it does put a lot of stress on the BackendService. Whenever a node is expanded a call to BackendService.findWithParent(...) is made for each child of the expanded node. This is called the n+1-Problem, sinces you need n+1 service calls to provide the expanded node.
解决方案
通过设置dynamic="true"
(和可选的cache="false"
总是重新加载子节点)使用树的ajax模式,并通过调用监听器加载/移除子节点视图。
另外请不要通过构造函数指定服务。我认为您找到的教程非常糟糕。将服务注入支持 bean 并在那里加载节点数据。
示例:
Facelet (UI)
<p:tree cache="false"
dynamic="true"
value="#{testView.root}" var="_node">
<!--load/update nodes on demand-->
<p:ajax event="expand"
listener="#{testView.onNodeExpand}" />
<p:ajax event="collapse"
listener="#{testView.onNodeCollapse}" />
<p:treeNode type="VArboParObjectifsParents">
<h:outputText value="#{_node}"/>
</p:treeNode>
</p:tree>
支持 Bean(视图)
@Named
@ViewScoped
public TestView implements Serializable {
@PostConstruct
public void initView()
{
initArbo();
}
public void initArbo()
{
List<VArboParObjectifsParents> vArbos = treePodeService.getPodeArboObjParentDao().findByIdTypeActivite(1);
this.root = new PodeLazyTreeNode(null);
for (int i = 0; i < vArbos.size(); i++)
{
PodeLazyTreeNode childNode = new PodeLazyTreeNode(vArbos.get(i), this.root);
addDummyTreeNode(childNode);
}
}
public void onNodeExpand(NodeExpandEvent event) {
final TreeNode expandedTreeNode = event.getTreeNode();
// load child data from service
// ...
if (/* child data available */) {
// create child tree nodes with dummy tree node
// ...
expandedTreeNode.setExpanded(true);
} else {
// remove dummy tree nod to mark as leaf
// ...
expandedTreeNode.setExpanded(false);
}
}
public void onNodeCollapse(NodeCollapseEvent event) {
final TreeNode collapsedTreeNode = event.getTreeNode();
// remove child nodes and add dummy tree node
// ...
collapsedTreeNode.setExpanded(false);
}
/**
* Adds a dummy {@link TreeNode} to the specified parent node. The dummy is
* used to prevent nodes with child nodes from being rendered as leaf nodes
* until they are expanded. The dummy will be removed when the parent node
* is expanded the first time.
*
* @param parentTreeNode
*/
private void addDummyTreeNode(TreeNode parentTreeNode) {
final TreeNode dummyNode = new DefaultTreeNode("Loading...",
parentTreeNode);
}
}
我正在尝试使用 PrimeFaces 库中的 Tree 组件,但由于数据量很大,我不得不延迟加载它。我按照教程进行操作,(http://blog.disy.net/primefaces-lazy-tree/) 但加载需要 15 秒(而不是 23 秒)。我是否在本教程或我的代码中遗漏了什么?我不能让它那样,因为 15 秒对用户来说太长了。
代码:
public interface TreePodeFindWithParent {
List<VArboParObjectifsParents> findActiviteWithParent(Integer parentId);
}
延迟加载中的方法class
public class PodeLazyTreeNode extends DefaultTreeNode
{
...
...
public PodeLazyTreeNode(VArboParObjectifsParents data, TreePodeService service)
{
super(vArboParObjectifsParents.class.getSimpleName(), data, null);
this.service = service;
}
...
private void ensureChildrenFetched()
{
if (!childrenFetched)
{
childrenFetched = true;
if ((VArboParObjectifsParents)getData() != null)
{
Integer parentId = ((VArboParObjectifsParents)getData()).getIdRoot();
List<PodeLazyTreeNode> childNodes = service.findActiviteWithParent(parentId).stream().map(item
-> new PodeLazyTreeNode(item, service)).collect(Collectors.toList());
super.getChildren().addAll(childNodes);
}
}
}
服务中的方法
public class TreePodeService implements Serializable, TreePodeWithParent
{
...
...
@Inject
private PodeArboParObjectifsParentsDao podeArboObjParentDao;
...
@Override
public List<VArboParObjectifsParents> findActiviteWithParent(Integer parentId) {
// TODO Auto-generated method stub
return podeArboObjParentDao.findByIdParent(parentId);
}
DAO(请求通过 Apache DeltaSpike 的数据模块完成):
@Repository(forEntity=VArboParObjectifsParents.class)
public interface PodeArboParObjectifsParentsDao extends EntityRepository<VArboParObjectifsParents, Integer>
{
List<VArboParObjectifsParents> findByIdParent(Integer idParent);
List<VArboParObjetcifsParents> findByIdTypeActivite(Integer idType);
}
视图中方法的调用:
@PostConstruct
public void initView()
{
initArbo();
}
public void initArbo()
{
List<VArboParObjectifsParents> vArbos = treePodeService.getPodeArboObjParentDao().findByIdTypeActivite(1);
this.root = new PodeLazyTreeNode(null, treePodeService);
for (int i = 0; i < vArbos.size(); i++)
{
root.getChildren().add(new PodeLazyTreeNode(vArbos.get(i), treePodeService));
}
}
UI:
<p:tree value="#{testView.root}" var="_node" >
<p:treeNode type="VArboParObjectifsParents">
<h:outputText value="#{_node}"/>
</p:treeNode>
</p:tree>
问题原因
来自 PrimeFaces Showcase:
Tree has two modes, in client mode all nodes are available at client side whereas in ajax mode only expanded nodes are available.
这意味着在树可见之前,树的所有节点都加载到浏览器中。您拥有的节点越多,加载整棵树所需的服务调用就越多。页面上也提示你linked:
n+1 Problem
Our implementation has so far been very simple. However, it does put a lot of stress on the BackendService. Whenever a node is expanded a call to BackendService.findWithParent(...) is made for each child of the expanded node. This is called the n+1-Problem, sinces you need n+1 service calls to provide the expanded node.
解决方案
通过设置dynamic="true"
(和可选的cache="false"
总是重新加载子节点)使用树的ajax模式,并通过调用监听器加载/移除子节点视图。
另外请不要通过构造函数指定服务。我认为您找到的教程非常糟糕。将服务注入支持 bean 并在那里加载节点数据。
示例:
Facelet (UI)
<p:tree cache="false"
dynamic="true"
value="#{testView.root}" var="_node">
<!--load/update nodes on demand-->
<p:ajax event="expand"
listener="#{testView.onNodeExpand}" />
<p:ajax event="collapse"
listener="#{testView.onNodeCollapse}" />
<p:treeNode type="VArboParObjectifsParents">
<h:outputText value="#{_node}"/>
</p:treeNode>
</p:tree>
支持 Bean(视图)
@Named
@ViewScoped
public TestView implements Serializable {
@PostConstruct
public void initView()
{
initArbo();
}
public void initArbo()
{
List<VArboParObjectifsParents> vArbos = treePodeService.getPodeArboObjParentDao().findByIdTypeActivite(1);
this.root = new PodeLazyTreeNode(null);
for (int i = 0; i < vArbos.size(); i++)
{
PodeLazyTreeNode childNode = new PodeLazyTreeNode(vArbos.get(i), this.root);
addDummyTreeNode(childNode);
}
}
public void onNodeExpand(NodeExpandEvent event) {
final TreeNode expandedTreeNode = event.getTreeNode();
// load child data from service
// ...
if (/* child data available */) {
// create child tree nodes with dummy tree node
// ...
expandedTreeNode.setExpanded(true);
} else {
// remove dummy tree nod to mark as leaf
// ...
expandedTreeNode.setExpanded(false);
}
}
public void onNodeCollapse(NodeCollapseEvent event) {
final TreeNode collapsedTreeNode = event.getTreeNode();
// remove child nodes and add dummy tree node
// ...
collapsedTreeNode.setExpanded(false);
}
/**
* Adds a dummy {@link TreeNode} to the specified parent node. The dummy is
* used to prevent nodes with child nodes from being rendered as leaf nodes
* until they are expanded. The dummy will be removed when the parent node
* is expanded the first time.
*
* @param parentTreeNode
*/
private void addDummyTreeNode(TreeNode parentTreeNode) {
final TreeNode dummyNode = new DefaultTreeNode("Loading...",
parentTreeNode);
}
}