一、JDBC 前言
1.1 JDBC 发展史简记
- JDBC 1.0:随着 JDK 1.1 一起发布。JDBC 操作相关的接口和类位于
java.sql
包中。包含了DriverManager
类,Driver
接口,Connection
接口,Statement
接口,ResultSet
接口,SQLException
类等。 - JDBC 2.0:随着 JDK 1.2 一起发布。 JDBC API被划分为两部分:核心API(java.sql包)和扩展API(javax.sql包)。
- JDBC 3.0:随 JDK 1.4 一起发布。
- JDBC 4.0:随 JDK 1.6 一起发布。
- JDBC 4.1:随 JDK 1.7 一起发布。
- JDBC 4.2:随 JDK 1.8 一起发布。
1.2 JDBC 简介
JDBC 是一种规范,它提供的接口,一套完整的,可移植的访问底层数据库的程序。
JDBC API中重要的接口和类:
- DriverManager:这个类管理数据库驱动程序的列表。
- Connection : 此接口表示通信上下文,即与数据库中的所有的通信都是通过此连接对象。
- Statement : 【不太合理】可以使用这个接口创建的对象的SQL语句提交到数据库。一些派生的接口接受除执行存储过程的参数。
- ResultSet:【不太合理】 这些对象保存从数据库后,执行使用
Statement
对象的SQL查询中检索数据。它作为一个迭代器,可以通过移动它来检索下一个数据。 - SQLException:这个类用于处理发生在数据库应用程序中的任何错误。
二、JDBC 使用
传统构建 JDBC 应用程序的五个步骤:
- 注册JDBC驱动程序:需要初始化驱动程序,以便可以打开与数据库的通信通道。
- 打开一个连接:需要使用
DriverManager.getConnection()
方法创建一个Connection
对象,它表示与数据库的物理连接。 - 执行查询:需要使用类型为
Statement
的对象来构建和提交SQL语句到数据库。 - 从结果集中提取数据:需要使用相应的
ResultSet.getXXX()
方法从结果集中检索数据。 - 清理环境:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
String JDBC_DRIVER = "com.mysql.jdbc.Driver";
String DB_URL = "jdbc:mysql://localhost/emp";
String USER = "root";
String PASS = "123456";
// 注册JDBC驱动程序
Class.forName("com.mysql.jdbc.Driver");
// 打开一个连接
Connection conn = DriverManager.getConnection(DB_URL,USER,PASS);
// 执行查询
Statement stmt = conn.createStatement();
String sql = "...";
ResultSet rs = stmt.executeQuery(sql);
// 从结果集中提取数据
// 清理环境
rs.close();
stmt.close();
conn.close();
2.1 Statement
当获得了与数据库的连接后,就可以与数据库进行交互了。 JDBC Statement
,CallableStatement
和PreparedStatement
接口定义了可用于发送SQL或PL/SQL命令,并从数据库接收数据的方法和属性。
接口 | 推荐使用 |
---|---|
Statement | 用于对数据库进行通用访问,在运行时使用静态SQL语句时很有用。 Statement 接口不能接受参数。 |
PreparedStatement | 当计划要多次使用SQL语句时使用。PreparedStatement 接口在运行时接受输入参数。 |
CallableStatement | 当想要访问数据库存储过程时使用。CallableStatement 接口也可以接受运行时输入参数。 |
Statement对象
在使用Statement
对象执行SQL语句之前,需要使用Connection
对象的createStatement()
方法创建一个Statement
对象。
Statement stmt = conn.createStatement();
在创建Statement
对象后,可以使用它来执行一个SQL语句,它有三个执行方法可以执行。它们分别是:
boolean execute (String SQL)
: 如果可以检索到ResultSet
对象,则返回一个布尔值true
; 否则返回false
。使用此方法执行SQL**DDL
**语句或需要使用真正的动态SQL,可使用于执行创建数据库,创建表的SQL语句等等。int executeUpdate (String SQL)
:返回受SQL语句执行影响的行数。使用此方法执行预期会影响多行的SQL语句,例如:INSERT
,UPDATE
或DELETE
语句。ResultSet executeQuery(String SQL)
:返回一个ResultSet
对象。 当您希望获得结果集时,请使用此方法,就像使用SELECT
语句一样。
PreparedStatement对象
PreparedStatement
接口扩展了Statement
接口,它添加了比Statement
对象更好一些优点的功能。此语句可以动态地提供/接受参数。
String SQL = "Update Employees SET age = ? WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(SQL);
JDBC 中的所有参数都由 ?
符号作为占位符,这被称为参数标记。
在执行SQL语句之前,必须为每个参数(占位符)提供值。并使用setXXX()
方法将值绑定到参数,其中XXX
表示要绑定到输入参数的值的Java数据类型。 如果忘记提供绑定值,则将会抛出一个SQLException
。
每个参数标记是它其顺序位置引用。与Java数组索引不同,开始位置是1
。
所有Statement
对象与数据库交互的方法(a)execute()
,(b)executeQuery()
和(c)executeUpdate()
也可以用于PreparedStatement
对象。 但是,这些方法被修改为可以使用输入参数的SQL语句。
CallableStatement对象
类似Connection
对象创建Statement
和PreparedStatement
对象一样,它还可以使用同样的方式创建CallableStatement
对象,该对象将用于执行对数据库存储过程的调用。
存储过程存在三种类型的参数:IN
,OUT
和INOUT
。 PreparedStatement
对象只使用IN
参数。CallableStatement
对象可以使用上面三个参数类型。
参数 | 描述 |
---|---|
IN | 创建SQL语句时其参数值是未知的。 使用setXXX() 方法将值绑定到IN 参数。 |
OUT | 由SQL语句返回的参数值。可以使用getXXX() 方法从OUT参数中检索值。 |
INOUT | 提供输入和输出值的参数。使用setXXX() 方法绑定变量并使用getXXX() 方法检索值。 |
String strSQL = "{call getEmpName (?, ?)}";
CallableStatement cstmt = conn.prepareCall (SQL);
String变量strSQL
表示存储过程,带有两个参数占位符。
使用CallableStatement
对象就像使用PreparedStatement
对象一样。 在执行语句之前,必须将值绑定到所有参数,否则将抛出一个SQLException
异常。
如果有IN
参数,只需遵循适用于PreparedStatement
对象的相同规则和技术; 使用与绑定的Java数据类型相对应的setXXX()
方法。
使用OUT
和INOUT
参数时,必须使用一个额外的CallableStatement
对象方法registerOutParameter()
。 registerOutParameter()
方法将JDBC数据类型绑定到存储过程并返回预期数据类型。
当调用存储过程,可以使用适当的getXXX()
方法从OUT
参数中检索该值。 此方法将检索到的SQL类型的值转换为对应的Java数据类型。
2.2 结果集ResultSet
java.sql.ResultSet
接口表示数据库查询的结果集。ResultSet
对象维护指向结果集中当前行的游标。
【重点—-》》》》待细补充】
处理NULL值
SQL使用NULL
值和Java使用null
是不同的概念。 所以要在Java中处理SQL NULL
值,可以使用三种策略:
- 避免使用返回原始数据类型的
getXXX()
方法。 - 对原始数据类型使用包装类,并使用
ResultSet
对象的wasNull()
方法来测试接收getXXX()
方法的返回值的包装器类变量是否应设置为null
。 - 使用原始数据类型和
ResultSet
对象的wasNull()
方法来测试接收到由getXXX()
方法返回的值的原始变量是否应设置为表示NULL
的可接受值。
2.3 事务
默认情况下JDBC连接处于自动提交模式,每个SQL语句在完成后都会提交到数据库。
事务能够控制何时更改提交并应用于数据库。 它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,整个事务将失败。
要启用手动事务支持,而不是使用JDBC驱动程序默认使用的自动提交模式,请调用Connection
对象的setAutoCommit()
方法。 如果将布尔的false
传递给setAutoCommit()
,则关闭自动提交。 也可以传递一个布尔值true
来重新打开它。
conn.setAutoCommit(false);
提交和回滚
完成更改后,若要提交更改,那么可在连接对象上调用commit()
方法,若想回撤或者说发生异常的情况下可以在连接对象上调用rollback()
方法:
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees VALUES (107, 22, 'Sita', 'Singh')";
stmt.executeUpdate(SQL);
// If there is no error.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback();
}
保存点
JDBC 3.0 新添加了Savepoint
接口提供了额外的事务控制能力。大多数现代DBMS支持其环境中的保存点,如Oracle的PL/SQL。
设置保存点(Savepoint
)时,可以在事务中定义逻辑回滚点。 如果通过保存点(Savepoint
)发生错误时,则可以使用回滚方法来撤消所有更改或仅保存保存点之后所做的更改。
Connection
对象有两种新的方法可用来管理保存点:
- setSavepoint(String savepointName):定义新的保存点,它还返回一个
Savepoint
对象。 - releaseSavepoint(Savepoint savepointName) :删除保存点。要注意,它需要一个
Savepoint
对象作为参数。 该对象通常是由setSavepoint()
方法生成的保存点。
rollback (String savepointName)
方法,它将使用事务回滚到指定的保存点。
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//set a Savepoint
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERT INTO Employees VALUES (106, 24, 'Curry', 'Stephen')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees VALUES (107, 32, 'Kobe', 'Bryant')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback(savepoint1);
}
2.4 异常SQLException
方法 | 描述 |
---|---|
getErrorCode( ) | 获取与异常关联的错误代码。 |
getMessage( ) | 获取驱动程序处理的错误的JDBC驱动程序的错误消息,或获取数据库错误的Oracle错误代码和消息。 |
getSQLState( ) | 获取XOPEN SQLstate字符串。 对于JDBC驱动程序错误,不会从此方法返回有用的信息。 对于数据库错误,返回五位数的XOPEN SQLstate代码。 此方法可以返回null 。 |
getNextException( ) | 获取异常链中的下一个Exception 对象。 |
printStackTrace( ) | 打印当前异常或可抛出的异常,并将其追溯到标准错误流。 |
printStackTrace(PrintStream s) | 将此throwable 及其回溯打印到指定的打印流。 |
printStackTrace(PrintWriter w) | 打印这个throwable ,它是回溯到指定的打印器(PrintWriter )。 |
2.5 批量处理
批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用来提交它们,一次执行完成与数据库之间的交互。一次向数据库发送多个SQL语句时,可以减少通信开销,从而提高性能。
- 不需要 JDBC 驱动程序来支持此功能。应该使用
DatabaseMetaData.supportsBatchUpdates()
方法来确定目标数据库是否支持批量更新处理。如果JDBC驱动程序支持此功能,该方法将返回true
。 Statement
,PreparedStatement
和CallableStatement
的addBatch()
方法用于将单个语句添加到批处理。executeBatch()
用于执行组成批量的所有语句。executeBatch()
返回一个整数数组,数组的每个元素表示相应更新语句的更新计数。- 就像将批处理语句添加到处理中一样,可以使用
clearBatch()
方法删除它们。此方法将删除所有使用addBatch()
方法添加的语句。 但是,无法指定选择某个要删除的语句。
使用Statement对象进行批处理的典型步骤序列:
- 使用
createStatement()
方法创建Statement
对象。 - 使用
setAutoCommit()
将自动提交设置为false
。 - 使用
addBatch()
方法在创建的Statement
对象上添加SQL语句到批处理中。 - 在创建的
Statement
对象上使用executeBatch()
方法执行所有SQL语句。 - 最后,使用
commit()
方法提交所有更改。
// Create statement object
Statement stmt = conn.createStatement();
// Set auto-commit to false
conn.setAutoCommit(false);
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(200,'Ruby', 'Yang', 30)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(201,'Java', 'Lee', 35)";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create one more SQL statement
String SQL = "UPDATE Employees SET age = 35 WHERE id = 100";
// Add above SQL statement in the batch.
stmt.addBatch(SQL);
// Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
使用PrepareStatement对象进行批处理的典型步骤顺序:
- 使用占位符创建SQL语句。
- 使用
prepareStatement()
方法创建PrepareStatement
对象。 - 使用
setAutoCommit()
将自动提交设置为false
。 - 使用
addBatch()
方法在创建的Statement
对象上添加SQL语句到批处理中。 - 在创建的
Statement
对象上使用executeBatch()
方法执行所有SQL语句。 - 最后,使用
commit()
方法提交所有更改。
// Create SQL statement
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(?, ?, ?, ?)";
// Create PrepareStatement object
PreparedStatemen pstmt = conn.prepareStatement(SQL);
//Set auto-commit to false
conn.setAutoCommit(false);
// Set the variables
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "JDBC" );
pstmt.setString( 3, "Li" );
pstmt.setInt( 4, 33 );
// Add it to the batch
pstmt.addBatch();
// Set the variables
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "CSharp" );
pstmt.setString( 3, "Liang" );
pstmt.setInt( 4, 31 );
// Add it to the batch
pstmt.addBatch();
//add more batches
...
//Create an int[] to hold returned values
int[] count = stmt.executeBatch();
//Explicitly commit statements to apply changes
conn.commit();
三、高级
自动加载驱动
在 JDBC 4.0 之前,编写 JDBC 程序都需要加上以下代码:
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
Java.sql.DriverManager
的内部实现机制决定了这样代码的出现。只有先通过 Class.forName
找到特定驱动的 class 文件,DriverManager.getConnection
方法才能顺利地获得 Java 应用和数据库的连接。这样的代码为编写程序增加了不必要的负担,JDK 的开发者也意识到了这一点。
从 Java 6 开始,应用程序不再需要显式地加载驱动程序了,DriverManager 开始能够自动地承担这项任务。
DriverManager 为什么能够做到自动加载呢?这就要归功于一种被称为 Service Provider
的新机制。
熟悉 Java 安全编程的程序员可能对其已经是司空见惯,而它现在又出现在 JDBC 模块中。JDBC 4.0 的规范规定,所有 JDBC 4.0 的驱动 jar文件必须包含一个 java.sql.Driver
,它位于 jar 文件的 META-INF/services
目录下。这个文件里每一行便描述了一个对应的驱动类。其实,编写这个文件的方式和编写一个只有关键字(key)而没有值(value)的 properties 文件类似。同样地,‘#’之后的文字被认为是注释。有了这样的描述,DriverManager 就可以从当前在 CLASSPATH 中的驱动文件中找到,它应该去加载哪些类。
而如果我们在 CLASSPATH 里没有任何 JDBC 4.0 的驱动文件的情况下,调用代码会输出一个 sun.jdbc.odbc.JdbcOdbcDriver
类型的对象。而仔细浏览 JDK 6 的目录,这个类型正是在 %JAVA_HOME%/jre/lib/resources.jar
的 META-INF/services 目录下的 java.sql.Driver
文件中描述的。也就是说,这是 JDK 中默认的驱动。而如果开发人员想使得自己的驱动也能够被 DriverManager 找到,只需要将对应的 jar 文件加入到 CLASSPATH 中就可以了。
当然,对于那些 JDBC 4.0 之前的驱动文件,我们还是只能显式地去加载了。
// 罗列本地机器上的 JDBC 驱动
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
System.out.println(drivers.nextElement());
}