JAVA-JDBC
参考链接:https://www.yiibai.com/jdbc/jdbc_quick_guide.html
JDBC is wtf?
JDBC代表Java数据库连接(Java Database Connectivity),它是用于Java编程语言和数据库之间的数据库无关连接的标准Java API,换句话说:JDBC是用于在Java语言编程中与数据库连接的API。
JDBC库包括通常与数据库使用相关,如下面提到的每个任务的API -
- 连接到数据库
- 创建SQL或mysql语句
- 在数据库中执行SQL或MySQL查询
- 查看和修改结果记录
JDBC架构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
- JDBC API:提供应用程序到JDBC管理器连接。
- JDBC驱动程序API:支持JDBC管理器到驱动程序连接。
JDBC API使用驱动程序管理器并指定数据库的驱动程序来提供与异构数据库的透明连接。
JDBC驱动程序管理器确保使用正确的驱动程序来访问每个数据源。 驱动程序管理器能够支持连接到多个异构数据库的多个并发驱动程序。
以下是架构图,它显示了驱动程序管理器相对于JDBC驱动程序和Java应用程序的位置
常见的JDBC组件
JDBC API提供以下接口和类 -
DriverManager
:此类管理数据库驱动程序列表。 使用通信子协议将来自java应用程序的连接请求与适当的数据库驱动程序进行匹配。在JDBC下识别某个子协议的第一个驱动程序将用于建立数据库连接。Driver
:此接口处理与数据库服务器的通信。我们很少会直接与Driver
对象进行交互。 但会使用DriverManager
对象来管理这种类型的对象。 它还提取与使用Driver
对象相关的信息。Connection
:此接口具有用于联系数据库的所有方法。 连接(Connection
)对象表示通信上下文,即,与数据库的所有通信仅通过连接对象。Statement
:使用从此接口创建的对象将SQL语句提交到数据库。 除了执行存储过程之外,一些派生接口还接受参数。ResultSet
:在使用Statement
对象执行SQL查询后,这些对象保存从数据库检索的数据。 它作为一个迭代器并可移动ResultSet
对象查询的数据。SQLException
:此类处理数据库应用程序中发生的任何错误。
demo
//STEP 1. Import required packages
import java.sql.*;
public class FirstExample {
// JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/emp";
// Database credentials
static final String USER = "root";
static final String PASS = "123456";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//STEP 3: Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//STEP 4: Execute a query
System.out.println("Creating statement...");
stmt = conn.createStatement();
String sql;
sql = "SELECT id, first, last, age FROM Employees";
ResultSet rs = stmt.executeQuery(sql);
//STEP 5: Extract data from result set
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
//Display values
System.out.print("ID: " + id);
System.out.print(", Age: " + age);
System.out.print(", First: " + first);
System.out.println(", Last: " + last);
}
//STEP 6: Clean-up environment
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}//end finally try
}//end try
System.out.println("There are so thing wrong!");
}//end main
}//end FirstExample
JDBC数据库连接
建立JDBC连接所涉及的编程相当简单。 以下是基本的四个步骤 -
- 导入JDBC包:使用Java语言的
import
语句在Java代码开头位置导入所需的类。 - 注册JDBC驱动程序:使JVM将所需的驱动程序实现加载到内存中,从而可以满足JDBC请求。
- 数据库URL配置:创建一个正确格式化的地址,指向要连接到的数据库(如:MySQL,Oracle和MSSQL等等)。
- 创建连接对象:最后,调用
DriverManager
对象的getConnection()
方法来建立实际的数据库连接。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = null;
conn = DriverManager.getConnection("jdbc:mysql://hostname:port/db_name","db_username", "db_password");
conn.close()
JDBC Statements
接口 | 推荐使用 |
---|---|
Statement |
用于对数据库进行通用访问,在运行时使用静态SQL语句时很有用。 Statement 接口不能接受参数。 |
PreparedStatement |
当计划要多次使用SQL语句时使用。PreparedStatement 接口在运行时接受输入参数。 |
CallableStatement |
当想要访问数据库存储过程时使用。CallableStatement 接口也可以接受运行时输入参数。 |
Statement对象
在使用Statement
对象执行SQL语句之前,需要使用Connection
对象的createStatement()
方法创建一个Statement
对象,如以下示例所示:
Statement stmt = null;
try {
stmt = conn.createStatement( );
. . .
}
catch (SQLException e) {
. . .
}
finally {
stmt.close();
}
在创建Statement
对象后,可以使用它来执行一个SQL语句,它有三个执行方法可以执行。它们分别是 -
boolean execute (String SQL)
: 如果可以检索到ResultSet
对象,则返回一个布尔值true
; 否则返回false
。使用此方法执行SQLDDL
语句或需要使用真正的动态SQL,可使用于执行创建数据库,创建表的SQL语句等等。int executeUpdate (String SQL):
返回受SQL语句执行影响的行数。使用此方法执行预期会影响多行的SQL语句,例如:INSERT
,UPDATE
或DELETE
语句。ResultSet executeQuery(String SQL):
返回一个ResultSet
对象。 当您希望获得结果集时,请使用此方法,就像使用SELECT
语句一样。
PreparedStatement对象
PreparedStatement
接口扩展了Statement
接口,它添加了比Statement
对象更好一些优点的功能。
此语句可以动态地提供/接受参数。
PreparedStatement pstmt = null;
try {
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
. . .
}
catch (SQLException e) {
. . .
}
finally {
pstmt.close();
}
JDBC中的所有参数都由 ?
符号作为占位符,这被称为参数标记。 在执行SQL语句之前,必须为每个参数(占位符)提供值。
setXXX()
方法将值绑定到参数,其中XXX
表示要绑定到输入参数的值的Java数据类型。 如果忘记提供绑定值,则将会抛出一个SQLException
。
每个参数标记是它其顺序位置引用。第一个标记表示位置1
,下一个位置2
等等。 该方法与Java数组索引不同(它不从0
开始)。
所有Statement
对象与数据库交互的方法(a)execute()
,(b)executeQuery()
和(c)executeUpdate()
也可以用于PreparedStatement
对象。 但是,这些方法被修改为可以使用输入参数的SQL语句。
CallableStatement对象
类似Connection
对象创建Statement
和PreparedStatement
对象一样,它还可以使用同样的方式创建CallableStatement
对象,该对象将用于执行对数据库存储过程的调用。
假设需要执行以下Oracle存储过程 -
CREATE OR REPLACE PROCEDURE getEmpName
(EMP_ID IN NUMBER, EMP_FIRST OUT VARCHAR) AS
BEGIN
SELECT first INTO EMP_FIRST
FROM Employees
WHERE ID = EMP_ID;
END;
存在三种类型的参数:IN
,OUT
和INOUT
。 PreparedStatement
对象只使用IN
参数。CallableStatement
对象可以使用上面三个参数类型。
以下是上面三种类型参数的定义 -
参数 | 描述 |
---|---|
IN | 创建SQL语句时其参数值是未知的。 使用setXXX() 方法将值绑定到IN 参数。 |
OUT | 由SQL语句返回的参数值。可以使用getXXX() 方法从OUT参数中检索值。 |
INOUT | 提供输入和输出值的参数。使用setXXX() 方法绑定变量并使用getXXX() 方法检索值。 |
以下代码片段显示了如何使用Connection.prepareCall()
方法根据上述存储过程来实例化一个CallableStatement
对象 -
CallableStatement cstmt = null;
try {
//STEP 2: Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//STEP 3: Open a connection
System.out.println("Connecting to database...");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
//STEP 4: Execute a query
System.out.println("Creating statement...");
String sql = "{call getEmpName (?, ?)}";
stmt = conn.prepareCall(sql);
//Bind IN parameter first, then bind OUT parameter
int empID = 102;
stmt.setInt(1, empID); // This would set ID as 102
// Because second parameter is OUT so register it
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
//Use execute method to run stored procedure.
System.out.println("Executing stored procedure..." );
stmt.execute();
//Retrieve employee name with getXXX method
String empName = stmt.getString(2);
System.out.println("Emp Name with ID:" +
empID + " is " + empName);
stmt.close();
conn.close();
}
catch (SQLException e) {
. . .
}
finally {
. . .
}
String变量strSQL
表示存储过程,带有两个参数占位符。
使用CallableStatement
对象就像使用PreparedStatement
对象一样。 在执行语句之前,必须将值绑定到所有参数,否则将抛出一个SQLException
异常。
如果有IN
参数,只需遵循适用于PreparedStatement
对象的相同规则和技术; 使用与绑定的Java数据类型相对应的setXXX()
方法。
使用OUT
和INOUT
参数时,必须使用一个额外的CallableStatement
对象方法registerOutParameter()
。 registerOutParameter()
方法将JDBC数据类型绑定到存储过程并返回预期数据类型。
当调用存储过程,可以使用适当的getXXX()
方法从OUT
参数中检索该值。 此方法将检索到的SQL类型的值转换为对应的Java数据类型。
JDBC结果集
SQL语句执行后从数据库查询读取数据,返回的数据放在结果集中。 SELECT
语句用于从数据库中选择行并在结果集中查看它们的标准方法。 java.sql.ResultSet
接口表示数据库查询的结果集。ResultSet
对象维护指向结果集中当前行的游标。 术语“结果集”是指包含在ResultSet
对象中的行和列数据。
ResultSet
接口的方法可以分为三类:
- 浏览方法:用于移动光标。
- 获取方法:用于查看光标指向的当前行的列中的数据。
- 更新方法:用于更新当前行的列中的数据。 然后在基础数据库中更新数据。
光标可以基于ResultSet
的属性移动。当创建生成ResultSet
的相应Statement
时,将指定这些属性。
JDBC提供以下连接方法来创建具有所需ResultSet
的语句 -
createStatement(int RSType, int RSConcurrency);
prepareStatement(String SQL, int RSType, int RSConcurrency);
prepareCall(String sql, int RSType, int RSConcurrency);
ResultSet类型
可能的RSType
值如下。如果不指定任何ResultSet
类型,将自动分配一个TYPE_FORWARD_ONLY
值。
类型 | 描述 |
---|---|
ResultSet.TYPE_FORWARD_ONLY |
光标只能在结果集中向前移动。 |
ResultSet.TYPE_SCROLL_INSENSITIVE |
光标可以向前和向后滚动,结果集对创建结果集后发生的数据库所做的更改不敏感。 |
ResultSet.TYPE_SCROLL_SENSITIVE |
光标可以向前和向后滚动,结果集对创建结果集之后发生的其他数据库的更改敏感。 |
ResultSet的并发性
可能的RSConcurrency
如下。 如果不指定任何并发类型,将自动获得一个CONCUR_READ_ONLY
值。
并发 | 描述 |
---|---|
ResultSet.CONCUR_READ_ONLY |
创建只读结果集,这是默认值。 |
ResultSet.CONCUR_UPDATABLE |
创建可更新的结果集 |
try {
Statement stmt = conn.createStatement(
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
}
catch(Exception ex) {
....
}
finally {
....
}
浏览结果集
ResultSet
接口中有几种涉及移动光标的方法,包括 -
编号 | 方法 | 描述 |
---|---|---|
1 | public void beforeFirst() throws SQLException |
将光标移动到第一行之前 |
2 | public void afterLast() throws SQLException |
将光标移动到最后一行之后。 |
3 | public boolean first() throws SQLException |
将光标移动到第一行。 |
4 | public void last() throws SQLException |
将光标移动到最后一行。 |
5 | public boolean absolute(int row) throws SQLException |
将光标移动到指定的行。 |
6 | public boolean relative(int row) throws SQLException |
从当前指向的位置,将光标向前或向后移动给定行数。 |
7 | public boolean previous() throws SQLException |
将光标移动到上一行。 如果上一行关闭结果集,此方法返回false 。 |
8 | public boolean next() throws SQLException |
将光标移动到下一行。 如果结果集中没有更多行,则此方法返回false 。 |
9 | public int getRow() throws SQLException |
返回光标指向的行号。 |
10 | public void moveToInsertRow() throws SQLException |
将光标移动到结果集中的特殊行,该行可用于将新行插入数据库。当前光标位置被记住。 |
11 | public void moveToCurrentRow() throws SQLException |
如果光标当前位于插入行,则将光标移回当前行; 否则,此方法什么也不做 |
查看结果集
ResultSet
接口包含数十种获取当前行数据的方法。
每个可能的数据类型都有一个get
方法,每个get
方法有两个版本 -
- 一个是采用列名称。
- 另一个采用列索引。
例如,如果对查看感兴趣的列包含一个int
,则需要使用ResultSet
的其中一个getInt()
方法 -
序号 | 方法 | 描述 |
---|---|---|
1 | public int getInt(String columnName) throws SQLException |
返回名为columnName 的列中当前行中的int 值。 |
2 | public int getInt(int columnIndex) throws SQLException |
返回指定列索引当前行中的int 值。列索引从1 开始,意味着行的第一列为1 ,行的第二列为2 ,依此类推。 |
更新结果集
ResultSet
接口包含用于更新结果集的数据的更新方法的集合。
与get方法一样,每种数据类型都有两种更新方法 -
- 一个是采用列名称。
- 另一个采用列索引。
例如,要更新结果集当前行的String
列,可以使用以下updateString()
方法之一:
序号 | 方法 | 描述 |
---|---|---|
1 | public void updateString(int columnIndex, String s) throws SQLException |
将指定列中的String 值更改为指定的s 值。 |
2 | public void updateString(String columnName, String s) throws SQLException |
与前前的方法类似,除了使用列的名称而不是列的索引指定。 |
有八种基本数据类型的更新方法,以及java.sql
包中的String
,Object
,URL
和SQL数据类型。
更新结果集中的一行会更改ResultSet
对象中当前行的列,但不会更改底层数据库中的列的值。 要更新数据库中的行,需要调用以下方法之一。
序号 | 方法 | 描述 |
---|---|---|
1 | public void updateRow() |
更新数据库中当前行 |
2 | public void deleteRow() |
从数据库中删除当前行 |
3 | public void refreshRow() |
刷新结果集中的数据以反映数据库中最近的任何更改。 |
4 | public void cancelRowUpdates() |
取消对当前行所做的任何更新。 |
5 | public void insertRow() |
在数据库中插入一行。 只有当光标指向插入行时,才能调用此方法。 |
JDBC事务
如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。
对于简单的应用程序可能没有问题,但是有三个原因需要考虑是否关闭自动提交并管理自己的事务 -
- 提高性能
- 保持业务流程的完整性
- 使用分布式事务
事务能够控制何时更改提交并应用于数据库。 它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,整个事务将失败。
要启用手动事务支持,而不是使用JDBC驱动程序默认使用的自动提交模式,请调用Connection
对象的setAutoCommit()
方法。 如果将布尔的false
传递给setAutoCommit()
,则关闭自动提交。 也可以传递一个布尔值true
来重新打开它。
例如,如果有一个名为conn
的Connection
对象,请将以下代码关闭自动提交 -
conn.setAutoCommit(false);
提交和回滚
完成更改后,若要提交更改,那么可在连接对象上调用commit()
方法,如下所示:
conn.commit( );
否则,要使用连接名为conn
的数据库回滚更新,请使用以下代码 -
conn.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();
}
在这种情况下,上述INSERT
语句不会成功执行,因为所有操作都被回滚了。
使用保存点
新的JDBC 3.0新添加了Savepoint
接口提供了额外的事务控制能力。大多数现代DBMS支持其环境中的保存点,如Oracle的PL/SQL。
设置保存点(Savepoint
)时,可以在事务中定义逻辑回滚点。 如果通过保存点(Savepoint
)发生错误时,则可以使用回滚方法来撤消所有更改或仅保存保存点之后所做的更改。
Connection
对象有两种新的方法可用来管理保存点 -
- setSavepoint(String savepointName): - 定义新的保存点,它还返回一个
Savepoint
对象。 - releaseSavepoint(Savepoint savepointName): - 删除保存点。要注意,它需要一个
Savepoint
对象作为参数。 该对象通常是由setSavepoint()
方法生成的保存点。
有一个rollback (String savepointName)方法,它将使用事务回滚到指定的保存点。
以下示例说明了使用Savepoint
对象 -
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);
}
在这种情况下,上述INSERT
语句都不会成功,因为所有操作都被回滚了。
JDBC批量处理
批量处理允许将相关的SQL语句分组到批处理中,并通过对数据库的一次调用来提交它们,一次执行完成与数据库之间的交互。
一次向数据库发送多个SQL语句时,可以减少通信开销,从而提高性能。
- 不需要JDBC驱动程序来支持此功能。应该使用
DatabaseMetaData.supportsBatchUpdates()
方法来确定目标数据库是否支持批量更新处理。如果JDBC驱动程序支持此功能,该方法将返回true
。 Statement
,PreparedStatement
和CallableStatement
的addBatch()
方法用于将单个语句添加到批处理。executeBatch()
用于执行组成批量的所有语句。executeBatch()
返回一个整数数组,数组的每个元素表示相应更新语句的更新计数。- 就像将批处理语句添加到处理中一样,可以使用
clearBatch()
方法删除它们。此方法将删除所有使用addBatch()
方法添加的语句。 但是,无法指定选择某个要删除的语句。
使用Statement对象进行批处理
以下是使用Statement
对象的批处理的典型步骤序列 -
- 使用
createStatement()
方法创建Statement
对象。 - 使用
setAutoCommit()
将自动提交设置为false
。 - 使用
addBatch()
方法在创建的Statement
对象上添加SQL语句到批处理中。 - 在创建的
Statement
对象上使用executeBatch()
方法执行所有SQL语句。 - 最后,使用
commit()
方法提交所有更改。
使用PrepareStatemeng对象进行批处理
以下是使用PrepareStatement
对象进行批处理的典型步骤顺序 -
- 使用占位符创建SQL语句。
- 使用
prepareStatement()
方法创建PrepareStatement
对象。 - 使用
setAutoCommit()
将自动提交设置为false
。 - 使用
addBatch()
方法在创建的Statement
对象上添加SQL语句到批处理中。 - 在创建的
Statement
对象上使用executeBatch()
方法执行所有SQL语句。 - 最后,使用
commit()
方法提交所有更改。