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

基于数据库的无限级树形结构后台(非递归遍历节点,通过增加维护节点PATH字段)
/**
 * 
 * @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);
	}
}
1 楼 yangzhichao 2008-03-29  
请问“新手帖”什么意思呢?
2 楼 yangzhichao 2008-03-29  
估计是不是少了代码说明了,只有长长的代码?
3 楼 yangzhichao 2008-03-29  
明白了,是不是已经有人讨论过了。
但是好像看其他的查找实现都是基于递归的的实现,我这个是通过增加path字段来保存
节点的路径,路径结构类似为“-ID1-ID2-ID3-”,遍历子节点或者判断上下级关系直接使用PATH字段的LIKE操作就可以