解决Hibernate不支持PostgreSQL中双冒号(::)的问题
解决Hibernate不支持PostgreSQL中双冒号(::)的问题
李玉珏 发表于3年前
解决Hibernate不支持PostgreSQL中双冒号(::)的问题
  • 发表于 3年前
  • 阅读 1774
  • 收藏 2
  • 点赞 0
  • 评论 6

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 在PostgreSQL中,双冒号(::)的作用是类型转换,而在Hibernate中,SQL中冒号的作用是命名参数,用于SQL中命名参数的匹配,这时,当在PostgreSQL数据库环境中,正常的SQL本身包括双冒号时,通过Hibernate进行查询就会报错,这个虽然可以通过转义来解决,但是到处转义显然不够优雅,而且双冒号是PostgreSQL特有的语法,也不会产生歧义,本文将提供改进的方法。

        在PostgreSQL中,双冒号(::)的作用是类型转换,而在Hibernate中,SQL中冒号的作用是命名参数,用于SQL中命名参数的匹配,这时,当在PostgreSQL数据库环境中,正常的SQL本身包括双冒号时,通过Hibernate进行查询就会报错,这个应该是Hibernate的一个Bug,怎么解决呢,本文将给出方案。

        通过研究Hibernate的源代码,发现了问题所在,问题出在org.hibernate.engine.query.spi.ParameterParser,这个类构造方法为私有,包括若干个静态方法,无法通过扩展二次开发的方式解决,遇到这个问题的,只能自行修改Hibernate的源代码,然后编译。

        经过分析,只需要修改其中的parse方法即可,下面的代码即为修改后的代码,测试了一下,大体应该是没问题的,该问题的发现、开发、测试是在Hibernate4.2.15版本下进行的,其他版本如有问题,请开发者自行处理。

public static void parse(String sqlString, Recognizer recognizer) throws QueryException {
        boolean hasMainOutputParameter = startsWithEscapeCallTemplate( sqlString );
        boolean foundMainOutputParam = false;

        int stringLength = sqlString.length();
        boolean inQuote = false;
        for ( int indx = 0; indx < stringLength; indx++ ) {
            char c = sqlString.charAt( indx );
            if ( inQuote ) {
                if ( '\'' == c ) {
                    inQuote = false;
                }
                recognizer.other( c );
            }
            else if ( '\'' == c ) {
                inQuote = true;
                recognizer.other( c );
            }
            else if ( '\\' == c ) {
                // skip sending the backslash and instead send then next character, treating is as a literal
                recognizer.other( sqlString.charAt( ++indx ) );
            }
            else {
                if ( c == ':' ) {
                    // named parameter
                    int right = StringHelper.firstIndexOfChar( sqlString, ParserHelper.HQL_SEPARATORS_BITSET, indx + 1 );
                    int chopLocation = right < 0 ? sqlString.length() : right;
                    //增加了双冒号的处理
                    if (sqlString.charAt( indx+1 ) != ':'){
                        String param = sqlString.substring( indx + 1, chopLocation );
                        if ( StringHelper.isEmpty( param ) ) {
                            throw new QueryException(
                                    "Space is not allowed after parameter prefix ':' [" + sqlString + "]"
                            );
                        }
                        recognizer.namedParameter( param, indx );
                        indx = chopLocation - 1;
                    }else{
                        recognizer.other(c);
                        recognizer.other(c);
                        indx++;
                    }
                }
                else if ( c == '?' ) {
                    // could be either an ordinal or JPA-positional parameter
                    if ( indx < stringLength - 1 && Character.isDigit( sqlString.charAt( indx + 1 ) ) ) {
                        // a peek ahead showed this as an JPA-positional parameter
                        int right = StringHelper.firstIndexOfChar( sqlString, ParserHelper.HQL_SEPARATORS, indx + 1 );
                        int chopLocation = right < 0 ? sqlString.length() : right;
                        String param = sqlString.substring( indx + 1, chopLocation );
                        // make sure this "name" is an integral
                        try {
                            Integer.valueOf( param );
                        }
                        catch( NumberFormatException e ) {
                            throw new QueryException( "JPA-style positional param was not an integral ordinal" );
                        }
                        recognizer.jpaPositionalParameter( param, indx );
                        indx = chopLocation - 1;
                    }
                    else {
                        if ( hasMainOutputParameter && !foundMainOutputParam ) {
                            foundMainOutputParam = true;
                            recognizer.outParameter( indx );
                        }
                        else {
                            recognizer.ordinalParameter( indx );
                        }
                    }
                }
                else {
                    recognizer.other( c );
                }
            }
        }
    }

 

 

共有 人打赏支持
李玉珏
粉丝 200
博文 43
码字总数 64437
评论 (6)
Tengern
为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理
李玉珏

引用来自“Tengern”的评论

为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理

嗯?让开发人员写两个反斜杠?这不好吧?
好像就pg有这个双冒号的写法吧?
李玉珏

引用来自“Tengern”的评论

为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理

其实在sql里,还可以用cast函数,但是都不太好,增加了开发人员的工作量,当时用双冒号,主要是用了pg的动态类型特性,到处转义的话,代码看上去不太干净
ayashaki

引用来自“Tengern”的评论

为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理
为什么我转义了还是报错。。 Space is not allowed after parameter prefix ':' [select @rownum\:=@rownum+1 AS rownum 用的hibernate3.3.2
李玉珏

引用来自“ayashaki”的评论

引用来自“Tengern”的评论

为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理
为什么我转义了还是报错。。 Space is not allowed after parameter prefix ':' [select @rownum\:=@rownum+1 AS rownum 用的hibernate3.3.2

我这个是在hibernate4上代码,3上是怎么实现的,我不知道,你可以下个代码跟踪下
ayashaki

引用来自“liyuj”的评论

引用来自“ayashaki”的评论

引用来自“Tengern”的评论

为什么不进行转义呐,使用“\\:\\:”。 else if ( '\\' == c ) 这段代码会进行转义处理
为什么我转义了还是报错。。 Space is not allowed after parameter prefix ':' [select @rownum\:=@rownum+1 AS rownum 用的hibernate3.3.2

我这个是在hibernate4上代码,3上是怎么实现的,我不知道,你可以下个代码跟踪下
3估计没有,最后还是选择换sql语句了。。。
×
李玉珏
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: