jdbc連接mysql
1.JDBC簡介
JDBC:
指 Java 數據庫連接,是一種標准Java應用編程接口( JAVA API),用來連接 Java 編程語言和廣泛的數據庫。從根本上來說,JDBC 是一種規范,它提供了一套完整的接口,允許便攜式訪問到底層數據庫
常見的 JDBC 組件:
JDBC 的 API 提供了以下接口和類:
DriverManager :這個類管理一系列數據庫驅動程序。匹配連接使用通信子協議從 JAVA 應用程序中請求合適的數據庫驅動程序。識別 JDBC 下某個子協議的第一驅動程序將被用於建立數據庫連接。
Driver : 這個接口處理與數據庫服務器的通信。你將很少直接與驅動程序互動。相反,你使用 DriverManager 中的對象,它管理此類型的對象。它也抽象與驅動程序對象工作相關的詳細信息。
Connection : 此接口具有接觸數據庫的所有方法。該連接對象表示通信上下文,即,所有與數據庫的通信僅通過這個連接對象進行。
Statement : 使用創建於這個接口的對象將 SQL 語句提交到數據庫。除了執行存儲過程以外,一些派生的接口也接受參數。
ResultSet : 在你使用語句對象執行 SQL 查詢后,這些對象保存從數據獲得的數據。它作為一個迭代器,讓您可以通過它的數據來移動。
SQLException : 這個類處理發生在數據庫應用程序的任何錯誤。
JDBC 4.0 軟件包:
JDBC 4.0 主要包含 java.sql 包和 javax.sql 包,提供的主要類與數據源進行交互。
2.JDBC SQL語法
SQL語法:
結構化查詢語言(SQL)是一種標准化的語言,可以讓你對數據庫進行curd操作,如創建項目,讀取內容,更新內容和刪除項目。SQL 支持你可能會使用到的任何數據庫,它可以讓你編寫獨立於底層數據庫的數據庫代碼
創建數據庫語法:create database 數據庫名;
如:create database hejh;
刪除數據庫語法:drop database 數據庫名;
如:drop database hejh;
注意:1.創建或刪除數據庫,必須有數據庫服務器的管理員權限;
2.刪除數據庫會把存儲在該數據庫中的數據一並刪除。
創建表語法:
create table 表名(
字段名1 字段類型1,
字段名2 字段類型2,
......
);
建表示例:
create table hejh(
id int not null,
age int not null,
frist varchar(20),
last varchar(20),
primary key(id)
);
刪除表語法:drop table 表名;
如:drop table hejh;
insert語法:insert into 表名 values(字段值1,字段值2,...); --(這種方式默認插入所有字段的值)
如:insert into hejh values(1,22,'19990405','20190505');
select語法:select 字段1,字段2,... from 表名 where 查詢限制條件;
如:select * from hejh where id=1;
注意:WHERE 子句可以使用 =,!=,<,>,<=,>=,BETWEEN 和 LIKE 這些比較操作符。
update語法:updata 表名 set 字段=字段新值 where 條件;
如:update hejh set age=33 where id=1;
注意:where子句可以使用=,!=,<,>,<=,>=,between和 like 這些比較操作符。
delete語法:delete from 表名 where 條件;
如:delete from hejh where id=1;
注意:where子句可以使用=,!=,<,>,<=,>=,between 和 like 這些比較操作符
3.設置JDBC使用環境
在開始使用 JDBC 之前,必須設置 JDBC 環境。(以mysql、windows系統為例)
1.安裝jdk,配置環境變量;
2.安裝mysql數據庫;
3.安裝eclipse;
4.JDBC簡單示例
創建數據庫:hejh;
create database hejh;

創建用戶表:user表
create table user( id int(5) not null, username varchar(20) not null, gender varchar(4), primary key(id) );

初始化數據:

創建JDBC應用程序
構建一個 JDBC 應用程序包括以下六個步驟:
-
導入數據包:導入含有需要進行數據庫編程的 JDBC 類的包。大多數情況下,使用 import java.sql.就足夠了。
-
注冊 JDBC 驅動器:需要你初始化一個驅動器,以便於你打開一個與數據庫的通信通道。
-
打開連接:需要使用 DriverManager.getConnection() 方法創建一個 Connection 對象,它代表與數據庫的物理連接。
-
執行查詢:需要使用類型聲明的對象建立並提交一個 SQL 語句到數據庫。
-
提取結果數據:要求使用適當的 ResultSet.getXXX() 方法從結果集中檢索數據。
- 清理環境:依靠 JVM 的垃圾收集來關閉所有需要明確關閉的數據庫資源。
查看數據庫hejh中user表的數據有3條:

java源碼如下:
package com.hejh.day0506; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import org.junit.Test; public class JdbcTest1 { static final String driver = "com.mysql.jdbc.Driver"; static final String url = "jdbc:mysql://localhost/hejh"; static final String user = "root"; static final String password = "root"; //開啟junit局部測試 @Test public void run() { ResultSet rs=null; Statement st = null; Connection conn =null; try { //注冊驅動 Class.forName(driver); //獲取連接 conn = DriverManager.getConnection(url, user, password); //獲取sql執行對象 st = conn.createStatement(); //編寫sql語句 String sql = "select * from user"; //執行sql語句,返回一個結果集 rs = st.executeQuery(sql); //處理結果集 while(rs.next()) { int id = rs.getInt("id"); String name = rs.getString("username"); String gender = rs.getString("gender"); System.out.println("["+id+","+name+","+gender+"]"); } } catch (Exception e) { e.printStackTrace(); }finally {
//關閉資源,后開啟的先關閉 try { if(rs!=null) { rs.close(); }else { rs=null; } } catch (SQLException e) { e.printStackTrace(); } if(st!=null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } }else { st=null; } if(conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } }else { conn=null; } } } }
junit局部測試結果如下:

第一個jdbc案例完成。
4.JDBC 驅動類型
什么是 JDBC 驅動程序?
JDBC 驅動實現了 JDBC API 中定義的接口,該接口用於與數據庫服務器進行交互。例如,使用 JDBC 驅動程序可以讓你打開數據庫連接,並通過發送 SQL 或數據庫命令,然后通過 Java 接收結果。java.sql 包中附帶的 JDK,包含了定義各種類與他們的行為和實際實現,這些類都在第三方驅動程序中完成。第三方供應商在他們的數據庫驅動程序中都實現了 java.sql.Driver 接口。
JDBC驅動程序類型:
JDBC 驅動程序的實現,因為各種各樣的操作系統和 Java 運行在硬件平台的不同而不同。Sun 公司將實現類型分為四類:
1.JDBC-ODBC橋驅動程序
2.JDBC-Native API
3.JDBC-Net純Java
4.100%純Java
具體參見資料:https://www.w3cschool.cn/jdbc/j3xk1myd.html
該使用哪種驅動程序?
如果你正在訪問一個數據庫,如 Oracle,Sybase 或 IBM,首選的驅動程序是類型4。
如果你的 Java 應用程序同時訪問多個數據庫類型,類型3是首選的驅動程序。
類型2驅動程序是在你的數據庫沒有提供類型3或類型4驅動程序時使用的。
類型1驅動程序不被認為是部署級的驅動程序,它存在的目的通常僅用於開發和測試。
5.JDBC連接數據庫
編寫建立一個 JDBC 連接的程序是相當簡單的,下面是簡單的四個步驟:
-
導入 JDBC 包:在你的 Java 代碼中,用 import 語句添加你所需的類。
-
注冊 JDBC 驅動程序:這一步會導致 JVM 加載所需的驅動程序到內存中執行,因此它可以實現你的 JDBC 請求。
-
數據庫 URL 制定:這是用來創建格式正確的地址指向你想要連接的數據庫。
- 創建連接對象:最后,代碼調用 DriverManager 對象的 getConnection() 方法來建立實際的數據庫連接。
1.導入jar包
import 語句告訴 Java 編譯器在哪里可以找到你在代碼中引用的類,這些引用放置在你的源代碼起始位置。使用標准的 JDBC 包,它允許你選擇,插入,更新和刪除 SQL 表中的數據,添加以下引用到您的源代碼中。
import java.sql.*; import javax.sql.*;
2.注冊JDBC驅動程序
方法1 - Class.forName()
注冊一個驅動程序中最常用的方法是使用 Java 的Class.forName() 方法來動態加載驅動程序的類文件到內存中,它會自動將其注冊。這種方法更優越一些,因為它允許你對驅動程序的注冊信息進行配置,便於移植。
//注冊驅動
Class.forName(“com.mysql.jdbc.Driver”);//會報異常
方法2 - DriverManager.registerDriver()
你注冊一個驅動程序的第二種方法是使用靜態 staticDriverManager.registerDriver() 方法。如果你使用的是不兼容 JVM 的非 JDK,比如微軟提供的,你必須使用 registerDriver() 方法
try { Driver myDriver = new oracle.jdbc.driver.OracleDriver(); DriverManager.registerDriver( myDriver ); } catch(ClassNotFoundException ex) { System.out.println("Error: unable to load driver class!"); System.exit(1); }
3.數據庫url制定
當你加載了驅動程序之后,你可以通過 DriverManager.getConnection() 方法建立一個連接。
為方便參考,以下列出了三個加載 DriverManager.getConnection() 方法:
- getConnection(String url)
- getConnection(String url, Properties prop)
- getConnection(String url, String user, String password)
在這里,每個格式需要一個數據庫 URL ,數據庫 URL 是指向數據庫的地址。在建立一個數據連接的時候,大多數會在配置一個數據庫 URL 時遇到問題。下表列出了常用的 JDBC 驅動程序名和數據庫URL。
| RDBMS | JDBC 驅動程序名稱 | URL 格式 |
|---|---|---|
| MySQL | com.mysql.jdbc.Driver | jdbc:mysql://hostname/ databaseName |
| ORACLE | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@hostname:port Number:databaseName |
| DB2 | COM.ibm.db2.jdbc.net.DB2Driver | jdbc:db2:hostname:port Number/databaseName |
| Sybase | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds:hostname: port Number/databaseName |
URL 格式所有加粗的部分都是靜態的,你需要將剩余部分按照你的數據庫實際情況進行設置。
4.創建連接對象
調用適當的用戶名和密碼以及 getConnection() 方法來獲得一個 Connection 對象
static final String url = "jdbc:mysql://localhost/hejh";
static final String user = "root";
static final String password = "root";
Connection conn = DriverManager.getConnection(url, user, password);
使用數據庫 URL 和 Properties 對象:
第三種 DriverManager.getConnection() 方法調用需要數據庫 URL 和 Properties 對象-
DriverManager.getConnection(String url, Properties info);
Properties 對象保存了一組關鍵數值。它通過調用 getConnection() 方法,將驅動程序屬性傳遞給驅動程序。使用下面的代碼可以建立與上述示例相同的連接:
import java.util.*; String URL = "jdbc:mysql://localhost/hejh"; Properties info = new Properties( ); info.put( "user", "username" ); info.put( "password", "password" ); Connection conn = DriverManager.getConnection(URL, info);
5.關閉 JDBC 連接
在 JDBC 程序的末尾,必須明確關閉所有連接到數據庫的連接,以結束每個數據庫會話。但是,如果忘了,Java 垃圾收集器也會關閉連接,它會完全清除過期的對象。依托垃圾收集器,特別是在數據庫編程,是非常差的編程習慣。你應該養成用 close()方法關閉連接對象的習慣。為了確保連接被關閉,你可以在代碼中的 'finally' 程序塊中執行。 無論異常是否發生,finally 程序是肯定會被執行的。要關閉上面打開的連接,應該調用 close()方法,如下所示:
finally { try { if(rs!=null) { rs.close(); }else { rs=null; } } catch (SQLException e) { e.printStackTrace(); } if(st!=null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } }else { st=null; } if(conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } }else { conn=null; } }
6.JDBC Statement 對象
1.Statement 對象
獲得了數據庫的連接,就可以和數據庫進行交互了。JDBC 的 Statement,CallableStatement 和 PreparedStatement 接口定義的方法和屬性,可以發送 SQL 命令或 PL/SQL 命令到數據庫,並從數據庫接收數據。在數據庫中,它們還定義了幫助 Java 和 SQL 數據類型之間轉換數據差異的方法。下表提供了每個接口的用途概要,根據實際目的決定使用哪個接口。
| 接口 | 推薦使用 |
|---|---|
| Statement | 可以正常訪問數據庫,適用於運行靜態 SQL 語句。 Statement 接口不接受參數。 |
| PreparedStatement | 計划多次使用 SQL 語句, PreparedStatement 接口運行時接受輸入的參數。 |
| CallableStatement | 適用於當你要訪問數據庫存儲過程的時候, CallableStatement 接口運行時也接受輸入的參數。 |
創建 Statement 對象:
使用 Statement 對象執行 SQL 語句之前,需要用 Connection 對象創建一個 Statement()對象,如下面的示例所示:
Statement stmt = null; try { stmt = conn.createStatement( ); . . . } catch (SQLException e) { . . . } finally { . . . }
當創建了一個 Statement 對象之后,可以用它的三個執行方法的任一方法來執行 SQL 語句:
-
boolean execute(String SQL) : 如果 ResultSet 對象可以被檢索,則返回的布爾值為 true ,否則返回 false 。當你需要使用真正的動態 SQL 時,可以使用這個方法來執行 SQL DDL 語句。
-
int executeUpdate(String SQL) : 返回執行 SQL 語句影響的行的數目。使用該方法來執行 SQL 語句,是希望得到一些受影響的行的數目,例如,INSERT,UPDATE 或 DELETE 語句。
- ResultSet executeQuery(String SQL) : 返回一個 ResultSet 對象。當你希望得到一個結果集時使用該方法,就像你使用一個 SELECT 語句。
關閉 Statement 對象:
正如關閉一個 Connection 對象來節約數據庫資源,出於同樣的原因你也應該關閉 Statement 對象。簡單的調用 close() 方法就可以完成這項工作。如果關閉了 Connection 對象,那么它也會關閉 Statement 對象。然而,你應該始終明確關閉 Statement 對象,以確保真正的清除。
Statement stmt = null; try { stmt = conn.createStatement( ); . . . } catch (SQLException e) { . . . } finally { stmt.close();//會報異常 }
PreparedStatement 對象:
PreparedStatement 接口擴展了 Statement 接口,它讓你用一個常用的 Statement 對象增加幾個高級功能。這個 statement 對象可以提供靈活多變的動態參數。創建 PreparedStatement 對象:
PreparedStatement pstmt = null; try { String SQL = "update user set age = ? where id = ?"; pstmt = conn.prepareStatement(SQL);
psmt.setInt(1,22);
psmt.setInt(2,1); . . . } catch (SQLException e) { . . . } finally { . . . }
JDBC 中所有的參數都被用 ? 符號表示,這是已知的參數標記。在執行 SQL 語句之前,你必須賦予每一個參數確切的數值。setXXX() 方法將值綁定到參數,其中 XXX 表示你希望綁定到輸入參數的 Java 數據類型。如果你忘了賦予值,你將收到一個 SQLException。每個參數標記映射它的序號位置。第一標記表示位置 1 ,下一個位置為 2 等等。這種方法不同於 Java 數組索引是從 0 開始的。所有的 Statement對象 的方法都與數據庫交互,(a) execute(),(b) executeQuery(),及 (c) executeUpdate() 也能被 PreparedStatement 對象引用。然而,這些方法被 SQL 語句修改后是可以輸入參數的。
關閉 PreparedStatement 對象
關閉 PreparedStatement 對象,簡單的調用 close() 方法可以完成這項工作。如果你關閉了 Connection 對象,那么它也會關閉 PreparedStatement 對象。然而,你應該始終明確關閉 PreparedStatement 對象,以確保真正的清除。
PreparedStatement pstmt = null; try { String SQL = "Update Employees SET age = ? WHERE id = ?"; pstmt = conn.prepareStatement(SQL); . . . } catch (SQLException e) { . . . } finally { pstmt.close();//會報異常
}
CallableStatement 對象:
正如一個 Connection 對象可以創建 Statement 對象和 PreparedStatement 對象,它也可以創建被用來執行調用數據庫存儲過程的 CallableStatement 對象。
在 MySQL 的 EMP 數據庫中創建相同的存儲過程:
DELIMITER $$ DROP PROCEDURE IF EXISTS `EMP`.`getEmpName` $$ CREATE PROCEDURE `EMP`.`getEmpName` (IN EMP_ID INT, OUT EMP_FIRST VARCHAR(255)) BEGIN SELECT first INTO EMP_FIRST FROM Employees WHERE ID = EMP_ID; END $$ DELIMITER ;
三種類型的參數有:IN,OUT 和 INOUT。PreparedStatement 對象只使用 IN 參數。CallableStatement 對象可以使用所有的三個參數。這里是每個參數的定義:
| 參數 | 描述 |
|---|---|
| IN | 在 SQL 語句創建的時候該參數是未知的。你可以用 setXXX() 方法將值綁定到IN參數中。 |
| OUT | 該參數由 SQL 語句的返回值提供。你可以用 getXXX() 方法獲取 OUT 參數的值。 |
| INOUT | 該參數同時提供輸入輸出的值。你可以用 setXXX() 方法將值綁定參數,並且用 getXXX() 方法獲取值。 |
下面的代碼片段展示了基於存儲過程如何使用 Connection.prepareCall() 方法來實例化 CallableStatement 對象。
CallableStatement cstmt = null; try { String SQL = "{call getEmpName (?, ?)}"; cstmt = conn.prepareCall (SQL); . . . } catch (SQLException e) { . . . } finally { . . . }
7.結果集
結果集:
SQL 語句從數據庫查詢中獲取數據,並將數據返回到結果集中。SELECT 語句是一種標准的方法,它從一個數據庫中選擇行記錄,並顯示在一個結果集中。 java.sql.ResultSet 接口表示一個數據庫查詢的結果集。一個 ResultSet 對象控制一個光標指向當前行的結果集。術語“結果集”是指包含在 ResultSet 對象中的行和列的數據。
ResultSet 接口的方法可細分為三類:
- 導航方法:用於移動光標。
- 獲取方法:用於查看當前行被光標所指向的列中的數據。
- 更新方法:用於更新當前行的列中的數據。這些更新也會更新數據庫中的數據。
光標的移動基於 ResultSet 的屬性。用相應的語句生成 ResultSet 對象時,同時生成 ResultSet 的屬性。JDBC 提供了連接方法通過下列創建語句來生成你所需的 ResultSet 對象:
- createStatement(int RSType, int RSConcurrency);
- prepareStatement(String SQL, int RSType, int RSConcurrency);
- prepareCall(String sql, int RSType, int RSConcurrency);
第一個參數表示 ResultSet 對象的類型,第二個參數是兩個 ResultSet 常量之一,該常量用於判斷該結果集是只讀的還是可修改的
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 | 創建一個可修改的結果集。 |
示例可以如下所示,初始化一個 Statement 對象,來創建一個只能前進而且只讀的 ResultSet 對象:
try { Statement stmt = conn.createStatement( ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); } catch(Exception ex) { .... } finally { .... }
導航結果集:
在 ResultSet 接口中包括如下幾種方法涉及移動光標-
| S.N. | 方法 & 描述 |
|---|---|
| 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 將光標移動到指定的第 row 行。 |
| 6 | public boolean relative(int row) throws SQLException 將光標移動到當前指向的位置往前或往后第 row 行的位置。 |
| 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()方法-
| S.N. | 方法 & 描述 |
|---|---|
| 1 | public int getInt(String columnName) throws SQLException 返回當前行中名為 columnName 的列的 int 值。 |
| 2 | public int getInt(int columnIndex) throws SQLException 返回當前行中指定列的索引的 int 值。列索引從 1 開始,意味着行中的第一列是 1 ,第二列是 2 ,以此類推。 |
同樣的,在 ResultSet 接口中還有獲取八個 Java 原始類型的 get 方法,以及常見的類型,比如 java.lang.String,java.lang.Object 和java.net.URL。
也有用於獲取 SQL 數據類型 java.sql.Date, java.sql.Time, java.sql.Timestamp, java.sql.Clob,java.sql.Blob 中的方法。查看文檔可以了解使用這些 SQL 數據類型的更多的信息。
更新的結果集:
ResultSet 接口包含了一系列的更新方法,該方法用於更新結果集中的數據。用 get 方法可以有兩個更新方法來更新任一數據類型:
- 一個需要列名。
- 一個需要列的索引。
例如,要更新一個結果集的當前行的 String 列,你可以使用任一如下所示的 updateString()方法-
| S.N. | 方法 & 描述 |
|---|---|
| 1 | public void updateString(int columnIndex, String s) throws SQLException 將指定列的字符串的值改為 s。 |
| 2 | public void updateString(String columnName, String s) throws SQLException 類似於前面的方法,不同之處在於指定的列是用名字來指定的,而不是它的索引。 |
八個原始數據類型都有其更新方法,比如 String,Object,URL,和在 java.sql 包中的 SQL 數據類型。
更新結果集中的行將改變當前行的列中的 ResultSet 對象,而不是基礎數據庫中的數據。要更新數據庫中一行的數據,你需要調用以下的任一方法:
| S.N. | 方法 & 描述 |
|---|---|
| 1 | public void updateRow() 通過更新數據庫中相對應的行來更新當前行。 |
| 2 | public void deleteRow() 從數據庫中刪除當前行。 |
| 3 | public void refreshRow() 在結果集中刷新數據,以反映數據庫中最新的數據變化。 |
| 4 | public void cancelRowUpdates() 取消對當前行的任何修改。 |
| 5 | public void insertRow() 在數據庫中插入一行。本方法只有在光標指向插入行的時候才能被調用。 |
8.JDBC數據類型
JDBC 驅動程序在將 Java 數據類型數據發送到數據庫之前,會將其轉換為相應的 JDBC 類型數據,對於大多數數據類型都采用了默認的映射關系。例如,一個 Java int 數據類型轉換為 SQL INTEGER。通過默認的映射關系來提供驅動程序之間的一致性。當你調用 PreparedStatement 中的 setXXX()方法或 CallableStatement 對象或 ResultSet.updateXXX()方法時, Java 數據類型會轉換為默認的 JDBC 數據類型,如下表概述。
| SQL | JDBC/Java | setXXX | updateXXX |
|---|---|---|---|
| VARCHAR | java.lang.String | setString | updateString |
| CHAR | java.lang.String | setString | updateString |
| LONGVARCHAR | java.lang.String | setString | updateString |
| BIT | boolean | setBoolean | updateBoolean |
| NUMERIC | java.math.BigDecimal | setBigDecimal | updateBigDecimal |
| TINYINT | byte | setByte | updateByte |
| SMALLINT | short | setShort | updateShort |
| INTEGER | int | setInt | updateInt |
| BIGINT | long | setLong | updateLong |
| REAL | float | setFloat | updateFloat |
| FLOAT | float | setFloat | updateFloat |
| DOUBLE | double | setDouble | updateDouble |
| VARBINARY | byte[ ] | setBytes | updateBytes |
| BINARY | byte[ ] | setBytes | updateBytes |
| DATE | java.sql.Date | setDate | updateDate |
| TIME | java.sql.Time | setTime | updateTime |
| TIMESTAMP | java.sql.Timestamp | setTimestamp | updateTimestamp |
| CLOB | java.sql.Clob | setClob | updateClob |
| BLOB | java.sql.Blob | setBlob | updateBlob |
| ARRAY | java.sql.Array | setARRAY | updateARRAY |
| REF | java.sql.Ref | SetRef | updateRef |
| STRUCT | java.sql.Struct | SetStruct | updateStruct |
JDBC 3.0 增強了對 BLOB,CLOB,ARRAY 和 REF 數據類型的支持。 ResultSet 對象現在有 UPDATEBLOB(),updateCLOB(), updateArray(),和 updateRef()方法,通過這些方法你可以直接操作服務器上的相應數據。
你能用 setXXX()方法和 updateXXX()方法將 Java 類型轉換為特定的 JDBC 數據類型。你能用 setObject()方法和 updateObject()方法將絕大部分的 Java 類型映射到 JDBC 數據類型。
ResultSet 對象為任一數據類型提供相應的 getXXX()方法,該方法可以獲取任一數據類型的列值。上述任一方法的使用需要列名或它的順序位置。
| SQL | JDBC/Java | setXXX | getXXX |
|---|---|---|---|
| VARCHAR | java.lang.String | setString | getString |
| CHAR | java.lang.String | setString | getString |
| LONGVARCHAR | java.lang.String | setString | getString |
| BIT | boolean | setBoolean | getBoolean |
| NUMERIC | java.math.BigDecimal | setBigDecimal | getBigDecimal |
| TINYINT | byte | setByte | getByte |
| SMALLINT | short | setShort | getShort |
| INTEGER | int | setInt | getInt |
| BIGINT | long | setLong | getLong |
| REAL | float | setFloat | getFloat |
| FLOAT | float | setFloat | getFloat |
| DOUBLE | double | setDouble | getDouble |
| VARBINARY | byte[ ] | setBytes | getBytes |
| BINARY | byte[ ] | setBytes | getBytes |
| DATE | java.sql.Date | setDate | getDate |
| TIME | java.sql.Time | setTime | getTime |
| TIMESTAMP | java.sql.Timestamp | setTimestamp | getTimestamp |
| CLOB | java.sql.Clob | setClob | getClob |
| BLOB | java.sql.Blob | setBlob | getBlob |
| ARRAY | java.sql.Array | setARRAY | getARRAY |
| REF | java.sql.Ref | SetRef | getRef |
| STRUCT | java.sql.Struct | SetStruct | getStruct |
日期和時間數據類型:
java.sql.Date 類映射 SQL DATE 類型,java.sql.Time 類和 java.sql.Timestamp 類也分別映射 SQL TIME 數據類型和 SQL TIMESTAMP 數據類型。以下示例顯示了日期和時間類如何轉換成標准的 Java 日期和時間值,並匹配成 SQL 數據類型所要求的格式。
package com.hejh.day0507; import java.util.Date; public class TimeTest { public static void main(String[] args) { //java util 打印當前具體時間
Date utilDate = new Date(); long utilTime = utilDate.getTime(); System.out.println("java util date:"+utilDate.toString());//java util date:Tue May 07 17:14:53 CST 2019 //sql 打印當前時間(某年-某月-某日)
java.sql.Date sqlDate = new java.sql.Date(utilTime); System.out.println("java sql date:"+sqlDate.toString());//java sql date:2019-05-07 //sql 打印當前時間(時:分:秒)
java.sql.Time sqlTime = new java.sql.Time(utilTime); System.out.println("java sql time:"+sqlTime.toString());//java sql time:17:34:33 //sql 打印當前具體時間(某年-某月-某日 時:分:秒)
java.sql.Timestamp sqlTimeStamp = new java.sql.Timestamp(utilTime); System.out.println("java sql Timestamp:"+sqlTimeStamp.toString());//java sql Timestamp:2019-05-07 17:36:44.176
} }
處理 NULL 值:
SQL 使用 NULL 值和 Java 使用 null 是不同的概念。那么,你可以使用三種策略來處理 Java 中的 SQL NULL 值:
- 避免使用返回原始數據類型的 getXXX()方法。
- 使用包裝類的基本數據類型,並使用 ResultSet 對象的 wasNull()方法來測試收到 getXXX()方法返回的值是否為 null,如果是 null,該包裝類變量則被設置為 null。
- 使用原始數據類型和 ResultSet 對象的 wasNull()方法來測試通過 getXXX()方法返回的值,如果是 null,則原始變量應設置為可接受的值來代表 NULL。
下面是一個處理 NULL 值的示例:
Statement stmt = conn.createStatement( ); String sql = "SELECT id, first, last, age FROM Employees"; ResultSet rs = stmt.executeQuery(sql); int id = rs.getInt(1); if( rs.wasNull( ) ) { id = 0; }
9.JDBC事務
事務:
如果 JDBC 連接是處於自動提交模式下,該模式為默認模式,那么每句 SQL 語句都是在其完成時提交到數據庫。對簡單的應用程序來說這種模式相當好,但有三個原因你可能想關閉自動提交模式,並管理你自己的事務:
- 為了提高性能
- 為了保持業務流程的完整性
- 使用分布式事務
你可以通過事務在任意時間來控制以及更改應用到數據庫。它把單個 SQL 語句或一組 SQL 語句作為一個邏輯單元,如果其中任一語句失敗,則整個事務失敗。
若要啟用手動事務模式來代替 JDBC 驅動程序默認使用的自動提交模式的話,使用 Connection 對象的的 setAutoCommit()方法。如果傳遞一個布爾值 false 到 setAutoCommit()方法,你就關閉自動提交模式。你也可以傳遞一個布爾值 true 將其再次打開。例如,如果有一個名為 conn 的 Connection 對象,以下的代碼將關閉自動提交模式:
conn.setAutoCommit(false);//關閉自動提交模式
提交和回滾:
當完成修改,並且要提交修改,可以在 connection 對象里調用 commit()方法,如下所示:
conn.commit( );
在默認情況下,事務自動提交,源碼和數據庫數據如下:
package com.hejh.day0508; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import org.junit.Test; public class AffairTest { static final String driver = "com.mysql.jdbc.Driver"; static String url = "jdbc:mysql://localhost/hejh"; static final String username = "root"; static final String password = "root"; @Test public void run() { Connection conn = null; PreparedStatement ps = null; try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); String sql = "update user set username = ? where id = ? "; ps = conn.prepareStatement(sql); ps.setString(1,"hejh11"); ps.setInt(2, 1); int i = ps.executeUpdate(); System.out.println("影響了:"+i+"行"); //影響了:1行
} catch (Exception e) { e.printStackTrace(); }finally { try { if(ps!=null) { ps.close(); }else { ps=null; } } catch (Exception e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn=null; } } catch (SQLException e) { e.printStackTrace(); } }//finally
} }

設置為手動提交,運行下面的源碼(替換try-catch部分代碼),數據庫數據刷新結果如下:
try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); String sql = "update user set username = ? where id = ? "; //在執行sql之前,設置事務為手動提交
conn.setAutoCommit(false);//手動提交事務,數據不會發生變化
ps = conn.prepareStatement(sql); ps.setString(1,"hejh111"); ps.setInt(2, 1); int i = ps.executeUpdate(); System.out.println("影響了:"+i+"行"); //影響了:1行
} catch (Exception e) { e.printStackTrace(); }

設置為手動提交,並且在sql執行后,手動提交一下事務,運行下面的源碼(替換try-catch部分代碼),數據庫數據刷新結果如下:
try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); String sql = "update user set username = ? where id = ? "; //在執行sql之前,設置事務為手動提交
conn.setAutoCommit(false);//手動提交事務,數據不會發生變化
ps = conn.prepareStatement(sql); ps.setString(1,"hejh111"); ps.setInt(2, 1); int i = ps.executeUpdate(); //sql語句執行后,手動提交事務,使sql生效
conn.commit(); System.out.println("影響了:"+i+"行"); //影響了:1行
} catch (Exception e) { e.printStackTrace(); }

另外,用名為 conn 的連接回滾數據到數據庫,
conn.rollback( );//可以放在catch塊中
還原點:
新的 JDBC 3.0 還原點接口提供了額外的事務控制。大部分現代的數據庫管理系統的環境都支持設定還原點,例如 Oracle 的 PL/SQL。當你在事務中設置一個還原點來定義一個邏輯回滾點。如果在一個還原點之后發生錯誤,那么可以使用 rollback 方法來撤消該還原點之后所做的修改。
Connection 對象有兩個新的方法來管理還原點:
-
setSavepoint(String savepointName): 定義了一個新的還原點,返回一個 Savepoint 對象。
- releaseSavepoint(Savepoint savepointName): 刪除一個還原點。請注意,參數是一個 Savepoint 對象,這個對象通常是由 setSavepoint() 方法生成的一個還原點。
有一個 rollback (String savepointName) 方法,該方法可以回滾到指定的還原點。下面的例子說明了如何使用 Savepoint 對象:
package com.hejh.day0508;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Savepoint;
import org.junit.Test;
public class SavePointTest {
@Test
public void save() throws SQLException {
Connection conn = null;
PreparedStatement ps =null;
Savepoint beforeInsert = null;
try {
Class.forName("com.mysql.jdbc.Driver");
//prepareStatement插入的中文參數有可能會出現問號,可以在這里指定一下字符編碼為utf-8
conn = DriverManager.getConnection("jdbc:mysql://localhost/hejh?useUnicode=true&characterEncoding=UTF-8",
"root", "root");
//設置一個還原點
beforeInsert = conn.setSavepoint();
String sql = "insert into user values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1,4);
ps.setString(2, "hejh222");
ps.setString(3, "男");
int i = ps.executeUpdate();
System.out.println("插入了:"+i+"行數據");
} catch (Exception e) {
//回滾到還原點BeforeInsert處
conn.rollback(beforeInsert);
e.printStackTrace();
}finally {
try {
if(ps!=null) {
ps.close();
}else {
ps = null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(beforeInsert!=null) {
conn.releaseSavepoint(beforeInsert);
}else {
beforeInsert=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn!=null) {
conn.close();
}else {
conn=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
10.JDBC異常
異常:
異常處理可以允許你處理一個異常情況,例如可控方式的程序定義錯誤。當異常情況發生時,將拋出一個異常。拋出這個詞意味着當前執行的程序停止,控制器被重定向到最近的適用的 catch 子句。如果沒有適用的 catch 子句存在,那么程序執行被終止。JDBC 的異常處理是非常類似於 Java 的異常處理,但對於 JDBC,最常見的異常是 java.sql.SQLException。
SQLException:
SQLException 異常在驅動程序和數據庫中都可能出現。當出現這個異常時,SQLException 類型的對象將被傳遞到 catch 子句。傳遞的 SQLException 對象具有以下的方法,以下的方法可用於檢索該異常的額外信息:
| 方法 | 描述 |
|---|---|
| getErrorCode( ) | 獲取與異常關聯的錯誤號。 |
| getMessage( ) | 獲取 JDBC 驅動程序的錯誤信息,該錯誤是由驅動程序處理的,或者在數據庫錯誤中獲取 Oracl 錯誤號和錯誤信息。 |
| getSQLState( ) | 獲取 XOPEN SQLstate 字符串。對於 JDBC 驅動程序錯誤,使用該方法不能返回有用的信息。對於數據庫錯誤,返回第五位的 XOPEN SQLstate 代碼。該方法可以返回 null。 |
| getNextException( ) | 獲取異常鏈的下一個 Exception 對象。 |
| printStackTrace( ) | 打印當前異常或者拋出,其回溯到標准的流錯誤。 |
| printStackTrace(PrintStream s) | 打印該拋出,其回溯到你指定的打印流。 |
| printStackTrace(PrintWriter w) | 打印該拋出,其回溯到你指定的打印寫入。 |
通過利用可從 Exception 對象提供的信息,你可以捕獲異常並繼續運行程序。這是一個 try 塊的一般格式:
try { // 可能出現異常的代碼
} catch(Exception ex) { //異常的捕捉和處理
} finally {// 必須執行的操作,想數據庫的資源關閉,
}
package com.hejh.day0508; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Savepoint; import org.junit.Test; public class ExceptionTest { @Test public void save() throws SQLException { Connection conn = null; PreparedStatement ps =null; Savepoint beforeInsert = null; try { Class.forName("com.mysql.jdbc.Driver"); //1.數據庫用戶名和密碼不符(root,hejh),實際用戶名密碼為(root,root)
conn = DriverManager.getConnection("jdbc:mysql://localhost/hejh?useUnicode=true&characterEncoding=UTF-8", "root", "hejh"); String sql = "insert into user values(?,?,?)"; ps = conn.prepareStatement(sql); ps.setInt(1,4); ps.setString(2, "hejh200"); ps.setString(3, "男"); int i = ps.executeUpdate(); System.out.println("插入了:"+i+"行數據"); } catch (Exception e) { //2.在這里捕獲異常,並將異常信息打印到控制台
e.printStackTrace(); }finally { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } try { if(beforeInsert!=null) { conn.releaseSavepoint(beforeInsert); }else { beforeInsert=null; } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn=null; } } catch (SQLException e) { e.printStackTrace(); } } } }
出現以下異常:
java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)
......
11.JDBC批處理
批處理:
批處理是指你將關聯的 SQL 語句組合成一個批處理,並將他們當成一個調用提交給數據庫。當你一次發送多個 SQL 語句到數據庫時,可以減少通信的資源消耗,從而提高了性能。
-
JDBC 驅動程序不一定支持該功能。你可以使用 DatabaseMetaData.supportsBatchUpdates() 方法來確定目標數據庫是否支持批處理更新。如果你的JDBC驅動程序支持此功能,則該方法返回值為 true。
-
Statement,PreparedStatement 和 CallableStatement 的 addBatch() 方法用於添加單個語句到批處理。
-
executeBatch() 方法用於啟動執行所有組合在一起的語句。
-
executeBatch() 方法返回一個整數數組,數組中的每個元素代表了各自的更新語句的更新數目。
- 正如你可以添加語句到批處理中,你也可以用 clearBatch() 方法刪除它們。此方法刪除所有用 addBatch() 方法添加的語句。但是,你不能有選擇性地選擇要刪除的語句。
Statement 對象執行批處理:
使用 Statement 對象來使用批處理所需要的典型步驟如下所示-
- 使用 createStatement() 方法創建一個 Statement 對象。
- 使用 setAutoCommit() 方法將自動提交設為 false。
- 被創建的 Statement 對象可以使用 addBatch() 方法來添加你想要的所有SQL語句。
- 被創建的 Statement 對象可以用 executeBatch() 將所有的 SQL 語句執行。
- 最后,使用 commit() 方法提交所有的更改。
批量執行3條insert語句,示例如下:
package com.hejh.day0508; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import org.junit.Test; import org.omg.Messaging.SyncScopeHelper; //批處理
public class Batching { @Test public void test() { Connection conn = null; Statement st = null; try { //注冊驅動
Class.forName("com.mysql.jdbc.Driver"); //獲取鏈接
conn = DriverManager.getConnection("jdbc:mysql://localhost/hejh?useUnicode=true&characterEncoding=UTF-8", "root", "root"); //設置為:手動提交事務
conn.setAutoCommit(false); //編寫3個insert sql語句
String sql1 = "insert into user values(4,'sss','女')"; String sql2 = "insert into user values(5,'www','男')"; String sql3 = "insert into user values(6,'yyy','女')"; //獲取statement對象
st = conn.createStatement(); //添加sql語句到批處理
st.addBatch(sql1); st.addBatch(sql2); st.addBatch(sql3); //執行批處理,返回一個整數數組,數組中的每個元素代表了各自的更新語句的更新數目
int[] arr = st.executeBatch(); //事務提交,使sql語句生效
conn.commit(); //處理數組
System.out.print("影響行數為:"); for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+" ");//影響行數為:1 1 1
} } catch (Exception e) { e.printStackTrace(); }finally { try { if(st!=null) { st.close(); }else { st = null; } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } } }

批處理和 PrepareStatement 對象:
使用 prepareStatement 對象來使用批處理需要的典型步驟如下所示:
- 使用占位符創建 SQL 語句。
- 使用任一 prepareStatement() 方法創建 prepareStatement 對象。
- 使用 setAutoCommit() 方法將自動提交設為 false。
- 被創建的 Statement 對象可以使用 addBatch() 方法來添加你想要的所有 SQL 語句。
- 被創建的 Statement 對象可以用 executeBatch() 將所有的 SQL 語句執行。
- 最后,使用 commit() 方法提交所有的更改。
示例如下:
package com.hejh.day0508; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import org.junit.Test; public class PrepareStatementBatching { @Test public void test() { Connection conn = null; PreparedStatement ps = null; try { //注冊驅動
Class.forName("com.mysql.jdbc.Driver"); //獲取鏈接
conn = DriverManager.getConnection("jdbc:mysql://localhost/hejh?useUnicode=true&characterEncoding=UTF-8", "root", "root"); //設置為:手動提交事務
conn.setAutoCommit(false); //編寫sql語句
String sql = "update user set username=? where id=?"; //獲取statement對象
ps = conn.prepareStatement(sql); //添加第一個sql語句到批處理
ps.setString(1, "s"); ps.setInt(2, 4); ps.addBatch(); //添加第二個sql語句到批處理
ps.setString(1, "w"); ps.setInt(2, 5); ps.addBatch(); //添加第三個sql語句到批處理
ps.setString(1, "y"); ps.setInt(2, 6); ps.addBatch(); //執行批處理,返回一個整數數組,數組中的每個元素代表了各自的更新語句的更新數目
int []arr = ps.executeBatch(); //提交事務,使sql語句生效
conn.commit(); //處理數組
System.out.print("分別影響了:"); for(int i=0;i<arr.length;i++) { System.out.print(arr[i]+" "); //分別影響了:1行 1行 1行 } } catch (Exception e) { e.printStackTrace(); }finally { try { if(ps!=null) { ps.close(); }else { ps = null; } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null) { conn.close(); }else { conn = null; } } catch (SQLException e) { e.printStackTrace(); } } } }

12.JDBC流數據
流數據:
PreparedStatement 對象必須具備使用輸入和輸出流來提供參數數據的能力。這使你能夠將整個文件存儲到數據庫列中,這樣數據庫就能存儲大型數據,例如 CLOB 和 BLOB 數據類型。
用於流數據有下列幾種方法:
- setAsciiStream(): 該方法是用來提供較大的 ASCII 值。
- setCharacterStream(): 該方法是用來提供較大的 UNICODE 值。
- setBinaryStream(): 該方法是用來提供較大的二進制值。
setXXXStream()方法需要一個額外的參數,該參數是除了參數占位符的文件大小。這個參數通知驅動程序通過使用流有多少數據被發送到數據庫中。
假如我們到要上傳一個名為 hejh.xml 的 XML 文件到數據庫的表中。下面是該 XML 文件的內容
<?xml version="1.0"?>
<hejh>
<id>1</id>
<username>hjh</username>
<gender>男</gender>
</hejh>
將該 xml_data.xml文件和要運行的示例保存在相同的目錄的。這個示例將創建一個數據庫表 xml_data ,然后 xml_data.xml 將被上傳到該表中。
將下面的示例拷貝並粘帖到 JDBCStreamingDate.java 中,編譯並運行它,如下所示:
package com.hejh.day0509; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class JDBCStreamingDate { public static void main(String[] args)throws SQLException, IOException { Connection conn = null; Statement st = null; PreparedStatement pps = null; ResultSet rs = null; try { //注冊驅動
Class.forName("com.mysql.jdbc.Driver"); //獲取連接
conn = DriverManager.getConnection("jdbc:mysql://localhost/hejh?useUnicode=true&characterEncoding=UTF-8", "root", "root"); //獲取 Statement 對象,來創建一張xml_data表
st = conn.createStatement(); createXMLTable(st); //將xml_data.xml文件讀取進來
File file = new File("src/xml_data.xml"); long fileLength = file.length(); FileInputStream fis = new FileInputStream(file); //獲取 對象,給表插入數據,並執行sql
String sql = "insert into xml_data values(?,?)"; pps = conn.prepareStatement(sql); pps.setInt(1, 100); pps.setAsciiStream(2, fis,(int)fileLength ); pps.execute(); //關閉輸入流
fis.close(); String selectSql = "select data from xml_data where id =100"; rs = st.executeQuery(selectSql); InputStream is = null; ByteArrayOutputStream bos = null; if(rs.next()) { is = rs.getAsciiStream(1); int c ; //獲取輸出流,將帶數據的hejh.xml文件輸出
bos = new ByteArrayOutputStream(); while((c=is.read())!=-1) { bos.write(c); } } System.out.println(bos.toString()); } catch (ClassNotFoundException e) { e.printStackTrace(); }finally {//關閉資源
if(rs!=null) { rs.close(); }else { rs = null; } if(pps!=null) { pps.close(); }else { pps = null; } if(st!=null) { st.close(); }else { st = null; } if(conn!=null) { conn.close(); }else { conn = null; } } } //創建一張表,表名xml_data
public static void createXMLTable(Statement st) throws SQLException { //sql語句,創建一張表xml_data
String createTable = "create table xml_data(id int,data long)"; //刪除表xml_data
String dropTable = "drop table xml_data"; //在建表前,應該先刪除表,如果存在相同的表名的話
try { st.execute(dropTable); } catch (SQLException e) { e.printStackTrace(); } //創建表
st.executeUpdate(createTable); } }
console輸出為:
<?xml version="1.0" encoding="UTF-8"?>
<hejh>
<id>1</id>
<username>hjh</username>
<gender>男</gender>
</hejh>
數據庫表現為:

本博文參考w3cschool網JDBC指南一文,附上網址:https://www.w3cschool.cn/jdbc/bgqu1my6.html
