当前位置:天才代写 > tutorial > JAVA 教程 > 消除JDBC的瓶颈

消除JDBC的瓶颈

2017-11-11 08:00 星期六 所属: JAVA 教程 浏览:445

副标题#e#

摘要

大部门的J2EE(Java 2 Platform, Enterprise Edition)和其它范例的Java应用都需要与数据库举办交互。与数据库举办交互需要重复地挪用SQL语句、毗连打点、事务生命周期、功效处理惩罚和异常处理惩罚。这些操纵都是很常见的;不外这个反复的利用并不是肯定需要的。在这篇文章中,我们将先容一个机动的架构,它可以办理与一个兼容JDBC的数据库的反复交互问题。

最近在为公司开拓一个小的J2EE应用时,我对执行和处理惩罚SQL挪用的进程感想很贫苦。我认为在Java开拓者中必然有人已经开拓了一个架构来消除这个流程。不外,搜索诸如"Java SQL framework" 可能 "JDBC [Java Database Connectivity] framework"等都没有获得满足的功效。

问题的提出?

在报告一个办理要领之前,我们先将问题描写一下。假如你要通过一个JDBC数据源执行SQL指令时,你凡是需要做些什么呢?

1、成立一个SQL字符串

2、获得一个毗连

3、获得一个预处理惩罚语句(prepared statement)

4、将值组合到预处理惩罚语句中

5、执行语句

6、遍历功效集而且形成功效工具

尚有,你必需思量那些不绝发生的SQLExceptions;假如这些步调呈现差异的处所,SQLExecptions的开销就会复合在一起,因为你必需利用多个try/catch块。

不外,假如我们仔细地调查一下这些步调,就可以发明这个进程中有几个部门在执行期间是稳定的:你凡是都利用同一个方法来获得一个毗连和一个预处理惩罚语句。组合预处理惩罚语句的方法凡是也是一样的,而执行和处理惩罚查询则是特定的。你可以在六个步调中提取中个中三个。纵然在有点差异的步调中,我们也可以在个中提取出民众的成果。可是我们应该奈何自动化及简化这个进程呢?


#p#副标题#e#

查询架构

我们首先界说一些要领的签名,这些要领是我们将要用来执行一个SQL语句的。要留意让它保持简朴,只传送需要的变量,我们可以编写一些雷同下面签名的要领:

public Object[] executeQuery(String sql, Object[] pStmntValues,
ResultProcessor processor);

我们知道在执行期间有所差异的方面是SQL语句、预处理惩罚语句的值和功效集是如何阐明的。很明明,sql参数指的是SQL语句。pStmntValues工具数据包括有必需插入到预处理惩罚语句中的值,而processor参数则是处理惩罚功效集而且返回功效工具的一个工具;我将在后头更具体地接头这个工具。

在这样一个要领签名中,我们就已经将每个JDBC数据库交互中三个稳定的部门隔分开来。此刻让我们接头exeuteQuery()及其它支持的要领,它们都是SQLProcessor类的一部门:

public class SQLProcessor {
public Object[] executeQuery(String sql, Object[] pStmntValues,
ResultProcessor processor) {
//Get a connection (assume it's part of a ConnectionManager class)
Connection conn = ConnectionManager.getConnection();
//Hand off our connection to the method that will actually execute
//the call
Object[] results = handleQuery(sql, pStmntValues, processor, conn);
//Close the connection
closeConn(conn);
//And return its results
return results;
}
protected Object[] handleQuery(String sql, Object[] pStmntValues,
ResultProcessor processor, Connection conn) {
//Get a prepared statement to use
PreparedStatement stmnt = null;
try {
//Get an actual prepared statement
stmnt = conn.prepareStatement(sql);
//Attempt to stuff this statement with the given values. If
//no values were given, then we can skip this step.
if(pStmntValues != null) {
PreparedStatementFactory.buildStatement(stmnt, pStmntValues);
}
//Attempt to execute the statement
ResultSet rs = stmnt.executeQuery();
//Get the results from this query
Object[] results = processor.process(rs);
//Close out the statement only. The connection will be closed by the
//caller.
closeStmnt(stmnt);
//Return the results
return results;
//Any SQL exceptions that occur should be recast to our runtime query
//exception and thrown from here
} catch(SQLException e) {
String message = "Could not perform the query for " + sql;
//Close out all resources on an exception
closeConn(conn);
closeStmnt(stmnt);
//And rethrow as our runtime exception
throw new DatabaseQueryException(message);
}
}
}
...
}

#p#副标题#e#

在这些要领中,有两个部门是不清楚的:PreparedStatementFactory.buildStatement() 和 handleQuery()’s processor.process()要领挪用。buildStatement()只是将参数工具数组中的每个工具放入到预处理惩罚语句中的相应位置。譬喻:

#p#分页标题#e#

...
//Loop through all objects of the values array, and set the value
//of the prepared statement using the value array index
for(int i = 0; i < values.length; i++) {
//If the object is our representation of a null value, then handle it separately
if(value instanceof NullSQLType) {
stmnt.setNull(i + 1, ((NullSQLType) value).getFieldType());
} else {
stmnt.setObject(i + 1, value);
}
}

由于stmnt.setObject(int index, Object value)要领不行以接管一个null工具值,因此我们必需利用本身非凡的结构:NullSQLType类。NullSQLType暗示一个null语句的占位符,而且包括有该字段的JDBC范例。当一个NullSQLType工具实例化时,它得到它将要取代的字段的SQL范例。如上所示,当预处理惩罚语句通过一个NullSQLType组适时,你可以利用NullSQLType的字段范例来汇报预处理惩罚语句该字段的JDBC范例。这就是说,你利用NullSQLType来表白正在利用一个null值来组合一个预处理惩罚语句,而且通过它存放该字段的JDBC范例。

此刻我已经表明白PreparedStatementFactory.buildStatement()的逻辑,我将表明另一个缺少的部门:processor.process()。processor是ResultProcessor范例,这是一个接口,它暗示由查询功效集成立域工具的类。ResultProcessor包括有一个简朴的要领,它返回功效工具的一个数组:

public interface ResultProcessor {
public Object[] process(ResultSet rs) throws SQLException;
}

一个典范的功效处理惩罚器遍历给出的功效集,而且由功效荟萃的行中形成域工具/工具布局。此刻我将通过一个现实世界中的例子来综合报告一下。

查询例子

你常常都需要操作一个用户的信息表由数据库中获得一个用户的工具,假设我们利用以下的USERS表:

USERS table
Column Name Data Type
ID NUMBER
USERNAME VARCHAR
F_NAME VARCHAR
L_NAME VARCHAR
EMAIL VARCHAR

而且假设我们拥有一个User工具,它的结构器是:

public User(int id, String userName, String firstName,
String lastName, String email)

假如我们没有利用这篇文章报告的架构,我们将需要一个颇大的要领来处理惩罚由数据库中吸收用户信息而且形成User工具。那么我们应该奈何操作我们的架构呢?

首先,我们结构SQL语句:

private static final String SQL_GET_USER = "SELECT * FROM USERS WHERE ID = ?";

接着,我们形成ResultProcessor,我们将利用它来接管功效集而且形成一个User工具:

public class UserResultProcessor implements ResultProcessor {
//Column definitions here (i.e., COLUMN_USERNAME, etc...)
..
public Object[] process(ResultSet rs) throws SQLException {
//Where we will collect all returned users
List users = new ArrayList();
User user = null;
//If there were results returned, then process them
while(rs.next()) {
user = new User(rs.getInt(COLUMN_ID), rs.getString(COLUMN_USERNAME),
rs.getString(COLUMN_FIRST_NAME), rs.getString(COLUMN_LAST_NAME),
rs.getString(COLUMN_EMAIL));
users.add(user);
}
return users.toArray(new User[users.size()]);

最后,我们将写一个要领来执行查询而且返回User工具:

public User getUser(int userId) {
//Get a SQL processor and execute the query
SQLProcessor processor = new SQLProcessor();
Object[] users = processor.executeQuery(SQL_GET_USER_BY_ID,
new Object[] {new Integer(userId)},
new UserResultProcessor());
//And just return the first User object
return (User) users[0];
}

这就是全部。我们只需要一个处理惩罚类和一个简朴的要领,我们就可以无需举办直接的毗连维护、语句和异常处理惩罚。另外,假如我们拥有别的一个查询由用户表中获得一行,譬喻通过用户名可能暗码,我们可以从头利用UserResultProcessor。我们只需要插入一个差异的SQL语句,而且可以从头利用以前要领的用户处理惩罚器。由于返回行的元数据并不依赖查询,所以我们可以从头利用功效处理惩罚器。

#p#副标题#e#

更新的架构

那么数据库更新又如何呢?我们可以用雷同的要领处理惩罚,只需要举办一些修改就可以了。首先,我们必需增加两个新的要领到SQLProcessor类。它们雷同executeQuery()和handleQuery()要领,除了你无需处理惩罚功效集,你只需要将更新的行数作为挪用的功效:

public void executeUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor) {
//Get a connection
Connection conn = ConnectionManager.getConnection();
//Send it off to be executed
handleUpdate(sql, pStmntValues, processor, conn);
//Close the connection
closeConn(conn);
}
protected void handleUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor, Connection conn) {
//Get a prepared statement to use
PreparedStatement stmnt = null;
try {
//Get an actual prepared statement
stmnt = conn.prepareStatement(sql);
//Attempt to stuff this statement with the given values. If
//no values were given, then we can skip this step.
if(pStmntValues != null) {
PreparedStatementFactory.buildStatement(stmnt, pStmntValues);
}
//Attempt to execute the statement
int rows = stmnt.executeUpdate();
//Now hand off the number of rows updated to the processor
processor.process(rows);
//Close out the statement only. The connection will be closed by the
//caller.
closeStmnt(stmnt);
//Any SQL exceptions that occur should be recast to our runtime query
//exception and thrown from here
} catch(SQLException e) {
String message = "Could not perform the update for " + sql;
//Close out all resources on an exception
closeConn(conn);
closeStmnt(stmnt);
//And rethrow as our exception
throw new DatabaseUpdateException(message);
}
}

#p#分页标题#e#

这些要领和查询处理惩罚要领的区别仅在于它们是如那里理惩罚挪用的功效:由于一个更新的操纵只返回更新的行数,因此我们无需功效处理惩罚器。我们也可以忽略更新的行数,不外有时我们大概需要确认一个更新的发生。UpdateProcessor得到更新行的数据,而且可以对行的数目举办任何范例简直认可能记录:

public interface UpdateProcessor {
public void process(int rows);
}

假如一个更新的挪用必需至少更新一行,这样实现UpdateProcessor的工具可以查抄更新的行数,而且可以在没有行被更新的时候抛出一个特定的异常。可能,我们大概需要记录下更新的行数,初始化一个功效处理惩罚可能触发一个更新的事件。你可以将这些需求的代码放在你界说的UpdateProcessor中。你应该知道:各类大概的处理惩罚都是存在的,并没有任何的限制,可以很容易得集成到架构中。

#p#副标题#e#

更新的例子

我将继承利用上面表明的User模子来报告如何更新一个用户的信息:

首先,结构SQL语句:

private static final String SQL_UPDATE_USER = "UPDATE USERS SET USERNAME = ?, " +
"F_NAME = ?, " +
"L_NAME = ?, " +
"EMAIL = ? " +
"WHERE ID = ?";

接着,结构UpdateProcessor,我们将用它来检讨更新的行数,而且在没有行被更新的时候抛出一个异常:

public class MandatoryUpdateProcessor implements UpdateProcessor {
public void process(int rows) {
if(rows < 1) {
String message = "There were no rows updated as a result of this operation.";
throw new IllegalStateException(message);
}
}
}

最后就写编写执行更新的要领:

public static void updateUser(User user) {
SQLProcessor sqlProcessor = new SQLProcessor();
//Use our get user SQL statement
sqlProcessor.executeUpdate(SQL_UPDATE_USER,
new Object[] {user.getUserName(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
new Integer(user.getId())},
new MandatoryUpdateProcessor());

如前面的例子一样,我们无需直接处理惩罚SQLExceptions和Connections就执行了一个更新的操纵。

#p#副标题#e#

事务

前面已经说过,我对其它的SQL架构实现都不满足,因为它们并不拥有预界说语句、独立的功效集处理惩罚可能可处理惩罚事务。我们已经通过buildStatement() 的要领办理了预处理惩罚语句的问题,尚有差异的处理惩罚器(processors)已经将功效集的处理惩罚疏散出来。不外尚有一个问题,我们的架构如那里理惩罚事务呢?

一个事务和一个独立SQL挪用的区别只是在于在它的生命周期内,它都利用同一个毗连,尚有,自动提交符号也必需配置为off。因为我们必需有一个要领来指定一个事务已经开始,而且在何时竣事。在整个事务的周期内,它都利用同一个毗连,而且在事务竣事的时候举办提交。

要处理惩罚事务,我们可以重用SQLProcessor的许多方面。为什么将该类的executeUpdate() 和handleUpdate()独立开来呢,将它们团结为一个要领也很简朴的。我这样做是为了将真正的SQL执行和毗连打点独立开来。在成立事务系统时,我们必需在几个SQL执行期间对毗连举办节制,这样做就利便多了。

为了令事务事情,我们必需保持状态,出格是毗连的状态。直到此刻,SQLProcessor照旧一个无状态的类。它缺乏成员变量。为了重用SQLProcessor,我们建设了一个事务封装类,它吸收一个SQLProcessor而且透明地处理惩罚事务的生命周期。

详细的代码是:

#p#分页标题#e#

public class SQLTransaction {
private SQLProcessor sqlProcessor;
private Connection conn;
//Assume constructor that initializes the connection and sets auto commit to false
...
public void executeUpdate(String sql, Object[] pStmntValues,
UpdateProcessor processor) {
//Try and get the results. If an update fails, then rollback
//the transaction and rethrow the exception.
try {
sqlProcessor.handleUpdate(sql, pStmntValues, processor, conn);
} catch(DatabaseUpdateException e) {
rollbackTransaction();
throw e;
}
}
public void commitTransaction() {
//Try to commit and release all resources
try {
conn.commit();
sqlProcessor.closeConn(conn);
//If something happens, then attempt a rollback and release resources
} catch(Exception e) {
rollbackTransaction();
throw new DatabaseUpdateException("Could not commit the current transaction.");
}
}
private void rollbackTransaction() {
//Try to rollback and release all resources
try {
conn.rollback();
conn.setAutoCommit(true);
sqlProcessor.closeConn(conn);
//If something happens, then just swallow it
} catch(SQLException e) {
sqlProcessor.closeConn(conn);
}
}
}

SQLTransaction拥有很多新的要领,可是个中的大部门都是很简朴的,而且只处理惩罚毗连可能事务处理惩罚。在整个事务周期内,这个事务封装类只是在SQLProcessor中增加了一个简朴的毗连打点。当一个事务开始时,它吸收一个新的毗连,而且将其自动提交属性配置为false。其后的每个执行都是利用同一个毗连(传送到SQLProcessor的handleUpdate()要领中),因此事务保持完整。

只有当我们的耐久性工具可能要领挪用commitTransaction()时,事务才被提交,而且封锁毗连。假如在执行期间产生了异常,SQLTransaction可以捕获该异常,自动举办回滚,而且抛出异常。

#p#副标题#e#

事务例子

让我们来看一个简朴的事务

//Reuse the SQL_UPDATE_USER statement defined above
public static void updateUsers(User[] users) {
//Get our transaction
SQLTransaction trans = sqlProcessor.startTransaction();
//For each user, update it
User user = null;
for(int i = 0; i < users.length; i++) {
user = users[i];
trans.executeUpdate(SQL_UPDATE_USER,
new Object[] {user.getUserName(),
user.getFirstName(),
user.getLastName(),
user.getEmail(),
new Integer(user.getId())},
new MandatoryUpdateProcessor());
}
//Now commit the transaction
trans.commitTransaction();
}

上面为我们展示了一个事务处理惩罚的例子,固然简朴,但我们可以看出它是如何事情的。假如在执行executeUpdate()要领挪用时失败,这时将会回滚事务,而且抛出一个异常。挪用这个要领的开拓者从不需要担忧事务的回滚可能毗连是否已经封锁。这些都是在靠山处理惩罚的。开拓者只需要体贴贸易的逻辑。

事务也可以很轻松地处理惩罚一个查询,不外这里我没有提及,因为事务凡是都是由一系列的更新构成的。

问题

在我写这篇文章的时候,对付这个架构,我提出了一些疑问。这里我将这些问题提出来,因为你们大概也会遇到同样的问题。

自界说毗连

假如每个事务利用的毗连纷歧样时会如何?假如ConnectionManager需要一些变量来汇报它从哪个毗连池获得毗连?你可以很容易就将这些特性荟萃到这个架构中。executeQuery() 和 executeUpdate()要领(属于SQLProcessor和SQLTransaction类)将需要吸收这些自界说的毗连参数,而且将他们传送到ConnectionManager。要记得所有的毗连打点都将在执行的要领中产生。

另外,假如更面向工具化一点,毗连制造者可以在初始化时传送到SQLProcessor中。然后,对付每个差异的毗连制造者范例,你将需要一个SQLProcessor实例。按照你毗连的可变性,这或者不是抱负的做法。

ResultProcessor返回范例

为什么ResultProcessor接口指定了process()要领应该返回一个工具的数组?为什么不利用一个List?在我利用这个架构来开拓的大部门应用中,SQL查询只返回一个工具。假如结构一个List,然后将一个工具插手个中,这样的开销较大,而返回一个工具的一个数组是较量简朴的。不外,假如在你的应用中需要利用工具collections,那么返回一个List更好。

SQLProcessor初始打点

#p#分页标题#e#

在这篇文章的例子中,对付必需执行一个SQL挪用的每个要领,初始化一个SQLProcessor。由于SQLProcessors完全是没有状态的,所以在挪用的要领中将processor独立出来是很有意义的。

而对付SQLTransaction类,则是缺少状态的,因此它不能独立利用。我发起你为SQLProcessor类增加一个简朴的要领,而不是进修如何初始化一个SQLTransaction,如下所示:

public SQLTransaction startTransaction() {
return new SQLTransaction(this);
}

这样就会令全部的事务成果都在SQLProcessor类中会见到,而且限制了你必需知道的要领挪用。

数据库异常

我利用了几种差异范例的数据库异常将全部大概在运行时产生的SQLExceptions封装起来。在我利用该架构的应用中,我发明将这些异常酿成runtime exceptions更为利便,所以我利用了一个异常处理惩罚器。你大概认为这些异常应该声明,这样它们可以只管在错误的产生点被处理惩罚。不外,这样就会令SQL异常处理惩罚的流程和以前的SQLExceptions一样,这种环境我们是只管制止的。

省心的JDBC programming

这篇文章提出的架构可以令查询、更新和事务执行的操纵越发简朴。在雷同的SQL挪用中,你只需要存眷可重用的支持类中的一个要领。我的但愿是该架构可以提高你举办JDBC编程的效率。

 

    关键字:

天才代写-代写联系方式