Java實現SQL格式化

在web項目中有時候會遇到須要將用戶輸入的sql格式化的需求,筆者最近就遇到這樣一個項目,通過查找資料,總結下面的隨手筆記,後續有空會在從新整理。sql語句本質上是字符串,要對其格式化,也就是字符串的查找、解析、替換之類的操做。java

1.若是要求不高,能夠本身寫一個簡單的格式化方法。參考https://www.cnblogs.com/firstdream/p/5651994.htmweb

package aaa;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.StringTokenizer;

public class SqlFormatterUtils {
	
	private static final Set<String> BEGIN_CLAUSES = new HashSet<String>();
	private static final Set<String> END_CLAUSES = new HashSet<String>();
	private static final Set<String> LOGICAL = new HashSet<String>();
	private static final Set<String> QUANTIFIERS = new HashSet<String>();
	private static final Set<String> DML = new HashSet<String>();
	private static final Set<String> MISC = new HashSet<String>();
	static final String indentString = "    ";
	static final String initial = "\n    ";

	public String format(String source) {
		return new FormatProcess(source).perform().trim();
	}

	static {
		BEGIN_CLAUSES.add("left");
		BEGIN_CLAUSES.add("right");
		BEGIN_CLAUSES.add("inner");
		BEGIN_CLAUSES.add("outer");
		BEGIN_CLAUSES.add("group");
		BEGIN_CLAUSES.add("order");

		END_CLAUSES.add("where");
		END_CLAUSES.add("set");
		END_CLAUSES.add("having");
		END_CLAUSES.add("join");
		END_CLAUSES.add("from");
		END_CLAUSES.add("by");
		END_CLAUSES.add("join");
		END_CLAUSES.add("into");
		END_CLAUSES.add("union");

		LOGICAL.add("and");
		LOGICAL.add("or");
		LOGICAL.add("when");
		LOGICAL.add("else");
		LOGICAL.add("end");

		QUANTIFIERS.add("in");
		QUANTIFIERS.add("all");
		QUANTIFIERS.add("exists");
		QUANTIFIERS.add("some");
		QUANTIFIERS.add("any");

		DML.add("insert");
		DML.add("update");
		DML.add("delete");

		MISC.add("select");
		MISC.add("on");
	}

	private static class FormatProcess {
		boolean beginLine = true;
		boolean afterBeginBeforeEnd = false;
		boolean afterByOrSetOrFromOrSelect = false;
		boolean afterValues = false;
		boolean afterOn = false;
		boolean afterBetween = false;
		boolean afterInsert = false;
		int inFunction = 0;
		int parensSinceSelect = 0;
		private LinkedList<Integer> parenCounts = new LinkedList<Integer>();
		private LinkedList<Boolean> afterByOrFromOrSelects = new LinkedList<Boolean>();

		int indent = 1;

		StringBuffer result = new StringBuffer();
		StringTokenizer tokens;
		String lastToken;
		String token;
		String lcToken;

		public FormatProcess(String sql) {
			this.tokens = new StringTokenizer(sql, "()+*/-=<>'`\"[], \n\r\f\t", true);
		}

		public String perform() {
			this.result.append("\n    ");

			while (this.tokens.hasMoreTokens()) {
				this.token = this.tokens.nextToken();
				this.lcToken = this.token.toLowerCase();

				if ("'".equals(this.token)) {
					String t;
					do {
						t = this.tokens.nextToken();
						this.token += t;
					} while ((!"'".equals(t)) && (this.tokens.hasMoreTokens()));
				} else if ("\"".equals(this.token)) {
					String t;
					do {
						t = this.tokens.nextToken();
						this.token += t;
					} while (!"\"".equals(t));
				}

				if ((this.afterByOrSetOrFromOrSelect) && (",".equals(this.token))) {
					commaAfterByOrFromOrSelect();
				} else if ((this.afterOn) && (",".equals(this.token))) {
					commaAfterOn();
				} else if ("(".equals(this.token)) {
					openParen();
				} else if (")".equals(this.token)) {
					closeParen();
				} else if (BEGIN_CLAUSES.contains(this.lcToken)) {
					beginNewClause();
				} else if (END_CLAUSES.contains(this.lcToken)) {
					endNewClause();
				} else if ("select".equals(this.lcToken)) {
					select();
				} else if (DML.contains(this.lcToken)) {
					updateOrInsertOrDelete();
				} else if ("values".equals(this.lcToken)) {
					values();
				} else if ("on".equals(this.lcToken)) {
					on();
				} else if ((this.afterBetween) && (this.lcToken.equals("and"))) {
					misc();
					this.afterBetween = false;
				} else if (LOGICAL.contains(this.lcToken)) {
					logical();
				} else if (isWhitespace(this.token)) {
					white();
				} else {
					misc();
				}

				if (!isWhitespace(this.token)) {
					this.lastToken = this.lcToken;
				}
			}

			return this.result.toString();
		}

		private void commaAfterOn() {
			out();
			this.indent -= 1;
			newline();
			this.afterOn = false;
			this.afterByOrSetOrFromOrSelect = true;
		}

		private void commaAfterByOrFromOrSelect() {
			out();
			newline();
		}

		private void logical() {
			if ("end".equals(this.lcToken)) {
				this.indent -= 1;
			}
			newline();
			out();
			this.beginLine = false;
		}

		private void on() {
			this.indent += 1;
			this.afterOn = true;
			newline();
			out();
			this.beginLine = false;
		}

		private void misc() {
			out();
			if ("between".equals(this.lcToken)) {
				this.afterBetween = true;
			}
			if (this.afterInsert) {
				newline();
				this.afterInsert = false;
			} else {
				this.beginLine = false;
				if ("case".equals(this.lcToken))
					this.indent += 1;
			}
		}

		private void white() {
			if (!this.beginLine)
				this.result.append(" ");
		}

		private void updateOrInsertOrDelete() {
			out();
			this.indent += 1;
			this.beginLine = false;
			if ("update".equals(this.lcToken)) {
				newline();
			}
			if ("insert".equals(this.lcToken))
				this.afterInsert = true;
		}

		private void select() {
			out();
			this.indent += 1;
			newline();
			this.parenCounts.addLast(new Integer(this.parensSinceSelect));
			this.afterByOrFromOrSelects.addLast(Boolean.valueOf(this.afterByOrSetOrFromOrSelect));
			this.parensSinceSelect = 0;
			this.afterByOrSetOrFromOrSelect = true;
		}

		private void out() {
			this.result.append(this.token);
		}

		private void endNewClause() {
			if (!this.afterBeginBeforeEnd) {
				this.indent -= 1;
				if (this.afterOn) {
					this.indent -= 1;
					this.afterOn = false;
				}
				newline();
			}
			out();
			if (!"union".equals(this.lcToken)) {
				this.indent += 1;
			}
			newline();
			this.afterBeginBeforeEnd = false;
			this.afterByOrSetOrFromOrSelect = (("by".equals(this.lcToken)) || ("set".equals(this.lcToken))
					|| ("from".equals(this.lcToken)));
		}

		private void beginNewClause() {
			if (!this.afterBeginBeforeEnd) {
				if (this.afterOn) {
					this.indent -= 1;
					this.afterOn = false;
				}
				this.indent -= 1;
				newline();
			}
			out();
			this.beginLine = false;
			this.afterBeginBeforeEnd = true;
		}

		private void values() {
			this.indent -= 1;
			newline();
			out();
			this.indent += 1;
			newline();
			this.afterValues = true;
		}

		private void closeParen() {
			this.parensSinceSelect -= 1;
			if (this.parensSinceSelect < 0) {
				this.indent -= 1;
				this.parensSinceSelect = ((Integer) this.parenCounts.removeLast()).intValue();
				this.afterByOrSetOrFromOrSelect = ((Boolean) this.afterByOrFromOrSelects.removeLast()).booleanValue();
			}
			if (this.inFunction > 0) {
				this.inFunction -= 1;
				out();
			} else {
				if (!this.afterByOrSetOrFromOrSelect) {
					this.indent -= 1;
					newline();
				}
				out();
			}
			this.beginLine = false;
		}

		private void openParen() {
			if ((isFunctionName(this.lastToken)) || (this.inFunction > 0)) {
				this.inFunction += 1;
			}
			this.beginLine = false;
			if (this.inFunction > 0) {
				out();
			} else {
				out();
				if (!this.afterByOrSetOrFromOrSelect) {
					this.indent += 1;
					newline();
					this.beginLine = true;
				}
			}
			this.parensSinceSelect += 1;
		}

		private static boolean isFunctionName(String tok) {
			char begin = tok.charAt(0);
			boolean isIdentifier = (Character.isJavaIdentifierStart(begin)) || ('"' == begin);
			return (isIdentifier) && (!LOGICAL.contains(tok))
					&& (!END_CLAUSES.contains(tok))
					&& (!QUANTIFIERS.contains(tok)) && (!DML.contains(tok))
					&& (!MISC.contains(tok));
		}

		private static boolean isWhitespace(String token) {
			return " \n\r\f\t".indexOf(token) >= 0;
		}

		private void newline() {
			this.result.append("\n");
			for (int i = 0; i < this.indent; i++) {
				this.result.append("    ");
			}
			this.beginLine = true;
		}
	}

	public static void main(String[] args) {
		System.out.println(new SqlFormatterUtils().format(
				"select aa,bb,cc,dd from ta1,(select ee,ff,gg from ta2 where ee=ff) ta3 where aa=bb and cc=dd group by dd order by createtime desc limit 3 "));
	}
}

2.藉助第三方工具SQLinForm,這個是一個收費的東西,具體能夠去官網上了解。pl/sql中用的就是這個東西。能夠直接把jar包放到項目中,筆者使用了pl/sql 9.0下的SQLinForm.jar,個性化的設置項比較多,所有摘錄以下:sql

package aaa;

import SQLinForm_200.SQLForm;

public class PlSqlFormat {
	public static void main(String[] args) {
		
		String sql = "select id, name as '姓名', age as '年齡', sex '性別', birthday from student s left join classroom c on s.classroom_id = c.id left join teacher t on s.chinese_id = t.id where s.name = 'a' ";
		SQLForm form = new SQLForm();
		
		form.setAlignmentAs(false);
	    form.setAlignmentComma(false);
	    form.setAlignmentComment(false);
	    form.setAlignmentConcat(false);
	    form.setAlignmentDeclaration(false);
	    form.setAlignmentEqual(false);
	    form.setAlignmentKeyword(false);
	    form.setAlignmentOperator(false);
	    form.setAndOrIndention(false);
	    form.setBracketSpaces("noSpacesAroundBracket");
	    form.setCase(false, false);
	    form.setColor(false);
	    form.setCommaSpaces("oneSpaceAfterComma");
	    form.setDisplayOfWarningMsg(false);
	    form.setdoubleIndentionMasterKeyword(false);
	    form.setEqualSpaces("oneSpaceAroundEqual");
	    form.setFormatLanguage("SQL");
	    form.setGraphLevel(false);
	    form.setIndention(8, false);
	    form.setIndentJoin(false);
	    form.setInitialIndentation(8);
	    form.setLineBreak(false, false, false, false, false, false);
	    form.setLinebreakAfterConcat(false);
	    form.setLinebreakBeforeConcat(false);
	    form.setLinebreakBeforeLineComment(false);
	    form.setLinebreakCase(false);
	    form.setLinebreakCaseAndOr(false);
	    form.setLinebreakCaseElse(false);
	    form.setLinebreakCaseThen(false);
	    form.setLinebreakCaseWhen(false);
	    form.setLinebreakJoin(false);
	    form.setLinebreakJoinJoin(false);
	    form.setLinebreakJoinOn(false);
	    form.setLinebreakKeyword(false);
	    form.setLineNumber(false);
	    form.setLowerCase(false);
	    form.setLowerCaseKeyword(false);
	    form.setNumCommas(80);
	    form.setOneLineSQL(false);
	    form.setPageWidth(80);
	    //form.setQuoteCharacter("");
	    form.setReplaceComment(false);
	    form.setSmallSQLWidth(80);
	    //form.setSourceSQL(boolean, String);
	    //form.setSourceSQLLanguage(String);
	    form.setSuppressComment(false);
	    form.setSuppressEmptyLine(false);
	    form.setSuppressLinebreak(false);
	    form.setSuppressSpace(false);
	    //form.setSuppressSpace(boolean, String);
	    //form.setti(String);
	    //form.setUserKeywords(String);
	    //form.setVariableName(String);

		System.out.println(form.formatSQLAsString(sql));
	}
}

輸出結果app

select  id, name as '姓名', age as '年齡', sex '性別', birthday
         from    student s
         left join classroom c on s.classroom_id = c.id
         left join teacher t   on s.chinese_id = t.id
         where   s.name = 'a'

由於不開源,源碼反編譯後也很難理解。因此最終筆者放棄這種方案。工具

3.阿里巴巴的druid聽說很好用,筆者接下來要研究一下。ui

相關文章
相關標籤/搜索