现在的位置: 首页 > 综合 > 正文

A Generic JDBC Abstraction Framework(4)–Two Levels of Abstraction

2013年04月22日 ⁄ 综合 ⁄ 共 15481字 ⁄ 字号 评论关闭
Two Levels of Abstraction

抽象的2个级别

Now that we have a powerful, database-agnostic approach to exception handling, let's look at implementing an abstraction framework that will make JDBC much easier to use.

现在我们有一个强大的,与数据库无关的途径去处理异常,让我们看实现一个是jdbc更容易用的抽象框架。

The JDBC framework used in the sample application involves two levels of abstraction. (In Chapter 4 we discussed how frameworks are often layered, enabling developers using them to work with the appropriate level of abstraction for the task in hand.)

jdbc框架适用于在简单的应用涉及了二个层次的抽象。(在第4章中我们讨论了框架如何经常的分层,使开发者用他们去公事适当级别的抽象任务。)

The lower level of abstraction, in the com.interface21.jdbc.core package, takes care of JDBC workflow and exception handling. It achieves this using a callback approach, requiring application code to implement simple callback interfaces.

低级别的抽象,在com.interface21.jdbc.core包中,注意jdbc工作流和异常处理。它完成这些用一个回调方法,要求应用代码去实现简单的回调接口。

The higher level of abstraction, in the com.interface21.jdbc.object package (which we've seen in use), builds on this to provide a more object-oriented, JDO-like interface that models RDBMS operations as Java objects. This completely hides the details of JDBC from application code that initiates these operations.

高级别的抽象,在com.interface21.jdbc.object 包中(我们已经看见在用的),建立在这个去提供一个更面向对象,类似于jdo接口建立rdbms操作为java对象的模型。它完全的从应用代码中把jdbc的内部机制隐藏了。

Let's look at each level of abstraction in turn.

让我们轮流的看每个级别的抽象。

A Framework to Control JDBC Workflow and Error Handling

控制jdbc工作流和错误处理的框架

The lower level of abstraction handles the issuing of JDBC queries and updates, taking care of resource cleanup and translating SQLExceptions into generic exceptions using the SQLExceptionTranslater discussed above.

低级抽象处理了jdbc查询和更新的问题,注意资源清理和转换sqlexceptions到通用异常,用在上面讨论的SQLExceptionTranslater

"Inversion of Control" Revisited

控制反转复习

Remember our discussion of the Strategy design pattern and "inversion of control" in Chapter 4? We saw that sometimes the only way to ensure that complex error handling is concealed by generic infrastructure code is for infrastructure code to invoke application code (an approach termed "inversion of control"), rather than for application code to invoke infrastructure code as in a traditional class library. We saw that infrastructure packages that apply this approach are usually termed frameworks, rather than class libraries.

记得我们在第4章讨论的策略模式和“控制反转”?我们明白有时候确保复杂的错误处理唯一的方式是被通用基础代码隐藏去调用应用代码(一种方法称为”控制反转“),比起应用代码去调用基础代码在一个传统的类库中。我们理解基础包应用这个方法通常叫做框架,而不是类库。

The complexity of JDBC error handling demands this approach. We want a framework class to use the JDBC API to execute queries and updates and handle any errors, while we supply the SQL and any parameter values. To achieve this, the framework class must invoke our code, rather than the reverse.

jdbc错误处理需求的复杂性需要这个方法。我们想要一个框架类去用jdbc api 去执行查询和更新并处理任何错误,当我们支持sql和任何参数值。为了完成这个,框架类必须调用我们的代码,而不是反过来。

The com.interface21.jdbc.core package

com.interface21.jdbc.core包

All JDBC-specific classes comprising the lower level of abstraction are in the com.interface21.jdbc.core package.

所有的特定的jdbc类在com.interface21.jdbc.core 包中由低级别的抽象组成。

The most important class in the com.interface21.jdbc.core package is JdbcTemplate, which implements core workflow and invokes application code as necessary. The methods in JdbcTemplate run queries while delegating the creation of PreparedStatements and the extraction of the results from JDBC ResultSets to two callback interfaces: the PreparedStatementCreator interface and the RowCallbackHandler interface. Application developers will need to provide implementations of these interfaces, not execute JDBC statements directly or implement JDBC exception handling.

在com.interface21.jdbc.core 包中最重要的类是jdbcTemplate,实现了核心工作流和必要的调用了应用程序代码。在JdbcTemplate 运行查询方法中,当委托PreparedStatements 的创建和从jdbc结果集中抽取结果是2个回调接口:the PreparedStatementCreator interface and the RowCallbackHandler interface.应用程序开发者需要提供这些接口的实现,不是直接的执行jdbc语句或者实现jdbc的异常处理。

The following UML class diagram illustrates the relationship of the JdbcTemplate class to the helper classes it uses and the application-specific classes that enable it to be parameterized:

下面的uml类图阐明了jdbcTemplate类到它用的帮助类的关系和特定与应用类的使它被参数化:

figu334_1_0

Let's consider each class and interface in turn. The classes above the horizontal line belong to the framework; those below the line indicate how application-specific classes implement framework interfaces.

让我们轮流的考虑每个类和接口。在水平线上的属于框架;在线下的表明特定的应用程序代码如何实现接口。

The PreparedStatementCreator Interface and Related Classes

PreparedStatementCreator 接口和关联类

The PreparedStatementCreator interface must be implemented by application-specific classes to create a java.sql.PreparedStatement, given a java.sql.Connection. This means specifying the SQL and setting bind parameters for an application-specific query or update, which will be run by the JdbcTemplate class. Note that an implementation of this interface doesn't need to worry about obtaining a connection or catching any SQLExceptions that may result from its work. The PreparedStatement it creates will be executed by the JdbcTemplate class. The interface is as follows:

PreparedStatementCreator 接口必须被特定的应用程序类实现去创建一个java.sql.PreparedStatement,得到一个java.sql.Connection。这意味着特定的sql和为一个特定应用程序设置绑定参数查询和更新,将被JdbcTemplate 类运行。注意接口的实现不需要担心获得连接和捕获任何可能从它的工作中导致的sqlexception。PreparedStatement 的创建将被JdbcTemplate 类执行。接口在下面:

public interface PreparedStatementCreator {
PreparedStatement createPreparedStatement (Connection conn)
throws SQLException;
}

The following shows a typical implementation, which uses standard JDBC methods to construct a PreparedStatement with the desired SQL and set bind variable values. Like many implementations of such simple interfaces, this is an anonymous inner class:

下面显示了一个典型的实现,用标准jdbc方法去构造一个PreparedStatement 带着预期的sql和设置绑定变量值。想许多简单接口的实现,这是一个匿名内部类:

PreparedStatementCreator psc = new PreparedStatementCreator() {

public PreparedStatement createPreparedStatement (Connection conn)
throws SQLException {
PreparedStatement ps = conn. prepareStatement (
"SELECT seat_id AS id FROM available_seats WHERE " +
"performance_id = ? AND price_band_id = ?");
ps.setInt(1, performanceId);
ps.setInt(2, seatType);
return ps;
}
};

The PreparedStatementCreatorFactory class is a generic helper that can be used repeatedly to create PreparedStatementCreator objects with different parameter values, based on the same SQL statement and bind variable declarations. This class is largely used by the higher-level, object-abstraction framework described below; application code will normally define PreparedStatementCreator classes as shown above.

PreparedStatementCreatorFactory 类是一个通用的帮助类能被用于重复的创建PreparedStatementCreator 对象用不用的参数值,基于相同的sql语句和绑定变量声明。这个类被在下面描述的高级的抽象对象框架利用;应用代码将通常定义PreparedStatementCreator 类在上面显示的。

The RowCallbackHandler Interface and Related Classes

回调接口和关联类

The RowCallbackHandler interface must be implemented by application-specific classes to extract column values from each row of the ResultSet returned by a query. The JdbcTemplate class handles iteration over the ResultSet. The implementation of this interface may also leave SQLExceptions uncaught: the JdbcTemplate will handle them. The interface is as follows:

回调接口必须被特定的应用类实现去抽取列值从结果集的每列中。jdbcTemplate类处理迭代结果集。接口的实现也应该留下未捕获的异常:JdbcTemplate 将捕获他们,

public interface RowCallbackHandler {
void processRow(ResultSet rs) throws SQLException;
}

Implementations should know the number of columns and data types to expect in the ResultSet. The following shows a typical implementation, which extracts an int value from each row and adds it to a list defined in the enclosing method:

实现,我不想说…

RowCallbackHandler rch = new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
int seatId = rs.getInt(1) ;
list.add(new Integer (seatId) );
}
};

The RowCountCallbackHandler class is a convenient framework implementation of this interface that stores metadata about column names and types and counts the rows in the ResultSet. Although it's a concrete class, its main use is as a superclass of application-specific classes.

RowCountCallbackHandler 是这个接口的便利框架实现,它存储了列名和类型在结果集中计数了每一行。即使他是一个具体的类,他主要用于特定应用代码的超类。

The ResultReader interface extends RowCallbackHandler to save the retrieved results in a java.util.List:

public interface ResultReader extends RowCallbackHandler {
List getResults() ;
}

The conversion of each row of the ResultSet to an object is likely to vary widely between implementations, so there is no standard implementation of this interface.

ResultSet 的每行转化到对象更可能有很广泛的实现,所以这里没有标准的接口实现。

Miscellaneous Classes

其他类

We've already examined the SQLExceptionTranslater interface and two implementations. The JdbcTemplate class uses an object of type SQLExceptionTranslater to convert SQLExceptions into our generic exception hierarchy, ensuring that this important part of the JdbcTemplate class's behavior can be parameterized.

The DataSourceUtils class contains a static method to obtain a Connection from a javax.sql.DataSource, converting any SQLException to an exception from our generic hierarchy, a method to close a Connection (again with appropriate error handling) and a method to obtain a DataSource fromJNDI. The JdbcTemplate class uses the DataSourceUtils class to simplify obtaining and closing connections.

DataSourceUtils 包含了静态方法获得连接,转换任何sqlexception到我们的通用层次异常中,一个方法关闭连接和一个方法从jndi中获取DataSource。

The JdbcTemplateClass: The Central Workflow

中央工作流:jdbc模板类

Now let's look at the implementation of the JdbcTemplate class itself. This is the only class that runs JDBC statements and catches SQLExceptions. It contains methods that can perform any query or update: the JDBC statement executed is parameterized by static SQL or a PreparedStatementCreator implementation supplied as a method argument.

现在我们着手于JdbcTemplate 类的自我实现。这个类仅仅运行jdbc语句和捕获sqlexception。它包含了能执行任何查询或者更新的方法:jdbc语句的执行被静态sql参数化或者一个PreparedStatementCreator 的实现支持方法参数。

The following is a complete listing of the JdbcTemplate class:

下面的不想翻译了,自己看代码….

package com.interface21.jdbc.core;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java14.java.util.logging.Level;
import java14.java.util.logging.Logger;
import javax.sql.DataSource;
import com.interface21.dao.DataAccessException;

public class JdbcTemplate {

The instance data consists of a Logger object, used to log info messages about SQL operations, a DataSource object, a boolean indicating whether SQLWarnings should be ignored or treated as errors, and the SQLExceptionTranslater helper object. All these instance variables are read-only after the JdbcTemplate has been configured, meaning that JdbcTemplate objects are threadsafe:

protected final Logger logger = Logger.getLogger(getClass().getName() );
private DataSource dataSource;
private boolean ignoreWarnings = true;
private SQLExceptionTranslater exceptionTranslater;

The constructor takes a DataSource, which will be used throughout the JdbcTemplate's lifecycle, and instantiates a default SQLExceptionTranslater object:

public JdbcTemplate(DataSource dataSource) {
this.dataSource = dataSource;
this.exceptionTranslater = new SQLStateSQLExceptionTranslater() ;
}

The following methods can be optionally used to modify default configuration before using the JdbcTemplate. They enable behavior on warnings and exception translation to be parameterized:

public void setIgnoreWarnings(boolean ignoreWarnings) {
this.ignoreWarnings = ignoreWarnings;
}

public boolean getIgnoreWarnings() {
return ignoreWarnings;
}

public void setExceptionTranslater(
SQLExceptionTranslater exceptionTranslater) {
this.exceptionTranslater = exceptionTranslater;
}

public DataSource getDataSource() {
return dataSource;
}

The remainder of the JdbcTemplate class consists of methods that perform JDBC workflow using the callback interfaces discussed above.

The simplest query() method, which takes a static SQL string and a RowCallbackHandler to extract results from the query ResultSet, illustrates how the use of callbacks centralizes control flow and error handling in the JdbcTemplate class. Note that this method throws our generic com.interface21.dao.DataAccessException, allowing callers to find the cause of any error without usingJDBC:

public void query(String sql, RowCallbackHandler callbackHandler)
throws DataAccessException {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
con = DataSourceUtils.getConnection(this.dataSource);
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
if (logger.isLoggable(Level.INFO))
logger.info("Executing static SQL query "' + sql +""');
while (rs.next()) {
callbackHandler.processRow(rs);
}
SQLWarning warning = ps.getWarnings();
rs.close();
ps.close();
throwExceptionOnWarningIfNotIgnoringWarnings(warning);
}
catch (SQLException ex) {
throw this.exceptionTranslater.translate("JdbcTemplate.query(sql)",
sql, ex);
}
finally {
DataSourceUtils.closeConnectionIfNecessary(this.dataSource, con);
}
} // query

The throwExceptionOnWarningIfNotIgnoringWarnings() method is a private helper used in several public workflow methods. If the warning it is passed is non-null and the JdbcTemplate is configured not to ignore exceptions, it throws an exception. If a warning was encountered, but the JdbcTemplate is configured to ignore warnings, it logs the warning:

private void throwExceptionOnWarningIfNotIgnoringWarnings(
SQLWarning warning) throws SQLWarningException {

if (warning != null) {
if (this.ignoreWarnings) {
logger.warning("SQLWarning ignored: " + warning);
} else {
throw new SQLWarningException("Warning not ignored", warning);
}
}
}

A more general query() method issues a PreparedStatement, which must be created by a PreparedStatementCreator implementation. I've highlighted the major difference from the query() method listed above:

public void query(PreparedStatementCreator psc,
RowCallbackHandler callbackHandler) throws DataAccessException {

Connection con = null;
Statement s = null;
ResultSet rs = null;
try {
con = DataSourceUtils.getConnection(this.dataSource);
PreparedStatement ps = psc.createPreparedStatement(con);
if (logger.isLoggable(Level.INFO) )
logger.info("Executing SQL query using PreparedStatement: ["
+ psc + "]");
rs = ps.executeQuery();

while (rs.next()){
if (logger.isLoggable(Level.FINEST) )
logger.finest("Processing row of ResultSet");
callbackHandler.processRow(rs);
}

SQLWarning warning = ps.getWarnings();
rs.close();
ps.close();
throwExceptionOnWarningIfNotIgnoringWarnings(warning);
}
catch (SQLException ex) {
throw this.exceptionTranslater.translate(
"JdbcTemplate.query(psc) with PreparedStatementCreator ["+
psc + "]", null, ex);
}
finally {
DataSourceUtils.closeConnectionIfNecessary(this.dataSource, con);
}
}

We apply the same approach to updates. The following method allows the execution of multiple updates using a single JDBC Connection. An array of PreparedStatementCreator objects supplies the SQL and bind parameters to use. This method returns an array containing the number of rows affected by each update:

public int[] update(PreparedStatementCreator[] pscs)
throws DataAccessException {

Connection con = null;
Statement s = null;
int index = 0;
try {
con = DataSourceUtils.getConnection(this.dataSource);
int[] retvals = new int[pscs.length];
for (index = 0; index < retvals.length; index++) {
PreparedStatement ps = pscs[index].createPreparedStatement(con);
retvals[index] = ps.executeUpdate();
if (logger.isLoggable(Level.INFO) )
logger.info("JDBCTemplate: update affected " + retvals[index] +
" rows");
ps.close();
}

return retvals;
}
catch (SQLException ex) {
throw this.exceptionTranslater.translate ("processing update " +
(index + 1) + " of " + pscs.length + "; update was [" +
pscs[index] + "]", null, ex);
}
finally {
DataSourceUtils.closeConnectionIfNecessary(this.dataSource, con);
}
}

The following convenience methods use the above method to execute a single update, given static SQL or a single PreparedStatementCreator parameter:

public int update(final String sql) throws DataAccessException {

if (logger.isLoggable(Level.INFO) )
logger.info("Running SQL update "' + sql + ""');
return update(PreparedStatementCreatorFactory.
getPreparedStatementCreator(sql) );
}

public int update(PreparedStatementCreator psc)
throws DataAccessException {
return update(new PreparedStatementCreator[] { psc })[0];
}
}

Note that the JdbcTemplate object maintains a javax.sql.DataSource instance variable, from which it obtains connections for each operation. It's important that the API should work with DataSource objects rather than java.sql.Connection objects, because otherwise:

注意:为什么用datasource而不是jdbc?

  • We'd have to obtain the connections elsewhere, meaning more complexity in application code and the need to catch SQLExceptions if using the DataSource.getConnection() method.

  • 用jdbc需要我们每次在不同的地方获得连接,意味着应用代码更复杂并需要捕获异常。

  • It's important that JdbcTemplate closes the connections it works with, as closing a connection can result in an exception, and we want our framework to take care of all JDBC exception handling. Obtaining the connections elsewhere and closing them in the JdbcTemplate class wouldn't make sense and would introduce the risk of attempts to use closed connections.

Using a DataSource doesn't limit the JdbcTemplate class to use within aJ2EE container: the DataSource interface is fairly simple, making it easy to implement for testing purposes or in standalone applications.

用datasource不限制jdbctemplate类去用j2ee容器:数据源接口非常的简单,从而更容易在测试或者独立的应用中执行。

抱歉!评论已关闭.