【问题标题】:Hibernate-generated column aliases break AliasToBeanResultTransformer when using query使用查询时,Hibernate 生成的列别名会破坏 AliasToBeanResultTransformer
【发布时间】:2012-04-18 00:15:25
【问题描述】:

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

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 看起来像这样:

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;
    }
}

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

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

我可以在createQuery 语句中明确指定别名吗?目前我正在尝试不使用标准来实现这一点,因此如果存在这样的查询对象,我将不胜感激。


更新

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

【问题讨论】:

  • 这很奇怪,因为我还在 HQL 中使用 as 指定了别名,它工作正常。
  • 你也使用session.createQuery(...)方法吗?我认为如果改为使用标准,则不应该出现问题。
  • 您的评论具有误导性。问题解决了吗?
  • @IvayloSlavov 。是的,我使用 session.createQuery(hqlQueryString ) 并在 HQL 中使用 as 指定别名,它工作正常。
  • @ManuPK 其实问题已经解决了,还在使用session.createQuery(hqlQueryString )。显然,@KenChan 是正确的,如果在查询中指定别名,它仍然有效,但 Hibernate 仍然会产生一个带有不同别名的混淆查询输出,这会误导我认为问题是别名。相反,我的 setter 类型不匹配。

标签: java hibernate alias nativequery


【解决方案1】:

其实不需要再实现一个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();

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

【讨论】:

  • 我必须承认我并不熟悉 hibernate api 的所有功能。您的建议似乎是合理的,当然,这是一个开箱即用的建议。不幸的是,我的使用场景是在编译时不知道具体查询、参数类型和结果类的通用方法中。因此,我担心我无法利用这种方法。尽管如此,知道解决问题的更好方法总是很高兴。
【解决方案2】:

对于原生查询,没有涉及到简单的解决方案。我不得不研究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;
    }
}

这段代码的不同主要在于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;
    }
}

它的来源是基于 Hibernate 自带的 BaseSetter 类,但被更改为支持不区分大小写的匹配。尽管如此,这个类,以及 Hibernate 使用的原始类,由于大量使用反射而缺乏性能。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-10-20
    • 2013-08-11
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多