select语句剖析
建立了数据库连接之后,应用程序便可开始检索数据。在 SQL 数据库中,数据(通常)是使用 SELECT 语句检索的。SELECT 语句具有几个基本组成部分。例如:
SELECT product_id, product_name FROM products WHEREproduct_id < 1000 ORDER BY product_id
此语句可细分如下:
- SELECT product_id, product_name:select 子句,定义将返回一个表(或者一组表)中的哪些列。如需返回全部列,则可以使用 SELECT *。
- FROM products:from 子句,定义了包含要返回的数据的表。您可以从多个表中进行选择。这也称为 join,需要谨慎设计where 子句。
- WHERE product_id < 1000:where 子句,决定将返回哪些可用行。
- ORDER BY product_id:order by 子句 决定了数据的返回顺序。
SELECT 语句也可将多行组织在一起,并返回一个汇总值。例如,以下语句将返回在 2001 年 9 月15 日获得的收入超过 1000 美元的所有产品的总收入:
SELECT product_id, sum(quantity*price) as revenue FROMorders WHERE order_date = '9/15/01' GROUP BYproduct_id HAVING revenue > 1000。
SELECT 语句由 Statement 对象执行。
创建语句
创建 Statement 对象非常简单,只需使用 Connection's createStatement() 方法,确保捕获潜在的 SQLException 即可。这一部分中使用的大多数类都将引发 SQLException,因此其余代码将转入这个 try-catch 块。
... import java.sql.Statement;
public class Pricing extends Object { ... } catch (SQLException e) { System.out.println("Error creating connection: "+e.getMessage()); }
//Create the Statement object, used to execute the SQL statement Statement statement = null; try { statement = db.createStatement(); } catch (SQLException e) { System.out.println("SQL Error: "+e.getMessage()); } finally { System.out.println("Closing connections..."); try { db.close(); } catch (SQLException e) { System.out.println("Can't close connection."); } } } } |
这与创建 PreparedStatements 和 使用 CallableStatement 调用存储过程 类似。
执行语句
为了实际检索数据,必须执行 Statement。这通常涉及传递一个 SELECT 语句,这将创建作为 ResultSet 返回的一组数据,如下所示。
... import java.sql.ResultSet;
public class Pricing extends Object { ... //Create the Statement object, used to execute the SQL statement Statement statement = null; //Create the ResultSet object, which ultimately holds the data retrieved ResultSet resultset = null; try { statement = db.createStatement(); //Execute the query to populate the ResultSet resultset = statement.executeQuery("SELECT * FROM products"); } catch (SQLException e) { System.out.println("SQL Error: "+e.getMessage()); } finally { ... |
如果SQL 语句存在问题,比如引用了并不存在的表,那么应用程序会引发一个 SQLException。否则,无论是否找到了任何数据,应用程序都将继续执行。
测试数据
图 3. 数据集中的ResultSet 指针位置
创建 ResultSet 时,它拥有一个引用数据集内相对位置的 “指针”。在 ResultSet 语句返回之后(即便表是空的),此指针也将定位在第一行 “上方”。
为了到达实际数据的第一行,应用程序将调用 next() 方法。该方法会返回一个布尔值,指示新位置是否有一个行。如果未找到任何数据,next() 会返回 false。
... resultset = statement.executeQuery("select * from products");
//Check for data by moving the cursor to the first record (if there is one) if (resultset.next()) { System.out.println("Data exists."); } else { System.out.println("No data exists."); } } catch (SQLException e) { ... |
稍后在 循环遍历数据 中,将使用类似的技术。
数据类型
确定存在数据之后,便可使用 ResultSet 的 getXXX() 方法来检索数据。实际上并不存在称为 getXXX() 的方法,但有一组方法是以这种形式表示的,因为 ResultSet 可能会返回多种类型的数据。例如,如果数据单元是数据库中的整型数据,ResultSet 可能会将其作为数字返回给应用程序(通过 getDouble()、getInt() 和 getFloat() 等方法);也可能会将其作为 String 返回(通过getString());或者将其作为数组返回(通过 getArray())。较大的数据单元甚至可以作为 InputStream (通过getBinaryStream())或者 Reader(通过 getCharacterStream())返回。
需要牢记的一个要点就是 Java 支持大多数数据库中找到的数据类型,但仅有某些数据类型可作为与原有类型不同的类型检索。例如,getString() 可以检索原本作为任何数据库类型的数据,但日期和时间值仅能通过 getDate()(或者 getTime())、getObject()或 getString())检索。
按名称检索数据
您可以通过两种方式检索数据本身:按照名称和按照索引。为了按照名称检索,可以使用之前讨论的 getXXX() 方法之一进行检索。这些方法将获取 int 或 String 形式的参数。稍后将具体介绍 按照索引检索数据,就目前而言,应用程序将按照列名称检索字段。
最终,所有这些数据均将作为纯文本的形式包含在一个 XML 文件之中,因此将对所有值使用 getString():
... if (resultset.next()) { //Output data by referencing the ResultSet columns by name System.out.print(resultset.getString("product_id")+"|"); System.out.print(resultset.getString("product_name")+" | "); System.out.print(resultset.getString("base_price")+" | "); System.out.print(resultset.getString("size")+" | "); System.out.print(resultset.getString("unit")+" | "); System.out.print(resultset.getString("lower")+" | "); System.out.print(resultset.getString("upper")+" | "); System.out.println(resultset.getString("unit_price")); } else { System.out.println("No data exists."); } ... |
编译和运行应用程序将显示第一行数据,即一家在线美食零售商的批量定价信息。
图 4. 按名称检索数据,第一行数据
循环遍历数据
每次只显示一行是一种非常有用的做法,但更好的方法是循环遍历数据,并将每一条记录分别显示在独立的一行之中。
为此,应用程序将转至下一行并输出数据,随后再转到下一行。在指针经过最后一行之后,next() 将返回 false,循环将终止。
... resultset = statement.executeQuery("select * from products"); //Execute the loop as long as there is a next record while (resultset.next()) { //Output data by referencing the ResultSet columns by name System.out.print(resultset.getString("product_id")+" | "); System.out.print(resultset.getString("product_name")+" | "); System.out.print(resultset.getString("base_price")+" | "); System.out.print(resultset.getString("size")+" | "); System.out.print(resultset.getString("unit")+" | "); System.out.print(resultset.getString("lower")+" | "); System.out.print(resultset.getString("upper")+" | "); System.out.println(resultset.getString("unit_price")); } } catch (SQLException e) { ... |
运行此应用程序将返回表中的所有行。
图 5. 循环遍历数据将显示表中的所有行
按索引检索数据
当然,按照列名称检索数据可能非常不便,特别是在涉及到许多列的情况下。此外这种做法也会加大通用化和自定义检索的难度。为了通过能够更加轻松自定义的方式更迅速地检索数据,可以使用各列的索引编号。
第一列的索引为 1、第二列为 2,依此类推。我们知道上一节中的示例有 8 列,因此可将其改写如下:
... while (resultset.next()) { for (int i=1; i <= 8; i++) { //Output each column by its index System.out.print(resultset.getString(i)+" | "); } //Output a line feed at the end of the row System.out.println(""); } ... |
图 6. 按索引检索数据的示例
使用上述技术将使数据检索过程完全通用化,因而可迁移至其他类似的任务和/或应用程序。
通用检索
本教程的最终目标是创建一个基于完全独立于应用程序的映射文件的数据检索和操作过程。因此,应用程序需要能够在无需预先了解数据结构的前提下检索数据。换句话说,应用程序需要具备一种单独确定 ResultSet 中有多少个列的方法。
为了实现这个目标,我们将利用 ResultSetMetaData 类。与数据库的元数据类似,它能访问各列的编号和类型,以及列名称和其他信息。
ResultSetMetaData 可用于输出与某条记录有关的所有信息,即便是该记录拥有的惟一信息就是 ResultSet 函数本身时也是如此。
... import java.sql.ResultSetMetaData;
public class Pricing extends Object {
... Statement statement = null; ResultSet resultset = null; //Create the ResultSetMetaData object, which will hold information about //the ResultSet ResultSetMetaData resultmetadata = null; try { statement = db.createStatement(); resultset = statement.executeQuery("select * from products");
//Get the ResultSet information resultmetadata = resultset.getMetaData(); //Determine the number of columns in the ResultSet int numCols = resultmetadata.getColumnCount();
while (resultset.next()) { for (int i=1; i <= numCols; i++) { //For each column index, determine the column name String colName = resultmetadata.getColumnName(i); //Get the column value String colVal = resultset.getString(i); //Output the name and value System.out.println(colName+"="+colVal); } //Output a line feed at the end of the row System.out.println(" "); } ... |
现在,运行应用程序不仅能提供数据,还会提供列名称,在为数据创建 XML 文档时,这是必不可少的:
图 7. 通用数据检索的示例
ResultSet选项
至此,应用程序使用的一直是利用默认属性创建的 ResultSet,但是您可以调整这些属性,从而控制数据获取方向以及 ResultSet 是否会显示其他用户做出的更改等方面。
例如,在创建 ResultSet 时,可采用使之能够滚动或用于直接更新数据的方式。它将自顶向下地获取数据,并且不受其他数据库用户做出的更改的影响。
属性实际上是在最终创建 ResultSet 的 Statement 上设置的:
Statement statement = db.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.FETCH_FORWARD ); |
请注意,将多个自定义属性传递给 ResultSet 方法可能会影响应用程序的性能。
PreparedStatement
提高应用程序性能的一种方法就是使用 PreparedStatement。
PreparedStatement 类似于 Statement,惟一区别是前者是使用 SQL 语句作为参数创建的。在大多数情况下,此类 SQL 语句都将由数据库预先编译(或者至少进行缓存)。例如,如果应用程序的结构如下,那么 SELECT 语句应预先编译:
... import java.sql.PreparedStatement;
public class Pricing extends Object { ... //Create the PreparedStatement PreparedStatement statement = null; ResultSet resultset = null; ResultSetMetaData resultmetadata = null; try {
//Compile or cache the SQL within the database and prepare it for execution statement = db.prepareStatement("select * from products"); //Execute the SQL above to populate the ResultSet resultset = statement.executeQuery();
//Get the ResultSet Information resultmetadata = resultset.getMetaData(); ... |
PreparedStatement 或许在特定查询反复执行时最为有用。在此类情况下,在运行时设置参数的能力非常有用。
设置IN 参数
在重用 Statement 或 PreparedStatement 的大多数情况下,每次使用都存在细微的差异,例如记录范围有所不同。在这些情况下,您就需要使用 IN 参数。举例来说,为了检索一个要在运行时确定的 product_id 范围,可以使用:
... statement = db.prepareStatement("select * from products where "+ "product_id < ? and product_id > ?"); statement.setInt(1, 5); statement.setInt(2, 10);
resultset = statement.executeQuery(); ... |
setXXX() 方法与 getXXX() 方法匹配,不同之处在于它们包括参数编号和要为参数设置的值。例如,setInt(2, 10) 将语句中的第二个 ? 替换为整数 10,从而以如下形式执行语句:
select * from products where product_id < 5 and product_id > 10 |
在使用 CallableStatement 调用存储过程时,您可以使用类似的技巧。
使用CallableStatement 调用存储过程
大多数现代数据库都允许开发人员在数据库内创建存储过程。这种存储过程可以简单到仅有单独一条 SQL 语句,也可以像迷你应用程序一样复杂。无论是哪种情况,有些时候很有必要从 Java 调用这些过程来生成一组待检索的数据。
CallableStatement 类扩展了 PreparedStatement,以便允许开发人员为数据库查询指定参数。随后 CallableStatement 将返回ResultSet(或者 ResultSet)。
创建 CallableStatement 与创建 PreparedStatement 极为相似,但使用了调用语句,而非 SQL 语句。随后驱动程序会将调用语句转为原生调用。
statement = db.prepareCall("{call product_listing }"); |
注意: CallableStatement 与 PreparedStatement 之间的差别之一就是 CallableStatement 除了通常创建的 ResultSet 之外,CallableStatement 还会提供OUT 参数。
检测空值
本教程中的数据涉及商品的批量定价。因此,某些数量范围的上限为空,表示无更高的批量折扣可用。
这种做法虽然可行,但会导致构建最终 XML 文档的难度加大,因为空值可能会造成问题。为了解决这个问题,您可以使用 wasNull()方法来确定特定数据段是否为空,以及是否使用其他内容取代它。这个示例展示了使用 “and up” 替换空值的情况。
... while (resultset.next()) { //Output each row for (int i=1; i <= numCols; i++) { //For each column, get name and value information String colName = resultmetadata.getColumnName(i); String colVal = resultset.getString(i); //Determine if the last column accessed was null if (resultset.wasNull()) { colVal = "and up"; } //Output the information System.out.println(colName+"="+colVal); } System.out.println(" "); } ... |
请注意,wasNull() 无参数。该方法处理从 ResultSet 中检索到的最后一列,因此必须先调用 getXXX()。
图 8. 检测空值的示例
此时,应用程序检索了恰当的数据和列名称。您已经为构建 XML 文档做好了准备。
:blac\�gcgЅp\�te;mso-font-kerning:0pt'>中检索到的最后一列,因此必须先调用 getXXX() 。
图 8. 检测空值的示例
此时,应用程序检索了恰当的数据和列名称。您已经为构建 XML 文档做好了准备。
本文来自:http://www.ibm.com/developerworks/cn/xml/tutorials/x-extract/section3.html