Java的JDBC操作
1.JDBC入門
1.1.什么是JDBC
JDBC從物理結構上來說就是java語言訪問數據庫的一套接口集合,本質上是java語言根數據庫之間的協議。JDBC提供一組類和接口,通過使用JDBC,開發人員可以使用java代碼發送sql語句,來操作數據庫
1.2.使用JDBC發送SQL的前提
登錄數據庫服務器(連接數據庫服務器)需要有以下幾項:
- 數據庫的IP地址
- 端口
- 數據庫用戶名
- 密碼 java連接數據庫代碼示例:
/**
* 連接數據庫的三種方式
*/
public class dbConnect {
//連接數據庫的URL,
// JDBC協議:數據庫子協議://主機:端口/連接的數據庫
private String url="jdbc:mysql://localhost:3306/db";
private String user="root";//用戶名
private String password = "root";//密碼
/**
* 1,連接數據庫方法1:
* @throws SQLException
*/
@Test
public void connect1() throws SQLException {
//1.連接的創建驅動程序類對象
//com.mysql.jdbc.Driver()是mysql連接java的驅動,由mysql提供
Driver driver=new com.mysql.jdbc.Driver();//新版本寫發,推薦
// Driver driver = new org.gjt.mm.mysql.Driver();//舊版本寫法
//設置用戶名和密碼
Properties props = new Properties();
props.setProperty("user",user);
props.setProperty("password",password);
//2.連接數據庫,返回連接對象
Connection conn = driver.connect(url,props);
System.out.println(conn);
}
/**
* 2.連接數據庫方法2:
* 使用驅動管理器來連接數據庫
*/
@Test
public void connect2() throws SQLException {
Driver driver = new com.mysql.jdbc.Driver();
//1.注冊驅動程序(可以注冊多個驅動程序)
//下面一行的代碼在加載類的時候已經被執行了,不需要再寫了
// DriverManager.registerDriver(driver);
//2.連接到數據庫
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
/**
* 3.連接數據庫方法3:
* 推薦使用此方法來連接數據庫
*/
@Test
public void connect3() throws ClassNotFoundException, SQLException {
//通過字節碼對象的方法加載靜態代碼塊,從而注冊驅動程序
Class.forName("com.mysql.jdbc.Driver");
//連接到具體的數據庫
Connection conn = DriverManager.getConnection(url,user,password);
//打印判斷是否連接成功
System.out.println(conn);
}
}
1.3.JDBC接口的核心API
JDBC的接口主要來源於舊版的java.sql.和新版的javax.sql. **Drive接口:**表示java驅動程序接口。所有具體的數據庫廠商要實現此接口
connect(url,properties):連接數據庫的方法
- url語法:JDBC協議:數據庫子協議://主機:端口/數據庫
- user:數據庫的用戶名
- password:數據庫用戶密碼
DriveManager類:驅動管理器類,用於管理所有注冊的驅動程序
registerDriver(driver):注冊驅動類對象 Connection getConnection(url,user,password);獲取連接對象
**Connection接口:**表示java程序和數據庫的連接對象。
Statement createStatement();創建Statement對象 PreparedStatement prepareStatement(String sql);創建PreparedStatement對象 CallableStatement prepareCall(String sql);創建CallableStatement對象
**Statement接口:**用於執行靜態sql語句
int executeUpdate(String sql);執行靜態的更新sql語句 ResultSet executeQuery(String sql);執行的靜態查詢sql語句
**PreparedStatement接口:**用於執行預編譯sql語句,是Statement的子接口
int executeUpdate();執行預編譯的更新sql語句 ResultSet executeQuery();執行預編譯的查詢sql語句
**CallableStatement接口:**用於執行存儲過程sql語句,是PreparedStatement的子接口
Result executeQuery();調用存儲過程的方法
**ResultSet接口:**用於執行存儲過程的sql語句
Result executeQuery();調用存儲過程的方法
對ResultSet結果獲取數據的基本思想是:先指定行,然后獲取該行上字段的值
ResultSet用於代表sql語句查詢的結果,對結果進行封裝,並使用游標來獲取數據,在初始的時候,游標在第一行之前 ,調用ResultSet.next()方法,可以使游標指向具體的數據行,每調用一次向下調用一行,調用該方法獲取該行的數據。
ResultSet是用於封裝結果的,所以提供的主要是get方法,當使用next()方法指定到一行的時候,可以使用以下兩種方式來獲取指定行的字段值: 方法1: 通過索引來獲取,索引從1開始,注意,並不是從0開始,
- getObject(int index);Object使用字段的數據類型來替換 方法2: 指定數據類型並帶有字段的參數來獲取,例如:
- getString("name");
常用的幾個類型: int getInt(); varchar getString() date getdate() bit getBoolean()
ResultSet還提供了對結果集進行定位行的方法:
- next();移動到下一行
- previous();移動到前一行
- absolute(int row);移動到指定行
- beforeFist();移動ResultSet的最前面
- afteLast();移動到ResultSet的最最后
2.使用Statement執行SQL語句
當書庫已經連接並准備好sql語句之后,就要將這個語句在數據庫中執行,這個使用使用Statement對象,該對象主要提供兩種方法,一種是
- executeUpdate(sql);該方法主要用於向數據庫發送插入,修改,刪除的命令,返回一個整數
- executeQuery()方法用於向數據庫發送查詢語句,用於查詢數據,返回的是結果封裝在ResultSet中,然后再從ResultSet中將數據打印出來
使用executeUpdate插入,修改,刪除代碼示例:
/**
* 測試Statement接口
*/
public class StatementDemo {
private String url = "jdbc:mysql://localhost:3306/db";
private String user = "root";
private String password = "root";
/**
*1.創建表
*/
@Test
public void TestCteate(){
Connection connection=null;
Statement statement=null;
try {
//1.驅動注冊程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連接對象
connection = DriverManager.getConnection(url,user,password);
//3.創建Statement對象
statement=connection.createStatement();
//4.准備sql
String sql = "CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20),gender VARCHAR(2));";
//5.發送sql語句,執行sql語句,並返回結果被影響的行數
int count = statement.executeUpdate(sql);
//6.輸出
System.out.println("影響了"+count+"行!");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.關閉連接(關閉順序:后開發先關閉)
if (statement!=null) try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
if (connection!=null) try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 插入數據
* 修改數據
* 刪除數據
*/
@Test
public void TestInsert(){
Connection connection=null;
Statement statement=null;
try {
//1.驅動注冊程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連接對象
connection = DriverManager.getConnection(url,user,password);
//3.創建Statement對象
statement=connection.createStatement();
//4.准備sql
//其他的修改,刪除,只需要更改這里的sql即可
//插入
// String sql = "INSERT INTO student(NAME,gender) VALUES ('張梅','女');";
//修改
// String sql = "UPDATE student SET NAME='張三' WHERE id=2";
//刪除
String sql = "DELETE FROM student WHERE id=2";
//5.發送sql語句,執行sql語句,並返回結果被影響的行數
int count = statement.executeUpdate(sql);
//6.輸出
System.out.println("影響了"+count+"行!");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.關閉連接(關閉順序:后開發先關閉)
if (statement!=null) try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
if (connection!=null) try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用executeQuery查詢數據代碼示例: 注意其中的ResultSet方法
/**
* 查詢數據
*/
@Test
public void TestFind(){
Connection connection=null;
Statement statement=null;
try {
//1.驅動注冊程序
Class.forName("com.mysql.jdbc.Driver");
//2.獲取連接對象
connection = DriverManager.getConnection(url,user,password);
//3.創建Statement對象
statement=connection.createStatement();
//4.准備sql
String sql = "SELECT * FROM student";
//5.發送sql語句,執行sql語句,並返回查詢結果
ResultSet rs = statement.executeQuery(sql);
//6.打印出數據
//方法1:移動光標:
/*
boolean flag = rs.next();
if(flag){
//取出列值
//方法1.1:以索引方式
int id = rs.getInt(1);
String name = rs.getString(2);
String gender = rs.getString(3);
System.out.println(id+","+name+","+gender);
//方法1.2:以列名稱
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
*/
//方法2:使用遍歷方法
//遍歷結果
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.關閉連接(關閉順序:后開發先關閉)
if (statement!=null) try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
if (connection!=null) try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
注意: 1.jdbc運行完以后一定要釋放資源,特別是Connection對象,它是非常西游的資源,使用完要馬上釋放。 2.jdbc中的釋放要后連接的先釋放 3.為確保資源釋放代碼能夠運行,此部分代碼一定要放在finally語句中
##3.使用PreparedStatement執行SQL語句
在Statement中,可以發現在連接數據庫和釋放資源方面有代碼重復的部分,這個時候可以把重復的部分抽取出來,現在把連接部分和關閉部分的代碼抽取放到jdbcUtil這個類中,有連接方法和關閉方法,代碼示例如下:
/**
* jdbc工具類
* 數據庫連接和資源釋放
*/
public class jdbcUtil {
private static String url="jdbc:mysql://localhost:3306/db";
private static String user="root";
private static String password="root";
/**
* 靜態代碼塊(只加載一次)
*/
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("驅動程序注冊出錯");
}
}
/**
* 抽取獲取連接對象的方法
*/
public static Connection getConnect(){
Connection conn = null;
try {
conn = DriverManager.getConnection(url,user,password);
return conn;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 釋放資源的方法
*/
public static void close(Connection conn, Statement stmt){
if (stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public static void close(Connection conn, Statement stmt, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
重復的部分已經抽取了,現在使用抽取的部分來實現PreparedStatement接口的方法 PreparedStatement接口是執行預編譯的sql語句的,最大的特點是,sql語句中的參數可以使用問號?來占用一個位置,然后再根據位置來設置sql里面的參數,最后發送到數據庫執行 PreparedStatement的CURD方法分別使用代碼表示如下: 1.插入
/**
* 插入數據
*/
@Test
public void TestInsert(){
Connection conn = null;
PreparedStatement pstmt = null;
try {
//1.獲取連接
conn = jdbcUtil.getConnect();
//2.准備預編譯的sql
String sql = "INSERT INTO student(NAME,gender) VALUES(?,?)";//問號?表示一個參數的占位符
//3.執行編譯sql語句(語法檢查)
pstmt = conn.prepareStatement(sql);
//4.設置參數
//參數的設置用兩個參數,第一個指定是第幾個問號,第二個是參數的值,參數位置從1開始
pstmt.setString(1, "李四");
pstmt.setString(2, "男");
//5.發送參數,執行sql
int count = pstmt.executeUpdate();//注意此處括號里不放sql
System.out.println("影響了"+count+"行!");
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
jdbcUtil.close(conn,pstmt);
}
}
2.修改
/**
* 更改數據
*/
@Test
public void testUpdate() {
Connection conn = null;
PreparedStatement stmt = null;
try {
//1.獲取連接
conn = jdbcUtil.getConnect();
//2.准備預編譯的sql
String sql = "UPDATE student SET NAME=? WHERE id=?"; //?表示一個參數的占位符
//3.執行預編譯sql語句(檢查語法)
stmt = conn.prepareStatement(sql);
//4.設置參數值
/**
* 參數一: 參數位置 從1開始
*/
stmt.setString(1, "王五");
stmt.setInt(2, 9);
//5.發送參數,執行sql
int count = stmt.executeUpdate();
System.out.println("影響了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
jdbcUtil.close(conn, stmt);
}
}
3.刪除
/**
* 刪除數據
*/
@Test
public void testDelete() {
Connection conn = null;
PreparedStatement stmt = null;
try {
//1.獲取連接
conn = jdbcUtil.getConnect();
//2.准備預編譯的sql
String sql = "DELETE FROM student WHERE id=?"; //?表示一個參數的占位符
//3.執行預編譯sql語句(檢查語法)
stmt = conn.prepareStatement(sql);
//4.設置參數值
/**
* 參數一: 參數位置 從1開始
*/
stmt.setInt(1, 9);
//5.發送參數,執行sql
int count = stmt.executeUpdate();
System.out.println("影響了"+count+"行");
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
jdbcUtil.close(conn, stmt);
}
}
4.查詢
/**
* 查詢數據
*/
@Test
public void testQuery() {
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
//1.獲取連接
conn = jdbcUtil.getConnect();
//2.准備預編譯的sql
String sql = "SELECT * FROM student";
//3.預編譯
stmt = conn.prepareStatement(sql);
//4.執行sql
rs = stmt.executeQuery();
//5.遍歷rs
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
//關閉資源
jdbcUtil.close(conn,stmt,rs);
}
}
PreparedStatement 和 Statment 區別: 1.語法不同:PreparedStatement可以使用預編譯的sql,而Statement只能使用靜態的sql 2.效率不同:PreparedStatement可以使用sql緩存區,效率比Statement高。Oracle是支持sql緩存區的,mysql是不支持緩存區的 3.安全性不同:PreparedStatement可以有效防止sql注入,而Statement不能防止sql注入
推薦使用PreparedStatement
##4.CallableStatement執行存儲過程 CallableStatement是PreparedStatement的子類,所以可以使用PreparedStatement的方法,即也可以使用編譯sql,同時該類是用來執行存儲過程的,所以兩者之間是可以連在一起用的。 注意沒有executeUpdate方法,所有調用存儲過程都是使用executeQuery方法。 根據存儲過程的不同,將代碼分為兩種:一個是只有出入參數的存儲過程,一個是只有輸出過程的存儲過程 帶有出入參數的存儲過程
# 帶有出入參數的存儲過程
DELIMITER $
CREATE PROCEDURE pro_findById(IN sid INT)
BEGIN
SELECT * FROM student WHERE id=sid;
END $
CALL pro_findById(4);
帶有輸出參數的存儲過程
# 帶有輸出參數的存儲過程
DELIMITER $
CREATE PROCEDURE pro_findById2(IN sid INT,OUT sname VARCHAR(20))
BEGIN
SELECT name INTO sname FROM student WHERE id=sid;
END $
CALL pro_findById2(4,@name);
SELECT @name;
使用jdbc分別調用以上兩種存儲過程
/**
*使用CallableStatement調用存儲過程
*/
public class CallableStatementDemo {
/**
*調用帶有輸入參數的存儲過程
* CALL pro_findById(4);
*/
@Test
public void testIn(){
Connection conn = null;
CallableStatement cstmt = null;
ResultSet rs = null;
try {
//1.獲取連接方法
conn = jdbcUtil.getConnect();
//2.准備sql
String sql = "CALL pro_findById(?)";//可以執行預編譯sql
//3.預編譯
cstmt=conn.prepareCall(sql);
//4.設置輸入參數
cstmt.setInt(1,4);
//5.發送參數
rs = cstmt.executeQuery();//注意:所有調用存儲過程的sql語句都是使用executeQuery方法
//遍歷結果
while (rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String gender = rs.getString("gender");
System.out.println(id+","+name+","+gender);
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
jdbcUtil.close(conn,cstmt,rs);
}
}
/**
* 執行帶有輸出參數的存儲過程
* CALL pro_findById2(4,@NAME);
*/
@Test
public void TestOut(){
Connection conn =null;
CallableStatement cstmt = null;
ResultSet rs = null;
try {
//1.獲取連接
conn = jdbcUtil.getConnect();
//2.准備sql
String sql = "CALL pro_findById2(?,?)";//第一個?是輸入參數,第二個?是輸出參數
//3.預編譯
cstmt = conn.prepareCall(sql);
//4.設置輸入參數
cstmt.setInt(1,4);
//5.設置輸出參數(注冊輸出參數)
/**
* 參數一:參數位置
* 參數二:存儲過程中的輸出參數的jdbc類型
*/
cstmt.registerOutParameter(2, Types.VARCHAR);
//6.發送參數,執行
cstmt.executeQuery();//結果不是返回到結果集中,而是返回到輸出參數中
//7.得到輸出參數的值
/**
* 索引值:預編譯sql中的輸出參數的位置
*/
String result = cstmt.getString(2);//getXX方法專門用於獲取存儲過程中的輸出參數
System.out.println(result);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally {
jdbcUtil.close(conn,cstmt,rs);
}
}
}
##5.類路徑的讀取和jdbcUtil的配置文件
在jdbcUtil類中,我們把數據庫,驅動,用戶名,密碼寫死了,以后想要修改就很麻煩,這種情況,我們會把配置文件再次抽取出來,創建一個db.properties文件來保存一些配置文件。需要輸入信息,在這個文件里面配置就可以了 首先,配置文件是這樣的:
url=jdbc:mysql://localhost:3306/db
user=root
password=123456
driverClass=com.mysql.jdbc.Driver
然后,在jdbcUtil類中調用:
/**
* jdbc工具類
* 數據庫連接和資源釋放
*/
public class jdbcUtil {
private static String url=null;
private static String user=null;
private static String password=null;
private static String driverClass=null;
/**
* 靜態代碼塊(只加載一次)
*/
static {
try {
//讀取db.properties
Properties props = new Properties();
/**
* 使用類路徑的讀取方法
*/
/**
* . :代表java命令運行的目錄
* 在java項目下:點 . java命令的運行目錄從項目的根目錄開始
* 在web項目下: 點 . java命令的運行目錄從tomcat/bin目錄開始
* 使用點.來指定目錄會產生在java 項目和javaweb項目中不適用的情況
*/
/**
* /:斜杠表示classpath的根目錄
* 在java項目下,classpath的根目錄從bin目錄開始
* 在web項目下,classpath的根目錄從WEB-INF/classes目錄開始。
* 而db.properties就在/目錄下
* 一般都是使用/來指定按文件名字
*/
InputStream in = jdbcUtil.class.getResourceAsStream("/db.properties");
//加載文件
props.load(in);
//讀取信息
url=props.getProperty("url");
user=props.getProperty("user");
password=props.getProperty("password");
driverClass=props.getProperty("driverClass");
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("驅動程序注冊出錯");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 抽取獲取連接對象的方法
*/
public static Connection getConnect(){
Connection conn = null;
try {
conn = DriverManager.getConnection(url,user,password);
return conn;
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 釋放資源的方法
*/
public static void close(Connection conn, Statement stmt){
if (stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public static void close(Connection conn, Statement stmt, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
}
其中比較重要的就是文件路徑的查找,因為在java項目和javaweb項目中的文件路徑是不一樣的,這個時候就可以使用類路徑來查找。java中也通常使用點和斜杠來指定路徑,但是指定的路徑是不一樣的: 點指定路徑: . :代表java命令運行的目錄 在java項目下:點 . java命令的運行目錄從項目的根目錄開始 在web項目下: 點 . java命令的運行目錄從tomcat/bin目錄開始 使用點.來指定目錄會產生在java 項目和javaweb項目中不適用的情況
斜杠指定路徑: /:斜杠表示classpath的根目錄 在java項目下,classpath的根目錄從bin目錄開始 在web項目下,classpath的根目錄從WEB-INF/classes目錄開始。 而db.properties就在/目錄下 一般都是使用/來指定按文件名字