java 使用查询时,Hibernate 生成的列别名会破坏 AliasToBeanResultTransformer

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10008572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 23:14:33  来源:igfitidea点击:

Hibernate-generated column aliases break AliasToBeanResultTransformer when using query

javahibernatealiasnativequery

提问by Ivaylo Slavov

What I am trying to achieve is to set a result transformer on a query defined in the following way:

我想要实现的是在按以下方式定义的查询上设置结果转换器:

String hqlQueryString = "select o.id as id, o.name as objectName from MyObject"; 
Class resultClass = MyObject.class;
Query query = session.createQuery(hqlQueryString).setResultTransformer(
        new new AliasToBeanResultTransformer(resultClass));
List result = query.list();

MyObject looks like this:

MyObject 看起来像这样:

public class MyObject {
    private int id;
    private String objectName;

    public int getId() {
        return id;
    }
    public void setId(int value) {
        this.id = value;
    }

    public String getObjectName() {
        return objectName;
    }
    public void setobjectName(String value) {
        this.objectName = value;
    }
}

The problem is, that although I have specified idand objectNameto be my aliases, the actual query being executed uses different aliases. This causes my AliasToBeanResultTransformerto fail to construct MyObjectbecause the aliases do not match property names.

问题是,虽然我已经指定idobjectName作为我的别名,但正在执行的实际查询使用不同的别名。这会导致 myAliasToBeanResultTransformer无法构造,MyObject因为别名与属性名称不匹配。

Is it possible to obtain the aliases of the query generated by hibernate programmatically (I can set them to the alias to bean result tranformer)? I tried using query.getReturnAliases()but it returns the aliases that I have defined in my HQL, not the ones that Hibernate actually uses.

是否可以通过编程方式获取由 hibernate 生成的查询的别名(我可以将它们设置为 bean 结果转换器的别名)?我尝试使用,query.getReturnAliases()但它返回我在 HQL 中定义的别名,而不是 Hibernate 实际使用的别名。

Can I explicitly specify the aliases in a createQuerystatement? Currently I am trying not to use criterion for this to work, so I'd appreciate an approach that uses query objects, if such exists.

我可以在createQuery语句中明确指定别名吗?目前我试图不使用标准来实现这一点,所以我很欣赏一种使用查询对象的方法,如果存在的话。



Update

更新

Although the issue described above is invalid for standard HQL queries (see comments), it is valid when executing a native query. To be specific - native queries seemed to treat all aliases as lowecase strings (despite specific capitalization that might have been introduced in the query). This causes the AliasToBeanResultTransformerto fail when setting the properties, in cases where capitalization matters.

尽管上述问题对标准 HQL 查询无效(请参阅评论),但在执行本机查询时有效。具体而言 - 本机查询似乎将所有别名视为小写字符串(尽管查询中可能引入了特定的大写字母)。这会导致AliasToBeanResultTransformer在设置属性时失败,在大小写很重要的情况下。

采纳答案by Ivaylo Slavov

As for native queries, there was no simple solution involved. I had to look into the implementation of the AliasToBeanResultTransformerclass and put a fix in there. I resolved the problem by creating a copy of the AliasToBeanResultTransformerclass and modified the private initializemethod of that class in the following way:

至于本机查询,不涉及简单的解决方案。我不得不研究AliasToBeanResultTransformer该类的实现并在其中进行修复。我通过创建AliasToBeanResultTransformer类的副本解决了该问题,并按initialize以下方式修改了该类的私有方法:

public class CaseInsensitiveAliasToBeanResultTransformer {
    private void initialize(String[] aliases) {
        this.aliases = new String[ aliases.length ];
        setters = new Setter[aliases.length];
        for ( int i = 0; i < aliases.length; i++ ) {
            String alias = aliases[i];
            if (alias != null) {
                this.aliases[i] = alias;
                setters[i] = CaseInsensitiveSetter.getSetter(resultClass, alias);
            }
        }
        isInitialized = true;
    }
}

This code differs mainly in the line CaseInsensitiveSetter.getSetter(resultClass, alias), where I have introduced a CaseInsensitiveSetterclass I will describe below. This class implements the Setter interface and allows retrieving the setter method of a class using case-insensitive matching - so this will allow me to bind the lower-cased query aliases to the proper members of my result class. Here is the code of the custom setter (only the important lines are shown for brevity):

这段代码的不同主要在于这一行CaseInsensitiveSetter.getSetter(resultClass, alias),我在这里介绍了一个CaseInsensitiveSetter我将在下面描述的类。这个类实现了 Setter 接口并允许使用不区分大小写的匹配来检索类的 setter 方法 - 因此这将允许我将小写查询别名绑定到我的结果类的正确成员。这是自定义设置器的代码(为简洁起见,仅显示了重要的行):

public class CaseInsensitiveSetter {

    public static Setter getSetter(Class<?> theClass, String propertyName) {

        Setter setter;

        if (theClass == Object.class || theClass == null) {
            setter = null;
        } else {
            setter = doGetSetter(theClass, propertyName);

            if (setter != null) {
                if (!ReflectHelper.isPublic(theClass, setter.getMethod())) {
                    setter.getMethod().setAccessible(true);
                }
            } else {
                setter = doGetSetter(theClass.getSuperclass(), propertyName);
                if (setter == null) {
                    Class<?>[] interfaces = theClass.getInterfaces();
                    for (int i = 0; setter == null && i < interfaces.length; i++) {
                        setter = doGetSetter( interfaces[i], propertyName);
                    }
                }
            }
            if (setter == null) {
                throw new PropertyNotFoundException( 
                    "Could not find a setter for property " + 
                    propertyName + " in class " + theClass.getName());
            }
        }
        return setter;
    }

    // The actual work is done here
    private static Setter doGetSetter(Class<?> resultClass, String propertyName) {

        Method[] methods = resultClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            // only carry on if the method has 1 parameter
            if ( methods[i].getParameterTypes().length == 1 ) {
                String methodName = methods[i].getName();

                if (methodName.startsWith("set")) {
                    String testStdMethod = methodName.substring(3);
                    if (testStdMethod.equalsIgnoreCase(propertyName)) {
                        Setter result = new CustomSetter(
                            resultClass, methods[i], propertyName);
                        return result;
                    }
                }
            }
        }
        return null;
    }
}

The source of this is based on the BaseSetterclass that comes with Hibernate, but is changed to support case-insensitive matching. Still, this one, and the original class that Hibernate uses, lacks performance because of the heavy usage of reflection.

这个的来源是基于BaseSetterHibernate 自带的类,但是改成支持不区分大小写的匹配。尽管如此,由于大量使用反射,这个和 Hibernate 使用的原始类仍然缺乏性能。

Also, keep in mind that if the result class contains different properties with names that would be equal in case-insensitive comparison, then only one of them will be picked by the current code and it might not work as expected.

另外,请记住,如果结果类包含名称在不区分大小写的比较中相等的不同属性,则当前代码将仅选择其中之一,并且可能无法按预期工作。

回答by Ken Chan

Actually don't need to implement another AliasToBeanResultTransformer, you can use addScalar(String columnAlias, Type type)to explicitly alias the columns of the native SQL:

实际上不需要实现另一个 AliasToBeanResultTransformer,您可以使用addScalar(String columnAlias, Type type)来显式别名本机 SQL 的列:

String nativeSQL = "select o.id as id, o.name as objectName from MyObject"; 
List<MyObject> resultList = session.createSQLQuery(nativeSQL)
        .addScalar("id" ,StandardBasicTypes.INTEGER)
        .addScalar("objectName",StandardBasicTypes.STRING)
        .setResultTransformer(new AliasToBeanResultTransformer(MyObject.class))
        .list();

The transformer will then look for a MyObjectclass and expect it having the setters setId()and setObjectName()in order to populate the returned values to the MyObjectinstance

然后转换器将寻找一个MyObject类并期望它具有设置器setId()setObjectName()以便将返回值填充到MyObject实例中