项目中SQL语句文件保存及提取方法
第十九章 SQL文保存方法
?@http://blog.csdn.net/struts2/article/details/1745050
在開發JavaWeb應用程序的過程中可能經常需要使用到SQL語句來訪問數據庫。為了屏蔽SQL注入帶來的危險,在Java中通常使用PreparedStatement,使用預編譯的SQL語句。預編譯的SQL語句是那些包含?的語句,使用PreparedStatement可以讓數據庫預先編譯這些SQL模板,只有調用的時候套用必須的參數即可。
?
SQL文件的存放位置
那么在JavaWeb項目中預編譯的SQL語句到底放在那里呢?
?
放在Java代碼里肯定是不好的,為什么,有兩點,第一,SQL語句放在Java代碼里
太難看可,有不好的味道(參看Refactor),第二,每次SQL語句變更(可能經常發生)
都需要編譯這些Java代碼,比較煩。
?
那么SQL語句到底放在那里呢?根據這么多年的開發經驗SQL文通常可以放在classes目錄
下的文件中,存放SQL語句的文件有三種類型:properties文件,xml文件和txt文件。
?
在詳細討論文件格式之前,我們先討論以下如何在Java類中得到這些文件的引用,以便從中得到需要的SQL語句。
?
使用Class的getResourceAsStream方法可以獲得對文件引用的InputStream。例如:文件目錄結構:
src
??? com
?????? jpleasure
????????? dao
??????????????? SomeDao.java
??????????????? SomeDao.properties
????? ????????????? ???????? SomeDao.xml
?????????????????? ?????????????????? SomeDao.sql
// properties
public class SomeDao{
???????? public InputStream getInputStream() {??????????? ?????????????????? ??????????????????????????????????????????????????????? ???
this.getClass().getClassLoader().
getResourceAsStream("com/jpleasure/dao/SomeDao.properties");
???????? }
}
?
// xml
public class SomeDao{
???????? public InputStream getInputStream() {
???????? ???????? this.getClass().getClassLoader().
getResourceAsStream("com/jpleasure/dao/SomeDao.properties");
???????? }
}
?
// txt(.sql)
public class SomeDao{
???????? public InputStream getInputStream() {
?????????????????? this.getClass().getClassLoader().
getResourceAsStream("com/jpleasure/dao/SomeDao.properties");
???????? }
}
?
Propertis文件
Properties 文件是Java支持的標準的屬性文件(相當于Windows對ini文件的支持)。
?
Properties 文件的格式為:
?
# 注釋
# 定義key,并且與key對應的值為value
key = value
?
可以使用java.util.Properties類來包裝properties文件,使用如下
Properties props = new Properties();
try {
???????? props.load(this.getInputStream());
} catch(IOException ioex) {
???????? // 文件不存在,或者格式問題等
}
String value = props.getString("key")
?
注意上述格式中value不能換行,要想換行必須使用轉義字符“/”。
?
在存儲SQL的時候我們使用如下的格式,例如:
?
# 某業務,某操作SQL
xxx_0001 = select * from dual
?
# 某業務,其他操作SQL
xxx_0002 =? /
select ???? /
???????? * ?? /
from ????? /
???????? dual
?
使用Properties文件的優點是:Java內置支持布需要手寫文件解析代碼,另外使用也非常簡單。
缺點是:SQL語句不能直接編寫,需要追加轉義字符"/",無法將這樣的SQL語句直接拷貝到數據庫客戶端中運行。
?
XML文件
使用XML文件保存SQL比較常用的格式為:
<sqls>
???????? <sql id=”xxx_0001”>
?????????????????? Select
*
from ??
dual?? <!-- 文件注釋? -->
</sql>
???????? <sql id=”xxx_0001”>
?????????????????? …
</sql>
????????
</sqls>
?
使用XML格式的文件保存SQL需要自己寫文件解析代碼,由于Java對XML提供了內置的支持,并且第三方的開源庫也很多,并且非常容易使用所以從xml文件中解析SQL語句也沒有什么困難。以下以jdom為例講解如何解析上述的XML格式的SQL文件。
?
protected Map analysis() {
?
?????????????????? Map map = new HashMap();
?
?????????????????? DOMBuilder builder = new DOMBuilder();
?????????????????? Document doc = null;
?????????????????? try {
??????????????????????????? // 解析XML文件
??????????????????????????? doc = builder.build(this.getInputStream());
?
??????????????????????????? // 獲得根節點: <sqls>
??????????????????????????? Element element = doc.getRootElement();
?
??????????????????????????? // 獲得所有根節點的子節點: <sql>節點列表
??????????????????????????? List sqlNodeList = element.getChildren("sql");
??????????????????????????? for (int i = 0; i < sqlNodeList.size(); i++) {
???????????????????????????????????? Element sqlNode = (Element) sqlNodeList.get(i);
???????????????????????????????????? // 獲得SQL語句ID
???????????????????????????????????? String id = sqlNode.getAttribute("id").getValue();
???????????????????????????????????? // 獲得SQL語句內容
???????????????????????????????????? String sql = sqlNode.getTextTrim();
???????????????????????????????????? map.put(id, sql);
??????????????????????????? }
?????????????????? } catch (JDOMException e) {
??????????????????????????? e.printStackTrace();
?????????????????? }
?????????????????? return map;
???????? }
?
使用XML文件格式保存SQL語句據的時候需要注意,SQL語句中的大于(>)小于(<)和XML文件的格式沖突,有兩種解決方法,
第一:使用全角的大于(>)小于(<)號。
第二:使用<![CDATA[??? ]]>來包圍所有的內容。
?
另外,通常情況下(不使用<![CDATA[??? ]]>的時候)SQL的注釋只能使用XML的注釋格式(<!-- -->)。
?
使用XML格式的文件的有點:SQL語句可以正常書寫,文件解析相對簡單。
缺點:大于號,小于號的沖突;無法添加SQL注釋(通常只能使用XML格式的注釋)
?
TXT文件(以.SQL為后綴)
我們先說一下TXT文件(.sql文件)的格式
?
------------------------------------
--@ SQL-1
------------------------------------
SELECT
??? SYSDATE
FROM
??? DUAL(SQL)
???
------------------------------------
--@ SQL-2
-- 某某用途的SQL文
------------------------------------
SELECT
??? SYSDATE, ROWID?? -- 某某字段
FROM
??? DUAL(SQL)???????? -- 某某表
?
使用txt格式的文件,非常的簡單,和一般的寫SQL語句一樣,可以使用任何的SQL標準語法。只是有一個地方需要注意,就是每個SQL語句的頭注釋的地方加上一行特殊的內容用來標記SQL語句的ID:
--@ SQL-1
?
這樣的文件解析比較困難,但是也不是不能做,解析代碼如下:
???????? protected Map analysis() {
?
?????????????????? Map sqlMap = new HashMap();
?
?????????????????? String id = null;
?????????????????? StringBuffer sql = new StringBuffer();
?
?????????????????? if (this.getInputStream() != null) {
??????????????????????????? BufferedReader sqlFileReader = null;
??????????????????????????? try {
???????????????????????????????????? sqlFileReader = new BufferedReader(
??????????????????????????????????????????????????????? new InputStreamReader(this.getInputStream()));
???????????????????????????????????? String currentLine = null;
?
???????????????????????????????????? // 逐行讀取文件內容
???????????????????????????????????? while ((currentLine = sqlFileReader.readLine()) != null) {
?
?????????????????????????????????????????????? // 發現新的SQL語句,將已經發現的SQL語句放在SQL容器中
?????????????????????????????????????????????? if (currentLine.startsWith("--@")) {
??????????????????????????????????????????????????????? if (id != null) {
???????????????????????????????????????????????????????????????? sqlMap.put(id, sql.toString());
???????????????????????????????????????????????????????????????? id = null;
???????????????????????????????????????????????????????????????? sql = new StringBuffer();
??????????????????????????????????????????????????????? }
??????????????????????????????????????????????????????? id = currentLine.substring("--@".length()).trim();
?????????????????????????????????????????????? } else if (currentLine.startsWith("--")) {
??????????????????????????????????????????????????????? // 不讀取注視行
??????????????????????????????????????????????????????? continue;
?????????????????????????????????????????????? } else {
??????????????????????????????????????????????????????? // 非注釋,ID行
?
??????????????????????????????????????????????????????? // 去掉SQL語句行的末尾注視
??????????????????????????????????????????????????????? if (currentLine.length() > 0) {
???????????????????????????????????????????????????????????????? int commentsIndex = currentLine.indexOf("--");
???????????????????????????????????????????????????????????????? if (commentsIndex > 0) {
?????????????????????????????????????????????????????????????????????????? currentLine = currentLine.substring(0,
???????????????????????????????????????????????????????????????????????????????????????????? commentsIndex);
???????????????????????????????????????????????????????????????? }
???????????????????????????????????????????????????????????????? // 將換行符替換為空格
???????????????????????????????????????????????????????????????? sql.append(currentLine + " ");
??????????????????????????????????????????????????????? } else {
???????????????????????????????????????????????????????????????? // 將換行符替換為空格
???????????????????????????????????????????????????????????????? sql.append(" ");
??????????????????????????????????????????????????????? }
?
?????????????????????????????????????????????? }
?????????????????????????????????????????????? currentLine = null;
???????????????????????????????????? }
???????????????????????????????????? if (id != null) {
?????????????????????????????????????????????? sqlMap.put(id, sql.toString());
???????????????????????????????????? }
?
??????????????????????????? } catch (IOException ioex) {
???????????????????????????????????? ioex.printStackTrace();
??????????????????????????? } finally {
???????????????????????????????????? if(sqlFileReader != null) {
?????????????????????????????????????????????? try {
??????????????????????????????????????????????????????? sqlFileReader.close();
?????????????????????????????????????????????? } catch (IOException e) {
???????????????????????????????????????????????????????
?????????????????????????????????????????????? }
???????????????????????????????????? }
??????????????????????????? }
?????????????????? }
?????????????????? return sqlMap;
???????? }
?
使用TXT格式的文件的有點:SQL語句書寫非常方便,可以拷貝出來直接運行,可以使用標準的SQL注釋格式。
缺點:每個SQL語句必須添加特殊的ID標志(相對與Properties和xml來說可能也不算是缺點),解析困難,需要較強的文本接卸的能力。
?
通用SQL文件讀取庫
如何設計一個同時支持三種文件格式的SQL文件讀取庫?
?
?
首先我們抽象出一個SqlManager的接口,它提供通過Key獲得對應SQL語句的操作。代碼為:
public interface SqlManager {
???????? /**
???????? ?* 獲取指定Key的SQL語句
???????? ?*
???????? ?* @param key
???????? ?*??????????? SQL語句的Key值
???????? ?* @return SQL語句內容
???????? ?* @throws SqlMgntException
???????? ?*???????????? 無法取得SQL語句時拋出此異常。
???????? ?*/
???????? public String getSql(String key) throws SqlMgntException;
}
?
然后使用一個抽象基類,來定義通用的屬性和操作,代碼為:
public abstract class AbstractSqlManager implements SqlManager {
?
???????? /* SQL文件輸入流 */
???????? private final InputStream is;
?
???????? /**
???????? ?* SQL文件輸入流獲取方法。
???????? ?*
???????? ?* @return SQL文件輸入流
???????? ?*/
???????? public InputStream getInputStream() {
?????????????????? return is;
???????? }
?
???????? /**
???????? ?* 默認構造方法
???????? ?*
???????? ?* @param is
???????? ?*??????????? SQL文件輸入流
???????? ?*/
???????? public AbstractSqlManager(InputStream is) {
?????????????????? this.is = is;
???????? }
?
???????? /* SQL語句容器 */
???????? private Map sqlContainer = null;
?
???????? /* 同步KEY */
???????? private final Object syncKey = new Object();
?
???????? /**
???????? ?* 通過SQL語句ID獲取SQL語句內容。
???????? ?*
???????? ?* @param key
???????? ?*??????????? SQL語句ID
???????? ?* @return SQL語句
???????? ?*/
???????? public String getSql(final String key) throws SqlMgntException {
?????????????????? synchronized (syncKey) {
??????????????????????????? if (sqlContainer == null) {
???????????????????????????????????? this.sqlContainer = initContainer();
??????????????????????????? }
?????????????????? }
?????????????????? return (String) sqlContainer.get(key);
???????? }
????????
???????? /**
???????? ?* 抽象的模板方法,用來解析不同類型的SQL文件。
???????? ?* @return 配對的SQL內容
???????? ?*/
???????? protected abstract Map initContainer();
?
}
?
注意其中的抽象方法:
protected abstract Map initContainer();
通過這個抽象方法把SQL文件的解析交給了具體的實例(模式參看:模板方法)
?
三個具體的SqlManager類我們不必具體說了,都是對上述抽線的解析方法的實現。
?
工廠類可以根據對應文件的后綴明創建對應的SqlManager。代碼為:
public class SqlManagerFactory {
?
???????? /* XMl文件類型 */
???????? public static final String TYPE_XML = ".xml";
?
???????? /* SQL文件類型 */
???????? public static final String TYPE_SQL = ".sql";
?
???????? /* 屬性文件類型 */
???????? public static final String TYPE_PROP = ".properties";
?
???????? // begin ma.zhao@dl.cn 2006/03/09
???????? // add singleton map for SqlManager
?
???????? private static Map sqlManagerContainer = new HashMap();
?
???????? // end ma.zhao@dl.cn 2006/03/09
?
???????? /**
???????? ?* 創建對應的SqlManager實現類
???????? ?*
???????? ?* @param filePath
???????? ?*??????????? SQL文件相對路徑和文件名
???????? ?* @return
???????? ?*/
???????? public static SqlManager createSqlManager(String filePath) throws SqlMgntException {
?????????????????? SqlManager manager = null;
?????????????????? synchronized (sqlManagerContainer) {
?
??????????????????????????? manager = (SqlManager) sqlManagerContainer.get(filePath);
???????????????????????????
??????????????????????????? // 如果SqlManager不存在,則根據文件初始化SqlManager,
??????????????????????????? // 并且將它放在SqlManager容器中
??????????????????????????? if (manager == null) {
????????????????????????????????????
???????????????????????????????????? InputStream is = SqlManagerFactory.class.getClassLoader()
??????????????????????????????????????????????????????? .getResourceAsStream(filePath);
????????????????????????????????????
???????????????????????????????????? if(is == null) {
?????????????????????????????????????????????? throw new SqlMgntException(
"Sql File Not Fount! Input file name path is:" + filePath);
???????????????????????????????????? }
????????????????????????????????????
???????????????????????????????????? if (filePath.endsWith(TYPE_XML)
??????????????????????????????????????????????????????? || filePath.endsWith(TYPE_XML.toUpperCase())) {
?????????????????????????????????????????????? manager = new SqlManagerXmlImpl(is);
???????????????????????????????????? } else if (filePath.endsWith(TYPE_SQL)
??????????????????????????????????????????????????????? || filePath.endsWith(TYPE_SQL.toUpperCase())) {
?????????????????????????????????????????????? manager = new SqlManagerSqlImpl(is);
???????????????????????????????????? } else if (filePath.endsWith(TYPE_PROP)
??????????????????????????????????????????????????????? || filePath.endsWith(TYPE_PROP.toUpperCase())) {
?????????????????????????????????????????????? manager = new SqlManagerPropImpl(is);
???????????????????????????????????? } else {
?????????????????????????????????????????????? throw new SqlMgntException(
"Sql File Type Not Support, Input file path is:" + filePath);
???????????????????????????????????? }
????????????????????????????????????
???????????????????????????????????? //
???????????????????????????????????? if (manager != null) {
?????????????????????????????????????????????? sqlManagerContainer.put(filePath, manager);
???????????????????????????????????? }
??????????????????????????? }
?????????????????? }
?????????????????? return manager;
???????? }
}
注意上述代碼可以控制每個對應的SQL文件始終只有一個對應的SqlManager,不會對同一個文件創建多個SqlManager類。
?
所有上述過程中的異常都以SqlMgntException的方式向上拋出,代碼為:
public class SqlMgntException extends Exception {
?
???????? /**
???????? ?*
???????? ?*/
???????? private static final long serialVersionUID = 1L;
?
???????? public SqlMgntException() {
?????????????????? super();
?????????????????? // TODO Auto-generated constructor stub
???????? }
?
???????? public SqlMgntException(String message, Throwable cause) {
?????????????????? super(message, cause);
?????????????????? // TODO Auto-generated constructor stub
???????? }
?
???????? public SqlMgntException(String message) {
?????????????????? super(message);
?????????????????? // TODO Auto-generated constructor stub
???????? }
?
???????? public SqlMgntException(Throwable cause) {
?????????????????? super(cause);
?????????????????? // TODO Auto-generated constructor stub
???????? }
}
?
優化的方向,
1 初始話加載所有的SQL語句
???????? 程序運行之初,將所有的SQL語句裝載在內存中。
轉載于:https://blog.51cto.com/10164643/1783112
總結
以上是生活随笔為你收集整理的项目中SQL语句文件保存及提取方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS3中的网格背景应该如何实现
- 下一篇: centos6.4 源码安装mysql5