核心技术Ⅱ:数据库编程

来自Wikioe
跳到导航 跳到搜索


关于JDBC

Java数据库连接(JDBC)API:

遗循了微软公司非常成功的ODBC模式(ODBC:微软提供为C语言访问数据库的一套编程接口)。JDBC和ODBC都基千同一个思想:根据API编写的程序都可以与驱动管理器进行通信,而驱动管理器则通过驱动程序与实际的数据库进行通信。


JDBC的目标:

  1. 通过使用标准的SQL语句,甚至是专门的SQL扩展,程序员就可以利用Java语言开发访问数据库的应用,同时还依旧遵守Java语言的相关约定。
  2. 数据库供应商和数据库工具开发商可以提供底层的驱动程序。因此,他们可以优化各自数据库产品的驱动程序。


JDBC 驱动程序类型:

  1. 笫1类驱动程序:将JDBC翻译成ODBC, 然后使用一个ODBC驱动程序与数据库进行通信。
    较早版本的Java包含一个这样的驱动程序:JDBC/ODBC桥,不过在使用这个桥接器之前需要对ODBC进行相应的部署和正确的设置。(Java 8已经不再提供JDBC/ODBC桥)
  2. 笫2类驱动程序:由部分Java程序和部分本地代码组成的,用于与数据库的客户端API进行通信。
    在使用这种驱动程序之前,客户端不仅斋要安装Java类库,还需要安装一些与平台相关的代码。
  3. 第3类驱动程序:纯Java客户端类库,它使用一种与具体数据库无关的协议将数据库请求发送给服务器构件,然后该构件再将数据库请求翻译成数据库相关的协议。这简化了部署,因为平台相关的代码只位于服务器端。
  4. 第4类驱动程序:纯Java类库,它将JDBC请求直接翻译成数据库相关的协议。


JDBC 的典型用法:

  1. 传统的客户端/服务器模型:通常是在服务器端部署数据库,而在客户端安装富GUI程序。(在此模型中,JDBC驱动程序应该部署在客户端)
    JDBC:传统的客户端-服务器应用.png
  2. 三层模型:客户端不直接调用数据库,而是调用服务器上的中间件层,由中间件层完成数据库查询操作。
    JDBC:三层结构的应用.png


结构化查询语言:SQL【SQL,读作“ [ˈsiːkwəl] ”】

SQL数据类型.png

JDBC配置

  1. 驱动程序JAR文件):
    需要准备对应的数据库驱动包:在运行访问数据库的程序时,需要将驱动程序的 JAR 文件包括到类路径中(编译时并不需要这个 JAR 文件)。
    // 在从命令行启动程序时,只需要使用下面的命令:
    java -classpath driverPath:. ProgramName
    
  2. 注册驱动器类
    1. 自动注册:许多JDBC的JAR文件会自动注册驱动器类,则可以跳过手动注册步骤。
      • (包含“META-INF/services/java.sql.Driver”文件的 JAR 文件可以自动注册驱动器类)
      • 自动注册对于遵循 JDBC4 的驱动程序是必须具备的特性。
    2. 手动注册:通过“DriverManager”来注册驱动器。
      1. 在 Java 程序中加载驱动器类:(注册驱动器的静态初始化器)
        Class.forName("org.postgresql.Driver"); // force loading of driver class
        
      2. 设置“jdbc.drivers”属性:
        • 命令行参数指定这个属性:
          java -Djdbc.drivers=org.postgresql.Driver ProgramName
          
        • 或,应用中通过调用设置系统属性:
          System.setProperty("jdbc.drivers", "org.postgresql.Driver");
          
        • 在这种方式中可以提供多个驱动器,用冒号将它们分隔开:
          org.postgresq1.Driver:org.apache.derby.jdbc.ClientDriver
          
  3. 连接到数据库
    1. 在代码中打开数据库连接:
      String url = "jdbc:postgresql :COREJAVA"; 
      String username = "dbuser"; 
      String password = "secret"; 
      Connection conn = DriverManager.getConnection(url, username, password);
      
      驱动管理器遍历所有注册过的驱动程序,以便找到一个能够使用数据库 URL 中指定的子协议的驱动程序。
    2. 使用数据库配置文件(如“database.properties”)
      jdbc.drivers=org.postgresql.Oriver
      jdbc.url=jdbc:postgresql:COREJAVA
      jdbc.username=dbuser
      jdbc.password=secret
      


  • 数据库驱动器名称:如:
    org.apache.derby.jdbc.ClientDriver
    org.postgresql.Driver
    com.mysql.jbdc.driver
    
  • 数据库URL:JDBCURL 的一般语法为:“jdbc:subprotocol:other_stuff”,其中subprotocol用于选择连接到数据库的具体驱动程序。
    URL包含各种与数据库类型相关的参数,例如“主机名”、“端口号”和“数据库名”。如:
    jdbc:derby://1ocalhost:1527/COREJAVA;create=true
    jdbc:postgresq1:COREJAVA
    


示例:

	try{
		Class.forName("com.mysql.jbdc.driver");
		Connection conn = DriverManager.getConnection("jdbc:mysql:///spring_day01","root","admin");

		String sql = "select * from users where username = ?";
		PrepareStatement pre = conn.prepareStatement(sql);
		pre.setString(1,"小陈");
    
		ResultSet rs = pre.executeQuery();
		while(rs.next()){
			String username = rs.getString("username");
			String password = rs.getString("password");
	
			User user = new User();
			user.setUsername(username);
			user.setPassword(password);

			System.out.println(user);
		}
	}catch(Exception ex){
		ex.printStackTrace();
	}finally{
		rs.close();
		pre.close();
		conn.close();
	}

使用JDBC语句

执行SQL语句

执行语句:

  1. executeUpdate”:返回受SQL语句影响的行数,或者对不返同行数的语句返回0。
    • 既可以执行诸如“INSERT”、“UPDATE”和“DELETE”之类的操作,也可以执行诸如“CREATETABLE”和“DROPTABLE”之类的数据定义语句。
  2. executeQuery”:返回一个“ResultSet”类型的对象,可以通过它来每次一行地迭代遍历所有查询结果。
    • 执行“SELECT”查询时必须使用executeQuery方法。
  3. execute”:可能会产生多个结果集和更新计数。
    如果第一个执行结果是结果集,则返回 true;反之,返回 false。调用“getResultSet”或“getUpdateCount”方法可以得到第一个执行结果。
    • 可以执行任意的SQL语句。(通常只用于由用户提供的交互式查询)


结果处理:分析结果集:

while (rs.next()) 
{
   String username = rs.getString("username");
   String password = rs.getString("password");
   int age = rs.getInt("age");
	
   User user = new User();
   user.setUsername(username);
   user.setPassword(password);
   user.setAge(age);

   System.out.println(user);
}
  • ResultSet接口的迭代协议与“java.util.Iterator”接口稍有不同:
    对于ResultSet接口,迭代器初始化时被设定在第一行之前的位置,必须调用“next”方法将它移动到第一行。另外,它没有“hasNext”方法,我们需要不断地调用“next”,直至该方法返回 false 。
  1. 不同的数据类型有不同的访问器,比如“getString”和“getDouble”。
    当 get 方法的类型和列的数据类型不一致时,每个 get 方法都会进行合理的类型转换。例如,调用“rs.getString("Price")”时,该方法会将 Price 列的浮点值转换成字符串。
  2. 每个访问器都有两种形式:
    1. 一种接受数字型参数。(列序号从1开始)如,“rs.getString(l)”返回的是当前行中第一列的值;
    2. 另一种接受字符串参数。如上例;


相关方法:

java.sql.DriverManager 1.1
static Connection getConnection(String url, String user, String password) 建立一个到指定数据库的连接,并返回一个 Connection 对象。
java.sql.Connection 1.1
Statement createStatement() 创建一个 Statement 对象,用以执行不带参数的 SQL 查询和更新。
void close() 立即关闭当前的连接,并释放由它所创建的JDBC资源。
java.sql.Statement 1.1
ResultSet executeQuery(String sqlQuery) 执行给定字符串中的 SQL 语句,并返回一个用于查看查询结果的 ResultSet 对象。
int executeUpdate(String sqlStatement) 执行字符串中指定的“INSERT”、“UPDATE”或“DELETE”等 SQL 语句。还可以执行数据定义语言 (Data Definition Language, DDL) 的语句,如“CREATE TABLE”。返回受影响的行数,如果是没有更新计数的语句,则返回 0。
long executeLargeUpdate(String sqlStatement) 8
boolean execute(String sql Statement) 执行字符串中指定的 SQL 语句。可能会产生多个结果集和更新计数。如果第一个执行结果是结果集,则返回 true;反之,返回 false。调用“getResultSet”或“getUpdateCount”方法可以得到第一个执行结果。【见“多结果集”一节】
ResultSet getResultSet() 返回前一条查询语句的结果集。如果前一条语句未产生结果媒,则返回 null 值。对于每一条执行过的语句,该方法只能被调用一次。
int getUpdateCount() 返回受前一条更新语句影响的行数。如果前一条语句未更新数据库,则返回-1。对于每一条执行过的语句,该方法只能被调用一次。
1ong getLargeUpdateCount() 8
void close() 关闭 Statement 对象以及它所对应的结果集。
boolean isClosed() 6 如果语句被关闭,则返回true。
void closeOnCompletion() 7 使得一旦该语句的所有结果集都被关闭,则关闭该语句。
java.sql.ResultSet 1.1
boolean next() 将结果集中的当前行向前移动一行。如果已经到达最后一行的后面,则返同false。注意,初始请况下必须调用该方法才能转到第一行
Xxx getXxx(int columnNumber) 用给定的列序号或列标签返回或更新该列的值,并将值转换成指定的类型。列标签是SQL的AS子句中指定的标签,在没有使用AS时,它就是列名。
Xxx getXxx(String columnLabel)

(Xxx指数据类型, 例如“int”、“double”、“String”和“Date”等)

<T> T getObject(int columnIndex, Class<T> type) 7
<T> T getObject(String columnLabel, Class<T> type) 7
void updateObject(int columnIndex, Object x, SQLType targetSqlType) 8
void updateObject(String columnLabel, Object x, SQLType targetSqlType) 8
int findColumn(String columnName) 根据给定的列名,返回该列的序号。
void close() 立即关闭当前的结果集。
boolean isClosed() 6 如果该语句被关闭,则返回true。

Connection、Statement、ResultSet

  1. 每个“Connection”对象都可以创建一个或多个“Statement”对象。(1:N)
    调用“Connection”类的“close”方法将关闭该连接上的所有语句。
  2. 同一个“Statement”对象可以用于多个不相关的命令和查询。 (1:N)
  3. 一个“Statement”对象最多只能有一个打开的结果集。(如果需要执行多个查询操作,且需要同时分析查询结果,那么必须创建多个 Statement对象)
    如果“Statement”对象上有一个打开的结果集,那么调用“close”方法将自动关闭该结果。
    • 在JavaSE 7,可以在“Statement”上调用“closeOnCompletion”方法,在其所有结果集都被关闭后,该语句会立即被自动关闭。


  • 至少有一种常用的数据库(Microsoft SQL Server)的JDBC驱动程序只允许同时存在一个活动的Statement对象。
    使用“DatabaseMetaData”接口中的“getMaxStatements”方法可以获取JDBC驱动程序支持的同时活动的语句对象的总数。
  • 使用完“ResultSet”、“Statement”或“Connection”对象后,应立即调用“close”方法。
    如果所用连接都是短时的,那么无需考虑关闭语句和结果集。只需将“close”语句放在带资源的try语句中,以便确保最终连接对象不可能继续保持打开状态:
    try (Connection conn = . . .) 
    {
        Statement stat = conn.createStatement();
        ResultSet result = stat.executeQuery(queryString);
        // process query result 
    }
    

分析SQL异常

SQL 异常按照层次结构树的方式组织到了一起:

SQL异常类型.png
  1. SQLException:异常
    每个“SQLException”都有一个由多个“SQLException”对象构成的链,这些对象可以通过“getNextException”方法获取。
    这个异常链是每个异常都具有的由“Throwable”对象构成的“成因”链之外的异常链,因此,需要用两个嵌套的循环来完整枚举所有的异常。JavaSE6后“SQLException”类实现了“Iterable<Throwable>”接口,其“iterator()”方法可以产生一个“Iterator<Throwable>”,这个迭代器可以迭代这两个链,首先迭代第一个“SQLException”的成因链,然后迭代下一个“SQLException”,以此类推。
    1. 使用下面这个改进的 for 循环:
      for (Throwable t : sqlException) 
      {
          // do something with t 
      }
      
    2. 调用“getSQLState”和“getErrorCode”方法来进一步分析它:
      “getSQLState”将产生符合 X/Open 或 SQL:2003 标准的字符串(调用“DatabaseMetaData”接口的“getSQLStateType”方法可以查出驱动程序所使用的标准);
      错误代码是与具体的提 供商相关的;
  2. SQLWarning:警告
    数据库驱动程序可以将非致命问题作为警告报告,可以从“Connection”、“Statement”和“ResultSet”中获取这些警告。与SQL异常类似,警告也是串成链的。
    • “SQLWarning”类是“SQLException”的子类。(但“SQLWarning”不会被当作异常抛出)
    1. 循环获取 SQLWarning:
      SQLWarning w = stat.getWarning();
      while (w != null) 
      {
          // do something with w 
          w = w.nextWarning();
      }
      
    2. 调用“getSQLState”和“getErrorCode”来获取有关警告的更多信息;
    • 当数据从数据库中读出并意外被截断时,SQLWarning的“DataTruncation”子类就派上用场了。如果数据截断发生在更新语句中,那么“DataTruncation”将会被当作异常抛出。【???】


相关方法:

java.sql.SQLException 1.1
SOLException getNextException() 返同链接到该SQL异常的下一个SQL异常,或者在到达链尾时返回null。
Iterator<Throwabl e> iterator() 6 获取迭代器,可以迭代链接的SQL异常和它们的成因。
String getSQLState() 获取“SQL状态”,即标准化的错误代码。
int getErrorCode() 获取提供商相关的错误代码。
java.sql.SQLWarning 1.1
SQLWarning getNextWarning() 返回链接到该警告的下一个警告,或者在到达链尾时返回 null。
java.sql.Connection 1.1
java.sql.Statement 1.1
java.sql.ResultSet 1.1
SQLWarning getWarnings() 返回未处理警告中的第一个,或者在没有未处理警告时返回 null。
java.sql.DataTruncation 1.1
boolean getParameter() 如果在参数上进行了数据截断,则返回 true;如果在列上进行了数据截断,则返回 false。
int getIndex() 返回被截断的参数或列的索引。
int getDataSize() 返回应该被传输的字节数量,或者在该值未知的情况下返回 -1。
int getTransferSize() 返回实际被传输的字节数量,或者在该值未知的情况下返回 -1。

执行查询操作

预备语句(PreparedStatement)

预备语句(prepared statement):准备一个带有宿主变量的查询语句,每次查询时只需为该变量填入不同的字符串就可以反复多次使用该语句。

  • PreparedStatement技术改进了查询性能:每当数据库执行一个查询时,它总是首先通过计算来确定查询策略,以便高效地执行查询操作;通过事先准备好查询并多次重用它,我们就可以确保查询所需的准备步骤只被执行一次
  1. 在预备查询语句中,每个宿主变批都用“?”来表示;
    String publisherQuery = 
       "SELECT Books.Price, Books.Title" + 
       " FROM Books, Publishers" + 
       " WHERE Books.Publisher _Id = Publishers. Publisher _Id AND Publishers. Name = ?";
    PreparedStatement stat = conn.prepareStatement(publisherQuery);
    
  2. 在执行预备语句之前,必须使用“set”方法将变量绑定到实际的值上;
    (和 ResultSet 接口中的 get 方法类似,针对不同的数据类型也有不同的 set 方法)
    • 想要重用已经执行过的预备查询语句,那么除非使用“set”方法或调用“clearParameters”方法,否则所有宿主变量的绑定都不会改变。
    stat.setString(l, publisher);    // 位置l表示第一个“?”
    
  3. 执行查询操作:
    ResultSet rs = stat.executeQuery();
    
    • 更新操作可以由“UPDATE”语句实现(不返回结果集),则应调用“executeUpdate”方法(返回值为被修改过的行数),而非“executeQuery”方法(返回查询的结果集);


   通过拼接字符串来手动构建查询非常枯燥乏味,而且存在潜在的危险。
   
   必须注意像引号这样的特殊宇符,而且如果查询中涉及用户的输入,那就还需要警惕注入攻击。
   
   因此,只有查询涉及变量时,才应该使用预备语句。

【P.S.:Java字符串拼接

   在相关的Connection对象关闭之后,PreparedStatement对象也就变得无效了。

   不过,许多数据库通常都会自动缓存预备语句。如果相同的查询被预备两次,数据库通常会直接重用查询策略。因此,无需过多考虑调用prepareStatement的开销。


相关方法:

java.sql.Connection 1.1
PreparedStatement prepareStatement(String sql) 返回一个含预编译语句的PreparedStatement对象。字符串sql代表一个SQL语句,该语句可以包含一个或多个由?字符指明的参数占位符。
java.sql.PreparedStatement 1.1
void setXxx(int n, Xxx x) (Xxx指int 、 double 、 String、 Date之类的数据类型)设置第n个参数值为x。
void clearParameters() 清除预备语旬中的所有当前参数。
ResultSet executeQuery() 执行预备SQL查询,并返回一个ResultSet对象。
int executeUpdate() 执行预备SQL语句“INSERT”、“UPDATE”或“DELETE”, 这些语句由 PreparedStatement 对象表示。该方法返回在执行上述语句过程中所有受影响的记录总数。如果执行的是数据定义语言(DDL)中的语句,如“CREATE TABLE”,则该方法返回0。

读写大对象(LOB)

除了数字、字符串和日期之外,许多数据库还可以存储大对象,例如图片或其他数据。在SQL中,二进制大对象称为BLOB字符型大对象称为CLOB

  1. 读取LOB:
    读取LOB, 需要执行 SELECT语句。
    1. 在“ResultSet”上调用“getBlob”或“getClob”方法,这样就可以获得Blob或Clob类型的对象;
    2. 从Blob、Clob中获取数据:
      1. Blob中获取二进制数据,可以调用“getBytes”或“getBinaryStream”:
        // 获取一张图像
        . . .
        stat.set(1, isbn);
        try (ResultSet resu1t = stat.executeQuery()) 
        {
           if (result.next()) 
           {
              Blob coverBlob = result.getBlob(l); 
              Image coverimage = ImageIO.read(coverBlob.getBinaryStream()); 
           }
        }
        
      2. Clob获取字符数据,可以调用“getSubString”或“getCharacterStream”方法来获取其中的;
  2. 写入LOB:
    需要在 Connection 对象上调用“createBlob”或“createClob”,然后获取一个用于该 LOB 的输出流或写出器,写出数据,并将该对象存储到数据库中。
    // 存储一张图像
    Blob coverBlob = connection.createBlob(); 
    
    int offset = O; 
    OutputStream out = coverBlob.setBinaryStream(offset); 
    ImageIO.write(coverImage, "PNG", out); 
    
    PreparedStatement stat = conn.prepareStatement("INSERT INTO Cover VALUES (?, ?) "); 
    stat.set(l, isbn); 
    stat.set(2, coverBlob); 
    stat.executeUpdate();
    


相关方法:

java.sql.ResultSet 1.1
Blob getBl ob(int col umnlndex) 1. 2 获取给定列的 BLOB 或 CLOB。
Blob getBlob(String columnlabel) 1.2
Clob getClob(int columnlndex) 1.2
Clob getClob(String columnlabel) 1.2
java.sql.Blob 1.2
1ong length() 获取该 BLOB 的长度。
byte[] getBytes(long startPosition, long length) 获取该 BLOB 中给定范围的数据。
InputStream getBinaryStream() 返回一个输入流,用于读取该 BLOB 中全部或给定范围的数据。
InputStream getBinaryStream(long startPosition, long length)
OutputStream setBinaryStream(1ong startPosition) 1. 4 返回一个输出流,用于从给定位览开始写入该 BLOB。
java.sql.Clob 1.4
1ong 1ength() 获取该 CLOB 中的字符总数。
String getSubString(1ong startPosition, 1ong 1ength) 获取该CLOB中给定范围的字符。
Reader getCharacterStream() 返同一个读入器(而不是流),用于读取CLOB中全部或给定范围的数据。
Reader getCharacterStream(1ong startPosition, 1ong 1ength)
Writer setCharacterStream(1ong startPosition) 1. 4 返回一个写出器(而不是流),用于从给定位值开始写入该 CLOB。
java.sql.Connection 1.1
Blob createBlob() 6 创建一个空的 BLOB 或 CLOB。
Clob createClob() 6

SQL 转义

“转义”语法是各种数据库普遍支持的特性,但是数据库使用的是与数据库相关的语法变体。因此,将转义语法转译为特定数据库的语法是JDBC驱动程序的任务之一。转义主要用于下列场景:

  1. 日期和时间字面常量
    (日期和时间字面常量随数据库的不同而变化很大)
    使用“d”、“t”、“ts”来表示“DATE”、“TIME”和“TIMESTAMP”值:
    {d '2008-01-24'} 
    {t '23:59:59'} 
    {ts '2008-01-24 23:59:59.999'}
    
  2. 调用标量函数
    标量函数(scalar function)是指仅返回单个值的函数。在数据库中包含大量的函数,但是不同的数据库中这些函数名存在着差异。JDBC规范提供了标准的名字,并将其转译为数据库相关的名字。
    要调用函数,需要像下面这样嵌入标准的函数名和参数:
    {fn left(?, 20)}
    {fn user()}
    
  3. 调用存储过程
    存储过程(stored procedure)是在数据库中执行的用数据库相关的语言编写的过程
    要调用存储过程,需要使用“call”转义命令,在存储过程没有任何参数时,可以不加括号。另外,应该用“=”来捕获存储过程的返回值:
    {call PROCl(?, ?)} 
    {call PROC2} 
    {call ? = PROC3(?)}
    
  4. 外连接
    两个表的外连接(outer join)并不要求每个表的所有行都要根据连接条件进行匹配。
    SELECT * FROM {oj Books LEFT OUTER JOIN Pub1ishers ON Books.Pub1isher_Id = Pub1isher.Pub1isher_Id} 
    -- 这个查询的执行结果中将包含有 Publisher_Id 在 Publishers 表中没有任何匹配的书,其中:
    -- 1、Publisher_ID 为“NULL”值的行,就表示不存在任何匹配;
    -- 2、如果应该使用“RIGHT OUTER JOIN”,就可以囊括没有任何匹配图书的出版商;
    -- 3、而使用“FULL OUTER JOIN”可以同时返回这两类没有任何匹配的信息。
    -- 由于并非所有的数据库对于这些连接都使用标准的写法,因此需要使用转义语法。
    
  5. 在 LIKE 子句中的转义字符
    _”和“%”字符在“LIKE”子句中具有特殊含义,用来匹配一个字符或一个字符序列。
    如果想要匹配所有包含“_”字符的字符串,就必须使用下面的结构:
    . . . WHERE ? LIKE %!_% {escape '!'} 
    -- 这里我们将!定义为转义字符!_组合表示字面常量下划线
    

多结果集(execute、getMoreResults)

在执行存储过程,或者在使用允许在单个查询中提交多个“SELECT”语句的数据库时,一个查询有可能会返回多个结果集。获取所有结果集的步骤:

  1. 使用“execute”方法来执行 SQL 语句;
  2. 获取第一个结果集或更新计数;
  3. 重复调用“getMoreResults”方法以移动到下一个结果集;
  4. 当不存在更多的结果集或更新计数时,完成操作。
  • 由多结果集构成的链中的下一项:【???】
    如果是结果集:“execute”和“getMoreResults”方法将返回true;“getUpdateCount”方法将返回-1
    如果是更新计数:“execute”和“getMoreResults”方法将返回false;“getUpdateCount”方法将返回计数值
    如果“getMoreResults”为false,且“updateCount”为-1,表示完成;


遍历所有的结果:(更新计数、结果集)

boo1ean isResult = stat.execute(command); 
boolean done = false; 
while (!done) 
{
   if (isResult) 
   {
      ResultSet resu1t = stat.getResu1tSet(); 
      // do something with result 
   }
   else
   {
      int updateCount = stat.getUpdateCount(); 
      if (updateCount >= 0) 
         // do something with updateCount 
      else 
         dnoe = true;   // “getMoreResults”为false,且“updateCount”为-1,表示完成;
   }
   if (!done) isResult = stat.getMoreResults();
}


相关方法:

java.sql.Statement 1.1
boolean getMoreResults() 获取该语句的下一个结果集,Current 参数是“CLOSE_CURRENT_RESULT”(默认值),“KEEP_CURRENT_RESULT”或“CLOSE_ALL_RESULTS”之一。如果存在下一个结果集,并且它确实是一个结果集,则返回 true。
boolean getMoreResults(int current) 6

获取自动生成的键

不同的提供商所提供的“对行自动编号”机制之间存在着很大的差异(mysql自增;Oracle中Sequence),这些自动编号的值经常用作主键。JDBC 提供了获取自动生成键的有效途径:当向数据表中插入一个新行,且其键自动生成时,代码来获取这个键:

stat.executeUpdate(insertStatement, Statement.RETURN_CENERATED_KEYS); 
ResultSet rs = stat.getGeneratedKeys(); 
if (rs.next()) 
{
    int key = rs.getInt(l); 
    ...
}

相关方法:

java.sql.Statement 1.1
boolean execute(String statement, int autogenerated) 1.4 像前面描述的那样执行给定的 SQL 语句,如果“autogenerated”被设置为“Statement.RETURN_GENERATEO_KEYS”,并且该语句是一条“INSERT”语句,那么第一列中就是自动生成的键。
int executeUpdate(String statement, int autogenerated) 1.4

可滚动和可更新的结果集

对于可滚动结果集而言,我们可以在其中向前或向后移动,甚至可以跳到任意位置。

在可更新的结果集中,可以以编程方式来更新其中的项,使得数据库可以自动更新数据。
  • 默认情况下,结果集是不可滚动和不可更新的。
  • 可更新的结果集并非必须是可滚动的,但如果将数据提供给用户去编辑,那么通常也会希望结果集是可滚动的。
ResultSet 类的 typeconcurrency
解释
type
TYPE_FORWARD_ONLY 结果集不能滚动(默认值)
TYPE_SCROLL_INSENSITIVE 结果集可以滚动,但对数据库变化不敏感
TYPE_SCROLL_SENSITIVE 结果集可以滚动,且对数据库变化敏感
concurrency
CONCUR_ READ_ ONLY 结果集不能用于更新数据库(默认值)
CONCUR_UPDATABLE 结果集可以用于更新数据库

可滚动的结果集

为了从查询中获取可滚动的结果集,必须使用下面的方法得到一个不同的 Statement 对象:

Statement stat = conn.createStatement(type, concurrency);

// 要获得预备语句,则调用:
PreparedStatement stat = conn.prepareStatement(command, type, concurrency);
并非所有的数据库驱动程序都支持可滚动和可更新的结果集。(使用 DatabaseMetaData 接口中的 supportsResultSetTypesupportsResultSetConcurrency 方法,我们可以获知在使用特定的驱动程序时,某个数据库究竟支持哪些结采集类型以及哪些并发模式。)

即使是数据库支持所有的结果集模式,某个特定的查询也可能无法产生带有所请求的所有属性的结果集。(例如,一个复杂查询的结果集就有可能是不可更新的结果集。)
    在这种情况下,executeQuery 方法将返回一个功能较少的 ResultSet 对象,并添加一个 SQLWarning 到连接对象中。

或者,也可以使用 ResultSet 接口中的 getTypegetConcurrency 方法查看结果集实际支持的模式。
    如果不检查结果集的功能就发起一个不支持的操作,比如对不可滚动的结果集调用 previous 方法,那么程序将抛出一个 SQLException 异常。


示例:

如果只想滚动遍历结果集,而不想编辑它的数据,那么可以使用以下语句:
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_ READ_ ONLY);

ResultSet rs = stat.executeQuery(query);


可滚动的结果集有一个游标,用以指示当前位置。

滚动结果集:

  1. 将游标向后滚动
    if(re.previous()) 
    . . .
    
    • 如果游标位于一个实际的行上,那么该方法将返回 true;如果游标位于第一行之前,那么返回 false。
  2. 将游标向后或向前移动多行
    rs.relative(n);
    
    • 如果 n 为正数,游标将向前移动;如果 n 为负数,游标将向后移动;如果 n 为 0,那么调用该方法将不起任何作用。
    • 如果试图将游标移动到当前行集的范围之外(即:根据 n 值的正负号,游标将被设置在最后一行之后或第一行之前),那么,该方法将返回 false,且不移动游标。如果游标位于一个实际的行上,那么该方法将返回 true。
  3. 将游标设置到指定行号
    rs.absolute(n) ;
    
  4. 返回当前行的行号
    int currentRow = rs.getRow();
    
    • 结果集中第一行的行号为 1。如果返回值为 0,那么当前游标不在任何行上(要么位于第一行之前,要么位于最后一行之后)。
  5. firstlastbeforeFirstafterLast 这些简便方法用于将游标移动到第一行、最后一行、第一行之前或最后一行之后。
    • isFirstislastisBeforeFirstisAfterLast 用于测试游标是否位于这些特殊位置上。


使用可滚动的结果集是非常简单的,将查询数据放入缓存中的复杂工作是由数据库驱动程序在后台完成的。

可更新的结果集

为了获得可更新的结果集,应该使用以下方法创建一条语句:

Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
并非所有的查询都会返回可更新的结果集:
    1、如果查询涉及多个表的连接操作,那么它所产生的结果集将是不可更新的;
    2、如果查询只涉及一个表,或者在查询时是使用主键连接多个表的,那么它所产生的结果集将是可更新的结果集。
    
可以调用 ResultSet 接口中的 getConcurrency 方法来确定结果集是否是可更新的。


示例:

假设想提高某些图书的价格,但是在执行 UPDATE 语句时又没有一个简单而统一的提价标准。此时,就可以根据任意设定的条件,迭代遍历所有的图书并更新它们的价格:
String query  "SELECT * FROM Books";
ResultSet rs = stat.executeQuery(query);
while(rs.next())
{
    if(. . .)
    {
        double increase = . . .
        double price  rs.getDoub1e("Price");
        rs.updateDouble("Price", price + increase);
        rs.updateRow(); // make sure to call updateRow after updating fields
    }
}


所有对应于 SQL 类型的数据类型都配有 updateXxx 方法,比如“updateDouble”、“updateString”等。
与 getXxx 方法相同,在使用 updateXxx 方法时必须指定列的名称序号。然后,你可以给该字段设置新的值。

    注意这里的列序号指的是该列在结果集中的序号,而非数据库中的列序号。

更新结果集:

  1. 更新:
    1. updateXxx 方法:改变的只是结果集中的行值,而非数据库中的值;
    2. updateRow 方法:将当前行中的所有更新信息发送给数据库;
      • 如果没有调用 updateRow 方法就将游标移动到其他行上,那么对此行所做的所有更新都将被丢弃。
    3. cancelRowUpdates 方法:取消对当前行的更新;
  2. 插入:
    1. moveToinsertRow 方法:将游标移动到特定的位置,我们称之为“插入行(insert row)”;
    2. 然后,调用 updateXxx 方法在插入行的位置上创建一个新的行;
    3. 再,调用 insertRow 方法将新建的行发送给数据库;
    4. 最后,调用 moveToCurrentRow 方法将游标移回到调用 moveTolnsertRow 方法之前的位置;
    示例:
    rs.moveToInsertRow();
    rs.updateString("Title", title);
    rs.updateString("ISBN", isbn);
    rs.updateString("Publisher_Id", pubid);
    rs.updateDouble("Price", price);
    rs.insertRow();
    rs.moveToCurrentRow();
    
    • 注意,你无法控制在结果集或数据库中添加新数据的位置。
    • 对于在插入行中没有指定值的列,将被设置为 SQL 的NULL 。但是,如果这个列有 “NOT NULL” 约束,那么将会抛出异常,而这一行也无法插入。
  3. 删除:
    // 删除游标所指的行(该方法会立即将该行从结果集和数据库中删除)
    rs.deleteRow();
    

ResultSet 接口中的 updateRow 、insertRow 和 deleteRow 方法的执行效果等同于 SQL 命令中的 UPDATE 、INSERT 和 DELETE。


JDBC 2 对结果集做了进一步的改进,例如,如果数据被其他的并发数据库连接所修改,那么它可以用最新的数据来更新结果集。JDBC 3 添加了另一种优化,可以指定结果集在事务提交时的行为。


相关方法:

java.sql.Connection 1.1
Statement createStatement(int type, int concurrency) 1. 2 创建一个语句或预备语句,且该语句可以产生指定类型和并发模式的结果集。

参数:

  1. command:要预备的命令
  2. type:ResultSet 接口中的下述常量之一: TYPE_FORWARD_ONLY、TYPL_SCROLL_INSENSITIVE 或者 TYPE_SCROLL_SENSITIVE
  3. concurrency:ResultSet 接口中的下述常量之一: CONCUR_ READ_ONLY 或者 CONCUR_UPDATABLE
PreparedStatement prepareStatement(String command, int type, intconcurrency) 1.2
java.sql.ResultSet 1.1
int getType() 1. 2 返回结果集的类型。
  • 返回值为以下常量之一: TYPE_FORWARD_ONLY、TYPE_ SCROLL_INSENSITIVE 或 TYPE_SCROLL_SENSITIVE
int getConcurrency( ) 1 . 2 返回结果集的并发设置。
  • 返回值为以下常量之一: CONCUR_READ_ONLY 或 CONCUR_UPDATABLE
boolean previous () 1. 2 将游标移动到前一行。
  • 如果游标位于某一行上,则返回 true;如果游标位于第一行之前的位置,则返回 false。
int get Row() 1. 2 得到当前行的序号。
  • 所有行从1 开始编号。
boolean absolute(int r) 1.2 移动游标到第 r 行。
  • 如果游标位于某一行上,则返回 true。
boolean relative(int d) 1.2 将游标移动 d 行。
  • 如果 d 为负数,则游标向后移动。如果游标位于某一行上,则返回 true。
boolean first() 1.2 移动游标到第一行或最后一行。
  • 如果游标位于某一行上,则返回 true。
boolean last() 1.2
void beforeFirst() 1.2 移动游标到第一行之前或最后一行之后的位置。
void afterlast() 1.2
boolean isFirst() 1.2 测试游标是否在第一行或最后一行。
boolean islast() 1.2
boolean isBeforeFirst() 1.2 测试游标是否在第一行之前或最后一行之后的位置。
boolean isAfterlast() 1.2
void moveToinsertRow() 1.2 移动游标到插入行。
  • 插入行是一个特殊的行,可以在该行上使用 updateXxx 和 insertRow 方法来插入新数据。
void moveToCurrentRow() 1.2 将游标从插入行移回到调用 moveToInsertRow 方法之前它所在的那一行。
void insertRow() 1.2 将插入行上的内容插入到数据库和结果集中。
void deleteRow() 1.2 从数据库和结果集中删除当前行。
void updateXxx(int column , Xxx data) 1.2 (Xxx 指数据类型,比如 int 、double 、String、Date 等)更新结果中当前行上的某个字段值。
void updateXxx(String columnName , Xxx data) 1.2
void updateRow() 1.2 将当前行的更新信息发送到数据库。
void cancelRowUpdates() 1.2 撤销对当前行的更新。
java.sql.DatabaseMetaData 1.1
boolean supportsResultSetType(int type) 1.2 如果数据库支持给定类型的结果集,则返回 true。
  • type 是 ResultSet 接口中的下述常量之一: TYPE_FORWARD_ONLY、TYPL_SCROLL_INSENSITIVE 或者 TYPE_SCROLL_SENSITIVE
boolean supportsResul tSetConcurrency(int type, int concurrency) 1.2 如果数据库支持给定类型和并发模式的结果集,则返回 true。

参数:

  1. type:ResultSet 接口中的下述常量之一: TYPE_FORWARD_ONLY、TYPL_SCROLL_INSENSITIVE 或者 TYPE_SCROLL_SENSITIVE
  2. concurrency:ResultSet 接口中的下述常量之一: CONCUR_ READ_ONLY 或者 CONCUR_UPDATABLE

行集

可滚动的结果集虽然功能强大,却有一个重要的缺陷:在与用户的整个交互过程中,必须始终与数据库保持连接。用户也许会离开电脑旁很长一段时间,而在此期间却始终占有着数据库连接。这种方式存在很大的问题,因为数据库连接属于稀有资源。在这种情况下,我们可以使用行集。

RowSet 接口扩展自 ResultSet 接口,却无需始终保持与数据库的连接。

行集还适用于将查询结果移动到复杂应用的其他层,或者是诸如手机之类的其他设备中。你可能从未考虑过移动一个结果集,因为它的数据结构非常庞大,且依赖于数据连接。

构建行集

以下为 javax.sql.rowset 包提供的接口,它们都扩展了 RowSet 接口:

  • CachedRowSet:允许在断开连接的状态下执行相关操作。
  • WebRowSet 对象:代表了一个被缓存的行集,该行集可以保存为 XML 文件。
    • 该文件可以移动到Web 应用的其他层中,只要在该层中使用另一个 WebRowSet 对象重新打开该文件即可。
  • FilteredRowSetJoinRowSet 接口:支持对行集的轻量级操作,它们等同于 SQL 中的 SELECT 和 JOIN 操作。
    • 这两个接口的操作对象是存储在行集中的数据,因此运行时无需建立数据库连接。
  • JdbcRowSet 是ResultSet 接口:的一个瘦包装器。它在 RowSet 接口中添加了有用的方法。


在Java 7 中,有一种获取行集的标准方式:

RowSetFactory factory = RowSetProvider.newFactory();
CachedRowSet crs = factory.createCachedRowSet();

获取其他行集类型的对象也有类似的方法。


在 Java 7 之前, 创建行集的方法都是与供应商相关的。另外,JDK 在 com.sun.rowset 中还提供了参考实现,这些实现类的名字以 Impl 结尾,例如“CachedRowSetImpl”。

  • 如果你无法使用 RowSetProvider,那么可以使用下面的类取而代之:
    CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl();
    

被缓存的行集

一个被缓存的行集中包含了一个结果集中所有的数据:
    CachedRowSet 是 ResultSet 接口的子接口,所以你完全可以像使用结果集一样来使用被缓存的行集。

被缓存的行集有一个非常重要的优点: 断开数据库连接后仍然可以使用行集:
    只需打开数据库连接、执行查询操作、将查询结果放入被缓存的行集,然后关闭数据库连接即可。

甚至可以修改被缓存的行集中的数据:
    这些修改不会立即反馈到数据库中,必须发起一个显式的请求,以便让数据库真正接受所有修改。
    此时 CachedRowSet 类会重新连接到数据库,并通过执行 SQL 语句向数据库中写人所有修改后的数据。

使用行集:

  1. 创建行集:【两种方法】
    1. 使用结果集填充 CachedRowSet 对象:
      ResultSet result = . . .;
      
      RowSetFactory factory = RowSetProvider.newFactory();
      CachedRowSet crs = factory.createCachedRowSet();
      crs.popu1ate(result);
      
      conn.c1ose ();    // now OK to close the database connection
      
    2. 让 CachedRowSet 对象自动建立一个数据库连接:
      // 设置数据库参数:
      crs.setURL("jdbc:derby://localhost:1527/COREJAVA");
      crs.setUsername("dbuser");
      crs.setPassword("secret");
      
      // 设置查询语句和所有参数:
      crs.setCommand ("SELECT * FROM Books WHERE Publisher_ID = ?");
      crs.setString(1, publisherId);
      
      // 将查询结果填充到行集中:
      crs.execute();
      
      • 可以指定每一页的尺寸:
        CachedRowSet crs = . . .;
        crs.setCommand(command);
        crs.setPageSize(20);
        . . .
        crs.execute();
        
      • 获取下一批数据:
        crs.nextPage();
        
  2. 将修改写回到数据库中:【两种方法】
    1. 指定写回的数据库的连接:
      crs.acceptChanges(conn);
      
    2. 在行集中设置了连接数据库所需的信息(如:URL、用户名和密码)时:
      crs.acceptChanges();
      
    • 如果一个行集包含的是复杂查询的查询结果,那么我们就无法将对行集数据的修改写回到数据库中。
    • 如果是使用结果集来填充行集,那么行集就无从获知需要更新数据的数据库表名。此时,必须调用 setTable 方法来设直表名称。【???】
在填充了行集之后,数据库中的数据发生了改变,这显然容易造成数据不一致性:

    为了解决这个问题,参考实现会首先检查行集中的原始值(即,修改前的值)是否与数据库中的当前值一致:
    1、如果一致,都么修改后的值将覆盖数据库中的当前值。
    2、否则,将抛出 SyncProviderException 异常,且不向数据库写回任何值。
    
    在实现行集接口时其他实现也可以采用不同的同步策略。


相关方法:

javax.sql.Rowset 1.4
String getURL() 获取或设置数据库的 URL。
void setURL(String url)
String getUsername() 获取或设置连接数据库所需的用户名。
void setUsername(String username)
String getpassword() 获取或设置连接数据库所需的密码。
void setPassword(String password)
String getCommand() 获取或设置向行集中填充数据时需要执行的命令。
void setCommand(String command)
void execute() 通过执行使用 setCommand 方法设置的语句集来填充行集。
  • 为了使驱动管理器可以获得连接,必须事先设定 URL、用户名和密码。
javax.sql.rowset.CachedRowSet 5.0
void execute(Connection conn) 通过执行使用 setCommand 方法设置的语句集来填充行集。
  • 该方法使用给定的连接,并负责关闭它。
void populate(ResultSet result) 将指定的结果集中的数据填充到被缓存的行集中。
String getTableName() 获取或设置数据库表名称,填充被缓存的行集时所需的数据来自该表。
void setTableName(String tableName)
int getpageSize() 获取和设置页的尺寸。
void setpageSize(int size)
boolean nextPage() 加载下一页或上一页,如果要加载的页存在,则返回 true。
boolean previousPage()
void acceptChanges() 重新连接数据库,并写回行集中修改过的数据。
  • 如果因为数据库中的数据已经被修改而导致无法写回行集中的数据,该方法可能会抛出 SyncProviderException 异常。
void acceptChanges (Connection conn)
javax.sql.rowset.RowSetProvider 7
static RowSetFactory newFactory() 创建一个行集工厂。
CachedRowSet createCachedRowSet() 创建一个指定类型的行集。
FilteredRowSet createFilteredRowSet()
JdbcRowSet createJdbcRowSet()
JoinRowSet createJoinRowSet()
WebRowSet createWebRowSet()

元数据

JDBC 还可以提供关于数据库及其表结构的详细信息。例如,可以获取某个数据库的所有表的列表,也可以获得某个表中所有列的名称及其数据类型。

如果是在开发业务应用时使用事先定义好的数据库,那么数据库结构和表信息就不是非常有用了。毕竟,在设计数据库表时,就已经知道了它们的结构。

但是,对于那些编写数据库工具的程序员来说,数据库的结构信息却是极其有用的

在 SQL 中,描述数据库或其组成部分的数据称为元数据(区别于那些存在数据库中的实际数据)。


我们可以获得三类元数据:

  1. 关于数据库的元数据(DatabaseMetaData);
  2. 关于结果集的元数据(ResultSetMetaData);
  3. 关于预备语句参数的元数据(?)。

DatabaseMetaData

DatabaseMetaData 接口用于提供有关于数据库的数据。

  • 其结果集中的每一行都包含了数据库中一张表的详细信息(其中,第三列是表的名称)。


示例:
DatabaseMetaData meta = conn.getMetaData();

while(meta.next())
    tableNames.addItem(meta.getString(3));


  • DatabaseMetaData 接口中有上百个方法可以用于查询数据库的相关信息,包括一些使用奇特的名字进行调用的方法,如:
    meta.supportCatalogsInPrivilegeDefinitions()
    
    meta.nullPlusNonNullIsNull()
    

ResultSetMetaData

ResultSetMetaData 用于提供结果集的相关信息。

  • 每当通过查询得到一个结果集时,都可以获取该结果集的列数以及每一列的名称、类型和宇段宽度。


示例:
ResultSet rs = stat.executeQuery("SELECT * FROM " + tableName);

ResultSetMetaData meta = rs.getMetaData();
for (inti = 1; i <= meta.getColu1nCount(); i++)
{
    String columnName = meta.getColumnLabel(i);
    int columnWidth = meta.getColu1nDisplaySize(i);
    . . .
}


相关方法:

java.sql.Connection 1.1
Database MetaData getMetaData() 返回一个 DatabaseMetaData 对象,该对象封装了有关数据库连接的元数据。
java.sql.DatabaseMetaData 1.1
ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[]) 返回某个目录(catalog)中所有表的描述,该目录必须匹配给定的模式(schema)、表名字模式以及类型标准。

(模式用于描述一组相关的表和访问权限,而目录描述的是一组相关的模式,这些概念对组织大型数据库非常重要。)

  • catalog 和 schema 参数可以为 "",用于检索那些没有目录或模式的表。
    • 如果不想考虑目录和模式,也可以将上述参数设为 null。
  • types 数组包含了所需的表类型的名称,通常表类型有 TABLE 、VIEW 、SYSTEM TABLE 、GLOBAL TEMPORARY 、LOCAL TEMPORARY 、ALIAS 和SYNONYM 。
    • 如果 types 为 null,则返回所有类型的表。

返回的结果集共有 5 列,均为String 类型:

名称 解释
1 TABLE一CAT 表目录(可以为 null)
2 TABLE_SCHE 间表模式(可以为 null)
3 TABLE_ NAME 表名称
4 TABLE_ TYPE 表类型
5 REMARKS 关于表的注释
int getJDBCMajorVersion() 1.4 返回建立数据库连接的 JDBC 驱动程序的主版本号和次版本号。
例如,一个 JDBC 3.0 的驱动程序有一个主版本号 3 和一个次版本号 0。
int getJDBCMinorVersion() 1.4
int getMaxConnections() 返回可同时连接到数据库的最大并发连接数。
int getMaxStatements() 返回单个数据库连接允许同时打开的最大并发语句数。
  • 如果对允许打开的语句数目没有限制或者不可知,则返回0。
java.sql.ResultSet 1.1
ResultSetMetaData getMetaData() 返回与当前 ResultSet 对象中的列相关的元数据。
java.sql.ResultSetMetaData 1.1
int getColumnCount() 返回当前 ResultSet 对象中的列数。
int getColumnDisplaySize(int column) 返回给定列序号的列的最大宽度。

参数: column 列序号

String getColumnLabel(int column) 返回该列所建议的名称。

参数: column 列序号

String getColumnName(int column) 返回指定的列序号所对应的列名。

参数: column 列序号

事务

如果将更新语句组合成一个事务(transaction),那么事务要么成功地执行所有操作并提交(commit),要么在中间某个位置发生失败。在这种情况下,可以执行回滚(rollback)操作,则数据库将自动撤销上次提交事务以来的所有更新操作产生的影响。

将多个语句组合成事务的主要原因是为了确保数据库完整性(database integrity)。

用JDBC对事务编程

默认情况下,数据库连接处于自动提交模式(autocommit mode)。

JDBC 事务编程:

  1. 关闭这个默认值:
    conn.setAutoCommit(false);
    
  2. 创建一个语句对象:【开始事务】
    Statement stat = conn.createStatement();
    
    String command1 = . . .;
    String command2 = . . .;
    String command3 = . . .;
    
  3. 任意多次地调用 executeUpdate 方法:
    stat.executeUpdate(command1);
    stat.executeUpdate(command2);
    stat.executeUpdate(command3) ;
    
  4. 如果执行了所有命令之后没有出错,则调用 commit 方法:【提交事务】
    conn.commit();
    
  5. 如果出现错误,则调用:【回滚事务】
    conn.rollback();
    

当事务被 SQLException 异常中断时,典型的办法就是发起回滚操作。

保存点

在使用某些驱动程序时,使用保存点(save point)可以更细粒度地控制回滚操作。

保存点意味着稍后只需返回到这个点,而非事务的开头

  1. 开始事务(创建语句对象):
    Statement stat = conn.createStatement() ;    // start transaction ;rollback() goes here
    
    String commandl = . . .;
    stat.executeUpdate(commandl);
    
  2. 创建保存点:
    Savepoint svpt = conn.setSavepoint();    // set save point; ro11back(svpt) goes here
    
    String command2 = . . .;
    stat.executeUpdate(command2);
    
  3. 回滚到保存点:
    if(. . .)
        conn.rollback(svpt);
    
    . . .
    conn.commit();
    
  4. 当不再需要保存点时,必须释放它:
    conn.releaseSavepoint(svpt);
    

批量更新

假设有一个程序需要执行许多 INSERT 语句,以便将数据填人数据库表中,此时可以使用批量更新的方法来提高程序性能。


在使用批量更新(batch update)时,一个语句序列作为一批操作将同时被收集和提交。

  • 使用 DatabaseMetaData 接口中的 supportsBatchUpdates 方法可以获知数据库是否支持这种特性。
  • 处于向一批中的语句可以是“INSERT”、“UPDATE”和“DELETE”等操作,也可以是数据库定义语句,如“CREATE TABLE”和“DROP TABLE”。
    • 但是,在批量处理中添加“SELECT”语句会抛出异常(从概念上讲,批量处理中的 SELECT 语句没有意义,因为它会返回结果集,而并不更新数据库)。


为了在批量模式下正确地处理错误,必须将批量执行的操作视为单个事务。如果批量更新在执行过程中失败,那么必须将它回滚到批量操作开始之前的状态。

批量更新步骤:

  1. 关闭自动提交模式(并保存原提交模式):
    boolean autoCommit = conn.setAutoCommit();
    conn.setAutoCommit(false);
    
  2. 开始事务(创建语句对象):
    Statement stat = conn.createStatement();
    
  3. 添加批量更新的语句:
    String command = "CREATE TABLE . . .";
    stat.addBatch(command);
    
    while(. . .)
    {
        command = "INSERT INTO . . . VALUES (" + . . . + ")";
        stat.addBatch(command);
    }
    
    • 应该调用“addBatch”方法,而非“executeUpdate”方法
  4. 提交批量更新:
    int[] counts = stat.executeBatch();
    
    // 或者
    stat.executeBatch();
    
    • 调用 executeBatch 方法将为所有已提交的语句返回一个记录数的数组。
  5. 提交事务:
    conn.commit();
    
  6. 恢复最初的提交模式:
    conn.setAutoCommit(autoCommit);
    


相关方法:

java.sql.Connection 1.1
boolean getAutoCommit() 获取该连接中的自动提交模式,或将其设置为 b。

如果自动更新为 true,那么所有语句将在执行结束后立刻被提交。

void setAutoCommit(boolean b)
void commit() 提交自上次提交以来所有执行过的语句。
void rollback() 撤销自上次提交以来所有执行过的语句所产生的影响。
Savepoint setSavepoint() 1.4 设置一个匿名或具名的保存点。
Savepoint setSavepoint(String name) 1.4
void rollback(Savepoint svpt) 1.4 回滚到给定保存点。
void releaseSavepoint(Savepoint svpt) 1.4 释放给定的保存点。
java.sql.Savepoint 1.4
int getSavepointld() 获取该匿名保存点的 ID 号。

如果该保存点具有名字,则抛出一个 SQLException 异常。

String getSavepointName() 获取该保存点的名称。

如果该对象为匿名保存点,则抛出一个 SQLException 异常。

java.sql.Statement 1.1
void addBatch(String command) 1.2 添加命令到该语句当前的批量命令中。
int[] executeBatch() 1.2 执行当前批量更新中的所有命令。

返回一个记录数的数组,其中每一个元素都对应一条语句:

  1. 如果其值非负,则表示受该语句影响的记录总数;
  2. 如果其值为 SUCCESS_NO_ INFO,则表示该语句成功执行了,但没有记录数可用;
  3. 如果其值为 EXECUTE_FAILED,则表示该语句执行失败了。
long[] executeLargeBatch() 8
java.sql.DatabaseMetaData 1.1
boolean supportsBatchUpdates() 1.2 如果驱动程序支持批量更新,则返回 true。

高级SQL类型

JDBC支持的 SQL 数据类型以及它们在 Java 语言中对应的数据类型:

SOL 数据类型及其对应的 Java 类型.png

其中:

  1. ARRAY”:SQLARRAY (SQL 数组)指的是值的序列。例如,Student 表中通常都会有一个 Scores 列,这个列就应该是 ARRAY OF INTEGER(整数数组)。“getArray”方法返回一个接口类型为“java.sql.Array”的对象,该接口中有许多方法可以用于获取数组的值。
    • 从数据库中获得一个 LOB数组 并不等于获取了它的实际内容,只有在访问具体的值时它们才会从数据库中被读取出来。(这对改善性能非常有好处,因为通常这些数据的数据量都非常大)
  2. ROWID”:某些数据库支持描述行位值的“ROWID”值,这样就可以非常快捷地获取某一行值。JDBC 4 引入了“java.sql.RowId”接口,并提供了用于在查询中提供行 ID,以及从结果中获取该值的方法。
  3. NCHAR(N),NVARCHAR(N),LONG NVARCHAR”:国家属性字符串(NCHAR及其变体)按照本地字符编码机制存储字符串,并使用本地排序惯例对这些字符串进行排序。JDBC4提供了方法,用于在查询和结果中进行 Java的 String 对象和国家属性字符串之间的双向转换。
  4. NCLOB”:有些数据库可以存储用户自定义的结构化类型。JDBC3提供了一种机制用于将 SQL 结构化类型自动映射成 Java 对象。
  5. SQLXML”:有些数据库提供用于 XML 数据的本地存储。 JDBC4 引入了SQLXML接口 , 它可以在内部的 XML 表示和 DOM 的 Source/Result 接口或二进制流之间起到中介作用。

Web与企业应用中的连接管理

  • 【使用“database.properties”文件对数据库连接进行简单设置,这种方法适用于小型的测试程序,但是不适用于规模较大的应用。】
  1. 在Web或企业环境中部署JDBC应用时,数据库连接管理与“Java名字和目录接口”(JNDI)是集成在一起的:
    遍布企业的数据源的属性可以存储在一个目录中,采用这种方式使得我们可以集中管理用户名、密码、数据库名和 JDBC URL:
    Context jndiContext = new InitialContext(); 
    DataSource source = (DataSource) jndiContext.lookup("java:comp/env/jdbc/corejava");
    Connection conn = source.getConnection();
    
    • 不再使用“DriverManager”,而是使用 JNDI 服务来定位数据源:
  2. 数据源(DataSource):就是一个能够提供简单的 JDBC连接和更多高级服务的接口,比如执行涉及多个数据库的分布式事务。(“javax.sql”标准扩展包定义了“DataSource”接口)
    在 Java EE 的容器中,甚至不必编程进行 JNDI 查找,只常在 DataSource 域上使用 Resource 注解,当加载应用时,这个数据源引用将被设置:
    @Resource (name= "jdbc/ corej ava") 
    private DataSou rce source;
    
  3. 数据库连接池(pool):数据库连接在物理上并未被关闭,而是保留在一个队列中并被反复重用。
    通过获取数据源并调用“getConnection”方法来得到连接池中的连接。使用完连接后,需要调用“close”方法。该方法并不在物理上关闭连接,而只是告诉连接池已经使用完该连接。连接池通常还会将池机制作用于预备语句上。