1. 程式人生 > >解決Hibernate native sql中雙冒號(:)轉義的問題

解決Hibernate native sql中雙冒號(:)轉義的問題

最近在做一個簽到的任務,由於要查詢到歷史連續最大簽到記錄的值,起初還是有點迷茫的--有一種想將查詢結果查詢出來,然後使用演算法來解決這個問題。但是折騰了半天,感覺有點難以處理,所以就google了一下和“簽到”類似的處理方法,最終參考別人的實現是通過資料庫來實現的,具體怎麼處理這裡不過多介紹了,文章結尾會將連結貼上。

迴歸到我需要講解的問題--使用Hibernate 時,native sql中包含冒號(:),由於冒號在hibernate中是一個特殊的符號,用來標識預編譯引數變數的.為何我會這麼糾結呢,因為我們的系統定位的dao層就是使用Hibernate,所以我只好尋求能夠處理好的方式(雖然我知道可以用spring jdbc來分分鐘就可以實現),下面看下我初始的sql語句(看不懂也沒有關係):


貌似上面的sql語句馬上就可以大功告成了,因為我在sqlyog中直接複製進去可以直接將查詢結果查詢出來,但是當程式跑起來的時候,後臺直接丟擲一個異常:’Space is not allowed after parameter prefix ':' ‘。

此時,小哥我的內心是崩潰的,問了下度娘,把hibernate的原始碼找了出來---org.hibernate.engine.query.spi.ParameterParser

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

		final int stringLength = sqlString.length();
		boolean inQuote = false;
		for ( int indx = 0; indx < stringLength; indx++ ) {
			final 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 == ':' ) {
					<span style="background-color: rgb(153, 255, 153);">// named parameter--就是這段程式碼的處理導致的
					final int right = StringHelper.firstIndexOfChar( sqlString, ParserHelper.HQL_SEPARATORS_BITSET, indx + 1 );
					final int chopLocation = right < 0 ? sqlString.length() : right;
					final 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;</span>
				}
				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
						final int right = StringHelper.firstIndexOfChar( sqlString, ParserHelper.HQL_SEPARATORS, indx + 1 );
						final int chopLocation = right < 0 ? sqlString.length() : right;
						final 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 );
				}
			}
		}
	}
有人說直接改原始碼,我本來都開始動手了,但是無意間瀏覽到一個部落格----別人是兩個冒號,看了下部落格的評論,有的小夥伴說可以直接用反斜槓來進行轉義,抱著一種將信將疑的態度試了一下,sql如下:

真的是成功了,幸好沒有用複雜的方式去解決問題(重寫hibernate)。

用hibernate也有幾年了,沒想到真的是too young too simple ,此部落格也希望能給自己的成長留下一個腳印。

最後,文章中說道的簽到型別任務的處理實現參考如下(貌似要翻牆哦):