第十九章 SQL文保存方法

本文探讨了在JavaWeb项目中管理SQL语句的方法,包括使用Properties、XML和TXT文件的不同方式及优缺点。并介绍了一个通用的SQL文件读取库的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第十九章 SQL文保存方法

在开发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语句。

使用ClassgetResourceAsStream方法可以获得对文件引用的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支持的标准的属性文件(相当于Windowsini文件的支持)。

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需要自己写文件解析代码,由于JavaXML提供了内置的支持,并且第三方的开源库也很多,并且非常容易使用所以从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标志(相对与Propertiesxml来说可能也不算是缺点),解析困难,需要较强的文本接卸的能力。

通用SQL文件读取库

如何设计一个同时支持三种文件格式的SQL文件读取库?

<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="_x0000_i1025" type="#_x0000_t75" style='width:414.75pt; height:245.25pt'> <v:imagedata src="file:///C:\DOCUME~1\mazhao\LOCALS~1\Temp\msohtmlclip1\01\clip_image001.jpg" o:title="sqlwrapper" /> </v:shape><![endif]--><!--[if !vml]--><!--[endif]-->

首先我们抽象出一个SqlManager的接口,它提供通过Key获得对应SQL语句的操作。代码为:

public interface SqlManager {

/**

* 获取指定KeySQL语句

*

* @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语句装载在内存中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值