`
yangzhichao
  • 浏览: 13418 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
最近访客 更多访客>>
社区版块
存档分类

基于数据库的无限级树形结构后台(非递归遍历节点,通过增加维护节点PATH字段)

    博客分类:
  • java
阅读更多
/**
 * 
 * @author yangzhichao
 *
 * @param <PK>
 * @param <T>
 */
public interface TreeNode<PK extends Number, T extends TreeNode<PK, T>> extends
		Serializable {
	public static final String DEFAULT_PATH_SEPARATOR = "-";

	/**
	 * 节点标识
	 * 
	 * @return
	 */
	PK getId();

	/**
	 * 节点名字
	 * 
	 * @return
	 */
	String getName();

	/**
	 * 获取直接父节点对象
	 * 
	 * @return 直接上级对象
	 */
	T getParent();

	/**
	 * 设置父节点
	 * 
	 * @param t
	 */
	void setParent(T t);

	/**
	 * 获取路径
	 * 
	 * @return
	 */
	String getPath();

	/**
	 * 
	 * @param path
	 * @return
	 */
	void setPath(String path);

	/**
	 * 是否为叶节点
	 * 
	 * @return 返回所有叶节点
	 */
	boolean isLeaf();

	/**
	 * 判断是否为指定节点的父节点
	 * 
	 * @param child
	 *            被判断的节点
	 * @return
	 */
	boolean isParentOf(T child);

	/**
	 * 判断是否为指定节点的子节点
	 * 
	 * @param parent
	 *            被判断的节点对象
	 * @return
	 */
	boolean isChildOf(T parent);

	/**
	 * 节点是否有效
	 * 
	 * @return
	 */
	boolean isValid();

	/**
	 * 获取子节点
	 * 
	 * @return
	 */
	Collection<T> getChildren();

	/**
	 * 节点类别
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	Class getClazz();

	/**
	 * 状态
	 * 
	 * @return
	 */
	int getStatus();
}


/**
 * 树型抽象实现
 * 
 * @author yangzhichao
 * 
 */
public abstract class AbstractTreeNode<PK extends Number, T extends AbstractTreeNode<PK, T>>
		implements TreeNode<PK, T> {
	/**
	 * 标识
	 */
	private PK id;
	/**
	 * 名字
	 */
	private String name;
	/**
	 * 上级节点
	 */
	private T parent;
	/**
	 * 路径
	 */
	private String path;
	/**
	 * 直接子节点集合
	 */
	private Collection<T> children;
	/**
	 * 序号
	 */
	private int order;

	public boolean isChildOf(T parent) {
		return parent == null || this.getParent() == null ? false : this
				.getParent().equals(parent);
	}

	public boolean isParentOf(T child) {
		return child == null || child.getParent() == null ? false : this
				.equals(child.getParent());
	}

	/**
	 * 默认根据是否有子节点判断
	 */
	public boolean isLeaf() {
		return !(this.getChildren() != null && this.getChildren().size() > 0);
	}

	@SuppressWarnings("unchecked")
	public boolean equals(Object object) {
		if (!(object instanceof AbstractTreeNode)) {
			return false;
		}
		AbstractTreeNode o = (AbstractTreeNode) object;
		// 类型和ID要一致
		return new EqualsBuilder().append(this.getClazz().getName(),
				o.getClazz().getName()).append(this.getId(), o.getId())
				.isEquals();
	}

	public PK getId() {
		return id;
	}

	public void setId(PK id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public T getParent() {
		return parent;
	}

	public void setParent(T parent) {
		this.parent = parent;
	}

	public Collection<T> getChildren() {
		return children;
	}

	public void setChildren(Collection<T> children) {
		this.children = children;
	}

	public int getOrder() {
		return order;
	}

	public void setOrder(int order) {
		this.order = order;
	}
}


/**
 * 树型DAO接口
 * 
 * @author yangzhichao
 * 
 * @param <PK>
 * @param <T>
 */
public interface TreeNodeDao<PK extends Number, T extends TreeNode<PK, T>>
		extends BaseDao<PK, T> {
	/**
	 * 获取父亲节点
	 * 
	 * @param t
	 * @return
	 */
	T getParent(T t) throws DataAccessException;

	/**
	 * 获取直接子节点
	 * 
	 * @param t
	 * @return
	 * @throws DataAccessException
	 */
	Collection<T> getDirectChildren(T t) throws DataAccessException;

	/**
	 * 获取直接子枝干
	 * 
	 * @param t
	 * @return
	 */
	Collection<T> getDirectBranches(T t) throws DataAccessException;

	/**
	 * 获取直接叶子节点
	 * 
	 * @param t
	 * @return
	 */
	Collection<T> getDirectLeaves(T t) throws DataAccessException;

	/**
	 * 获取所有孩子
	 * 
	 * @param t
	 * @return
	 * @throws DataAccessException
	 */
	Collection<T> getAllChildren(T t) throws DataAccessException;

	/**
	 * 通过PATH获取所有子节点
	 * 
	 * @param t
	 * @return
	 * @throws DataAccessException
	 */
	Collection<T> getAllChildrenByPath(T t) throws DataAccessException;

	/**
	 * 通过PATH获取所有子枝干
	 * 
	 * @param t
	 * @return
	 * @throws DataAccessException
	 */
	Collection<T> getAllBranchesByPath(T t) throws DataAccessException;

	/**
	 * 通过PATH获取所有子叶子
	 * 
	 * @param t
	 * @return
	 * @throws DataAccessException
	 */
	Collection<T> getAllLeavesByPath(T t) throws DataAccessException;

	/**
	 * 通过PATH获取所有父节点
	 * 
	 * @param t
	 * @return
	 */
	Collection<T> getAllParentByPath(T t) throws DataAccessException;

	/**
	 * 通过PATH获取到ROOT节点为止(包含ROOT)的所有父亲节点
	 * 
	 * @param t
	 * @param root
	 * @return
	 */
	Collection<T> getAllParentByPath(T t, T root) throws DataAccessException;

	/**
	 * 更新所有path属性包含oldPath子串的节点的path属性的oldPath子串为newPath
	 * 
	 * @param oldPath
	 * @param newPath
	 * @throws DataAccessException
	 */
	void updatePath(Class<T> clazz, String oldPath, String newPath)
			throws DataAccessException;
}


/**
 * 
 * @author yangzhichao
 * 
 * @param <PK>
 * @param <T>
 */
public abstract class HibernateTreeNodeDao<PK extends Number, T extends TreeNode<PK, T>>
		extends SpringHibernateDao<PK, T> implements TreeNodeDao<PK, T> {

	/**
	 * 根据ID字符串和类别获取对象
	 * 
	 * @param id
	 * @param c
	 * @return
	 */
	protected abstract T get(String id, Class<T> c);

	@SuppressWarnings("unchecked")
	public Collection<T> getAllParentByPath(T t) {
		ArrayList<T> parents = new ArrayList<T>();
		for (String parentID : t.getPath().trim().split("-")) {
			if (parentID != null && !parentID.trim().equals("")
					&& !parentID.trim().equals("-")) {
				parents.add(this.get(parentID, (Class<T>) t.getClazz()));
			}
		}
		return parents;
	}

	@SuppressWarnings("unchecked")
	public Collection<T> getAllParentByPath(T t, T root) {
		ArrayList<T> parents = new ArrayList<T>();
		for (String parentID : t.getPath().trim().split("-")) {
			if (parentID != null && !parentID.trim().equals("")
					&& !parentID.trim().equals("-")) {
				parents.add(this.get(parentID, (Class<T>) t.getClazz()));
				if (root.getId().toString().equals(parentID.trim())) {
					break;
				}
			}
		}
		return parents;
	}

	public T getParent(T t) {
		return t.getParent();
	}

	/**
	 * 路径分隔符
	 */
	public String getPathSeparator() {
		return TreeNode.DEFAULT_PATH_SEPARATOR;
	}

	/**
	 * 设置节点t的路径
	 * 
	 * @param t
	 */
	private void setPath(T t) {
		// 设置路径
		if (t.getParent() == null) {
			t.setPath("-");
		} else {
			t.setPath(t.getParent().getPath() + t.getParent().getId()
					+ this.getPathSeparator());
		}
	}

	/**
	 * 插入
	 * 
	 * @throws
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = DataAccessException.class)
	public void insert(T t) throws DataAccessException {
		this.setPath(t);
		super.insert(t);
	}

	/**
	 * 更新
	 * 
	 * @throws
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = DataAccessException.class)
	public void update(T t) throws DataAccessException {
		boolean parentChanged = false;
		T old = this.get(t.getId(), t.getClazz());
		T oldParent = this.getParent(old);
		if (oldParent != null && !oldParent.equals(t.getParent())
				|| (oldParent == null && t.getParent() != null)) {
			// 上级节点改变
			parentChanged = true;
		}
		// 清除旧副本,不然保存时候会找到两个同一ID的对象会报错
		this.getHibernateTemplate().evict(old);
		if (parentChanged) {
			if (t.getParent() != null
					&& t.getParent().getPath() != null
					&& t.getParent().getPath().indexOf("-"+t.getId().toString()+"-") != -1) {
				throw new DataAccessException(this.getClass(),
						"不能选择子节点作为当前节点的父节点");
			}
			if (t.getParent() != null && t.getParent().equals(t)) {
				throw new DataAccessException(this.getClass(),
						"不能选择本身作为当前节点的父节点");
			}
			// 设置路径
			this.setPath(t);
			// 重新设置所有子节点路径
			this.updatePath(t.getClazz(), old.getPath(), t.getPath());
		}
		super.update(t);
	}

	/**
	 * 更新路径,当某节点上级节点改变时候调用
	 * 
	 * @throws DataAccessException
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED,rollbackFor = DataAccessException.class)
	public void updatePath(final Class<T> clazz, String oldPath, String newPath)
			throws DataAccessException {
		if (oldPath == null || oldPath.trim().length() < 1
				|| oldPath.trim().equals("-")) {
			return;
		}
		if (newPath == null || newPath.trim().length() < 1) {
			newPath = "-";
		}
		if (oldPath.charAt(0) != '-') {
			oldPath = "-" + oldPath;
		}
		if (oldPath.charAt(oldPath.length() - 1) != '-') {
			oldPath = oldPath + "-";
		}
		if (newPath.charAt(0) != '-') {
			newPath = "-" + newPath;
		}
		if (newPath.charAt(newPath.length() - 1) != '-') {
			newPath = newPath + "-";
		}
		final String newPathTemp = newPath;
		final String oldPathTemp = oldPath;
		try {
			this.getHibernateTemplate().execute(new HibernateCallback() {
				public Object doInHibernate(Session session)
						throws HibernateException, SQLException {
					StringBuilder hql = new StringBuilder();
					hql.append(" update ");
					hql.append(clazz.getName());
					hql.append(" o ");
					hql.append(" set o.path=replace(o.path,:oldPath");
					hql.append(",:newPath");
					hql.append(")");
					hql.append(" where o.path like :oldPathLike ");
					Query query = session.createQuery(hql.toString());
					query.setString("oldPath", oldPathTemp.trim()).setString(
							"newPath", newPathTemp.trim()).setString(
							"oldPathLike", "%" + oldPathTemp.trim() + "%")
							.executeUpdate();
					return null;
				}
			});
		} catch (Exception e1) {
			throw new DataAccessException(this.getClass(), "更新路径失败", e1);
		}
	}

	public Collection<T> getAllChildren(T t) throws DataAccessException {
		return this.getAllChildrenByPath(t);
	}

	public Collection<T> getAllChildrenByPath(T t) throws DataAccessException {
		HibernateCriteriaQuery query = new HibernateCriteriaQuery();
		DetachedCriteria dc = DetachedCriteria.forClass(t.getClazz());
		dc.add(Restrictions.like("path", "%-" + t.getId().toString() + "-%"));
		query.setQuery(dc);
		return this.find(query);
	}

	public Collection<T> getDirectChildren(T t) throws DataAccessException {
		HibernateCriteriaQuery query = new HibernateCriteriaQuery();
		DetachedCriteria dc = DetachedCriteria.forClass(t.getClazz());
		dc.add(Restrictions.eq("parent", t));
		query.setQuery(dc);
		return this.find(query);
	}
}
分享到:
评论
3 楼 yangzhichao 2008-03-29  
明白了,是不是已经有人讨论过了。
但是好像看其他的查找实现都是基于递归的的实现,我这个是通过增加path字段来保存
节点的路径,路径结构类似为“-ID1-ID2-ID3-”,遍历子节点或者判断上下级关系直接使用PATH字段的LIKE操作就可以
2 楼 yangzhichao 2008-03-29  
估计是不是少了代码说明了,只有长长的代码?
1 楼 yangzhichao 2008-03-29  
请问“新手帖”什么意思呢?

相关推荐

    数据库控制树形结构的生成

    这个表是树结构的基础,每个节点对应表中的一条记录。 2. **构建连接**:在节点表中,通过父节点ID字段关联不同节点,形成父子关系。这使得我们能够通过查询来构建树形结构。 3. **遍历与构建**:为了生成实际的树...

    Oracle递归树形结构查询功能

    例如,它可以将每个节点的路径作为字符串返回,便于理解和分析树结构。 在实际应用中,例如组织结构的展示,我们可以创建一个包含部门信息的表`SYS_DEPT`,其中`dept_id`为主键,`par_dept_id`表示父级部门ID,以此...

    树形结构数据库设计

    树形结构数据库设计模仿了自然界中的树状模型,每个节点代表一个数据项,可以有零个或多个子节点,而只有一个父节点(根节点除外)。这种结构非常适合表示层级关系,如组织架构、目录结构、产品分类等。在数据库中,...

    PHP无限级分类-非递归

    为了更高效地处理无限级分类,可以使用预计算(pre-calculated)的`path`字段,存储每个分类从根到当前节点的路径。例如,对于`id=4`且`parent_id=2`的分类,`path`可能为`1/2/4`。这样,我们可以快速定位到任意分类...

    PHP 数据库树的遍历方法

    通过递归的方式实现了树形结构的深度优先遍历,适用于多种场景下的数据处理需求。此外,还涉及到了环境配置、类文件加载、配置文件读取等实际开发中的常见问题。希望本文能帮助读者更好地理解和应用树形结构的遍历...

    asp树形目录带access

    - 树形结构通常由嵌套的HTML无序列表(`&lt;ul&gt;`和`&lt;li&gt;`)实现,通过递归函数或循环遍历数据库记录来生成层次结构。 - 使用JavaScript或VBScript动态生成HTML,根据父ID关系决定节点的嵌套层次。 - 可以使用CSS样式...

    layui树形菜单动态遍历的例子

    动态遍历树形菜单通常用于显示层级结构的数据,例如文件系统、组织结构或者权限管理等。 首先,我们来看前端部分。在jsp页面中,需要引入layui的CSS和JavaScript库,并且注册layui的`layer`和`tree`模块。`layer`...

    sql server递归子节点、父节点sql查询表结构的实例

    在SQL Server中,递归...通过CTE,我们可以有效地查询出层级关系中的子节点和父节点,同时利用系统视图和扩展属性来获取表的相关描述和结构信息。在实际工作中,熟练掌握这些技巧将对数据库管理和数据分析大有裨益。

    递归查询菜单树,支持mysql,oracle

    递归查询是一种在数据库中处理层次数据的方法,它通过自身调用来遍历层级结构。在菜单树场景中,每个菜单项可能有子菜单项,形成一种树状结构。通过递归查询,我们可以获取到所有级别的菜单项,包括它们的父级和子级...

    PHP+Mysql树型结构(无限分类)数据库设计的2种方式实例

    当树结构层次较深时,递归查询会导致大量的SQL操作,增加数据库负载,尤其是处理大规模的树结构时,这可能成为性能瓶颈,限制了树结构的扩展性。 接下来,我们看看预排序遍历树方式。MPTT通过在每个节点上增加两个...

    php 无限级分类 带分类路径

    1. **递归方式**:这是最常见的方法,通过递归函数遍历整个分类树,每次递归时为当前节点添加父节点的ID或名称,形成路径。这种方法简单直观,但当分类数量巨大时,可能会导致大量的函数调用,影响性能。 2. **栈...

    通过父编码信息给子节点分组

    在Oracle数据库环境中,"通过父编码信息给子节点分组"通常涉及到树形结构数据的处理,这在很多业务场景中都很常见,如组织架构、产品分类、地区层级等。这种问题的关键在于如何利用数据库的查询功能来构建并展现这种...

    C#无限级菜单绝对实用

    在桌面应用中,如WPF或WinForms,可以利用控件如TreeView,通过数据绑定和递归模板来生成无限级结构。 ```xml &lt;!-- WPF 示例 --&gt; HierarchicalDataTemplate x:Key="MenuItemTemplate"&gt; &lt;Binding Path=...

    VC++ 遍历文件夹自动生成目录树 VC++ 遍历文件夹自动生成目录树

    在VC++编程中,遍历文件夹并自动生成目录树是一项常见的任务,它涉及到文件系统操作和数据结构的使用。这个任务通常用于构建文件管理器、进行文件搜索或实现类似资源管理器的功能。下面我们将详细讲解如何在VC++中...

    php+mysql不用递归实现的无限级分类实例(非递归)

    通过设计这样的字段,我们可以利用数据库的关联查询,而不用编写递归函数来遍历分类结构。 接下来,文章中提供的操作代码展示了如何利用SQL语句来实现无限级分类的查询和展示: 1. 查询无限级分类并展示: ```php...

    树型框显示ACCESS数据库字段记录的例程

    树型框是一种常见的图形用户界面元素,它可以以层次结构显示数据,非常适合用来表示数据库中的表结构和记录。 在Visual Basic (VB) 或 Visual Studio .NET (C#、VB.NET)等环境中,我们可以利用ADO.NET库来连接和...

    php通过前序遍历树实现无需递归的无限极分类

    它利用数据库的`lft`和`rgt`字段来表示树结构,通过非递归的迭代处理,降低了内存消耗,提升了性能。在CodeIgniter框架下,这种实现方式尤其适合处理大型数据集,为开发人员提供了一种强大的工具来管理具有层次结构...

    无限级分类

    2. **路径枚举法**:在分类表中增加一个`path`字段,存储分类在整个树中的路径,如“0,1,3”表示第三级分类。查询时,根据路径快速定位到某个分类及其上下文。 在编程实现上,可以使用多种语言,如Java、Python、...

    树的应用-哈夫曼编码.pdf

    【哈夫曼编码】是一种基于二叉树的数据压缩方法,由数据结构中的“最小带权路径长度”(Minimum Weight Path Length, MWPL)原理构造。在哈夫曼编码中,出现频率较高的字符会得到较短的编码,而出现频率较低的字符则...

    遍历JSON文件内容

    在`traverseJson`方法中,我们递归地遍历JSON节点。如果是对象,我们遍历其字段;如果是数组,我们遍历其元素;其他情况则打印出节点的值。 4. **打印输出**:遍历过程中,我们可以根据需要对JSON数据进行处理,...

Global site tag (gtag.js) - Google Analytics