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