通用 DAO 和嵌套属性支持

Posted

技术标签:

【中文标题】通用 DAO 和嵌套属性支持【英文标题】:Generic DAO and nested properties support 【发布时间】:2012-08-13 17:53:57 【问题描述】:

我正在尝试通过 DAO 对象执行数据库访问,但我遇到了需要查询另一个实体中的字段的情况。

考虑通过外键EntityA.idEntityB 在实体A 中连接的两个实体(EntityA 和EntityB)。

我有GenericDao<EntityA> daoA,我正在尝试在dao 的相同find 方法中获取与EntityB 的确定字段匹配的所有结果:idEntityB.fieldOfB

有可能吗?如果是这样,一些方向会很好。谢谢

编辑

我的代码示例:

实体

public class EntityA 
    @JoinColumn(name = "id_entity_b", referencedColumnName = "id")
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    private EntityB idEntityB;
    // getter+setter...


public class EntityB 
    // ...
    private String fieldOfB;
    // getter+setter...

DAO 访问

GenericDao<EntityA> daoA = // ...

Set<Criterion> filter = new HashSet<Criterion>();
filter.add(Restrictions.eq("idEntityB.fieldOfB"));

List<EntityA> list = dao.findByFilter(filter);

错误消息类似于“无法解析属性 idEntityB.fieldOfB

编辑 2

我能够找到我想做的事情。虽然我的 API 略有不同,但我相信这对于在自己项目的早期阶段遇到此问题的任何人都有帮助。

http://code.google.com/p/hibernate-generic-dao/

该框架具有强大而灵活的搜索功能。 这通过将搜索对象传递给一般的搜索方法来使用 和通用 DAO。

此项目完全支持具有嵌套属性的搜索。

【问题讨论】:

您能发布您当前的代码吗? 为什么不直接通过它在EntytyA中的ID来加载EntytyB? 您的查询结果是 EntityA 类型的集合吗? @JamesB - 是的,理想的最终结果是一个 List 我自己编写了一个通用 DAO,它还允许通过反射和 Criteria API 对嵌套子属性进行简单过滤,并在必要时对过滤器进行动态获取。当我回到家时,我可以发布我的一些代码。我首先从过滤属性字符串(如foo.bar.name)创建一个树结构,然后从该树中添加对条件的限制。 【参考方案1】:

这是我的通用标准过滤方法。

根据 bean 约定的属性具有以下形式 foo.bar.name。 使用 Criteria API,可以从给定的过滤映射构建树,并且可以添加限制。我在测试期间观察到的一种特殊情况是,对标识符属性进行过滤不需要新的子标准,因为该属性已被获取。

/**
 * Creates a detached criteria from the given Type and given map of filters.
 * 
 * @param type Target type the Criteria is build for.
 * @param identifierPropertyName If provided (not null) the identifier
 *            property name can be identified for the given type to simplify
 *            the queries if the identifier property is the only property
 *            used on the parent no subqueries are needed.
 * @param filters
 * 
 * @see #createTree(Set, String)
 * @see #addRestrictions(DetachedCriteria, TreeNode)
 * 
 * @return
 */
public static DetachedCriteria createDetachedCriteria(final Class<?> type, final String identifierPropertyName,
    final Map<String, Criterion> filters)

    final DetachedCriteria criteria = DetachedCriteria.forClass(type);

    // add restrictions using tree
    final TreeNode<Entry<String, Criterion>> rootNode = HibernateUtils2.createTree(filters.entrySet(),
        identifierPropertyName);

    final Iterator<TreeNode<Entry<String, Criterion>>> it = rootNode.getChildren().iterator();

    while (it.hasNext())
        HibernateUtils.addRestrictions(criteria, it.next());

    return criteria;


/**
 * Creates a Tree from the given Set using a fictional root TreeNode.
 * 
 * @param <T>
 * 
 * @param filters
 * @param identifierPropertyName Property name which is merged with its
 *            parent property. Example: <b>user.id</b> is treated as single
 *            property.
 * @return
 */
public static <T extends Object> TreeNode<Entry<String, T>> createTree(final Set<Entry<String, T>> filters,
    final String identifierPropertyName)


    final Iterator<Entry<String, Object>> it = filters.iterator();

    /*
     * create key property tree for Entity properties
     */
    final TreeNode<Entry<String, Object>> rootNode = new TreeNode<Entry<String, Object>>(
        new SimpleEntry<String, Object>("root", null));

    while (it.hasNext())
    
        final Entry<String, Object> entry = it.next();
        // foo.bar.name
        final String key = entry.getKey();

        String[] props;

        /*
         * check if we have a nested hierarchy
         */
        if (key.contains("."))
        
            props = key.split("\\.");
            // check for identifier since identifier property name does not
            // need new subcriteria
            if (!StringUtils.isBlank(identifierPropertyName))
            
                int propsTempLength = props.length - 1;
                if (props[propsTempLength].equals(identifierPropertyName))
                
                    props = Arrays.copyOf(props, propsTempLength);
                    propsTempLength--;
                    props[propsTempLength] = props[propsTempLength] + "." + identifierPropertyName;
                
            

            // check for "this" identifier of beginning, which needs to be
            // added for projections because of hibernate not recognizing it
            if (props.length > 1 && props[0].equals("this"))
            
                props[0] = "this." + props[1];

                props = ArrayUtils.remove(props, 1);
            
        
        else
            props = new String[]
            
                key
            ;

        TreeNode<Entry<String, Object>> currNode = rootNode;

        // create nested criteria
        for (int i = 0; i < props.length; i++)
        
            Object valueAdd;

            // only leaf needs value
            if (i != props.length - 1)
                valueAdd = null;
            else
                valueAdd = entry.getValue();

            final TreeNode<Entry<String, Object>> childTempNode = new TreeNode<Entry<String, Object>>(
                new SimpleEntry<String, Object>(props[i], valueAdd));

            // try to get the real node
            TreeNode<Entry<String, Object>> childNode = currNode.getChild(childTempNode.getElement());
            // check if we already have a unique node
            if (childNode == null)
            
                childNode = childTempNode;
                // add new child to set if its a new node
                currNode.addChild(childNode);
            

            currNode = childNode;
        
    

    return rootNode;


/**
 * Recursively adds the given Restriction's wrapped in the given TreeNode to
 * the Criteria.
 * 
 * @param criteria
 * @param treeNode
 */
public static void addRestrictions(final DetachedCriteria criteria,
    final TreeNode<Entry<String, Criterion>> treeNode)

    // if we have a leaf simply add restriction
    if (treeNode.getChildren().size() == 0)
        criteria.add(treeNode.getElement().getValue());
    else
    
        // create new sub Criteria and iterate children's
        final DetachedCriteria subCriteria = criteria.createCriteria(treeNode.getElement().getKey());

        final Iterator<TreeNode<Entry<String, Criterion>>> it = treeNode.getChildren().iterator();

        while (it.hasNext())
            HibernateUtils.addRestrictions(subCriteria, it.next());
    


/*
 * Utility classes
 */

/**
 * Generic TreeNode implementation with a Set to hold its children to only allow
 * unique children's.
 */
public class TreeNode<T>

    private final T element;

    private final Set<TreeNode<T>> childrens;

    public TreeNode(final T element)
    
        if (element == null)
            throw new IllegalArgumentException("Element cannot be null");

        this.element = element;

        this.childrens = new HashSet<TreeNode<T>>();
    

    public void addChildren(final TreeNode<T> children)
    
        this.childrens.add(children);
    

    /**
     * Retrieves the children which equals the given one.
     * 
     * @param children
     * @return If no children equals the given one returns null.
     */
    public TreeNode<T> getChildren(final TreeNode<T> children)
    
        final Iterator<TreeNode<T>> it = this.childrens.iterator();

        TreeNode<T> next = null;

        while (it.hasNext())
        
            next = it.next();
            if (next.equals(children))
                return next;
        

        return null;
    

    public T getElement()
    
        return this.element;
    

    public Set<TreeNode<T>> getChildrens()
    
        return this.childrens;
    

    /**
     * Checks if the element of this instance equals the one of the given
     * Object.
     */
    @Override
    public boolean equals(final Object obj)
    
        if (this == obj)
            return true;

        if (obj != null && obj instanceof TreeNode)
        
            final TreeNode<?> treeNode = (TreeNode<?>) obj;

            return this.element.equals(treeNode.element);
        
        else
            return false;
    

    @Override
    public int hashCode()
    
        int hash = 1;
        hash = hash * 17 + this.element.hashCode();
        return hash;
    

希望这对您有所帮助。或者看看你提到的通用 dao 项目。我知道这个项目并查看了它,但从未下载过。

使用这种方法可以创建非常简单的查询,如下所示:

Map<String, Object> filters = new HashMap<String, Object>();
filters.put("foo.bar.name", Restrictions.like("name", "peter"));
filters.put("foo.test.id", Restrictions.eq("id", 2));

List<Class> data = HibernateUtils.createDetachedCriteria(Class, "get identifier from sessionFactory", filters).getExecutableCriteria(session).list();

这种将属性名称作为键添加到 Restrictions 的奇怪方法与 Restrictions 没有属性名称的 getter 和 setter 的事实有关。

自定义过滤

我的真实应用程序使用类似的代码,不仅限于Criterion 类。 对于我的 Web 层,我创建了与 Restrictions api 等效的自定义过滤器,但客户端不再需要休眠 jar。

编辑

不支持跨非实体(例如组件和复合 ID)的通用过滤。它可以在ClassMetadata 的帮助下轻松扩展以支持它们而无需反思。如果需要代码,我可以提供。

【讨论】:

谢谢。我的项目必须支持其他类型的运算符,因此我将尝试向您添加代码运算符令牌和通用标准构建机制,例如 Utils.buildCriteria(String prop, OPERATOR op, Object value);,其中 equals 应用于 FK,op 运算符应用于最后一个嵌套属性.这确实很有帮助。已接受答案并 +1。 我无法找到您正在使用的 TreeNode 的兼容实现。如果可以的话,你能把它的代码也贴出来吗? 添加了 TreeNode 实现并修复了标识符属性问题。 @nuno 在您的问题中,您没有提到使用自定义过滤器。目前我在我的客户端上使用自定义过滤器(客户端不需要休眠 jar)并且标准构建方法可以采用任何类型的对象(标准、自定义过滤器、对象)并根据值的类型添加限制。如果您对这种方法感兴趣,我可以为您提供一些代码。我刚刚删除了所有不相关的代码,只是为了支持答案中的标准。【参考方案2】:

看看这个例子:http://viralpatel.net/blogs/hibernate-one-to-one-mapping-tutorial-using-annotation/

您需要将实体之间的关系声明为

public class EntityA 
    @JoinColumn(name = "id_entity_b")
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    private EntityB idEntityB;
    // getter+setter... and the rest

EntityB代替Integer

这就是 Hibernate 提供的ORM 的要点:您不需要使用键,而是使用对象。将这种表示转换为带键的关系映射是 Hibernate 的 ORM 层的工作。

【讨论】:

是的,实际上我的代码就是这样使用的。对不起,我从我的代码中抄错了。 (原始问题中的代码现在类似于我的代码)

以上是关于通用 DAO 和嵌套属性支持的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC:通用 DAO 和服务类

使用mybatis完成通用dao和通用service

通用 DAO 如何为所有不同的 DAO 实现返回相同的类型?

通用 DAO - “永远不要完全通用!”

SpringBoot - 实践阿里巴巴Manager 层_通用业务处理层

SpringBoot - 实践阿里巴巴Manager 层_通用业务处理层