Java基礎--JDBC

来源:https://www.cnblogs.com/l-y-h/archive/2019/07/02/11123700.html
-Advertisement-
Play Games

一、JDBC 1、JDBC簡介 (1) JDBC(Java Database Connectivity),即Java資料庫連接。用於在Java程式中實現資料庫操作功能。 (2)是一種用於執行SQL語句的Java API,使用戶以相同的方式連接不同的資料庫。 (3)JDBC提供一套標準介面,即訪問數據 ...


一、JDBC

1、JDBC簡介

  (1) JDBC(Java Database Connectivity),即Java資料庫連接。用於在Java程式中實現資料庫操作功能。
  (2)是一種用於執行SQL語句的Java API,使用戶以相同的方式連接不同的資料庫。
  (3)JDBC提供一套標準介面,即訪問資料庫通用的API,不同的資料庫廠商會根據各自的資料庫特點去實現這些介面。

 

2、JDBC工作步驟

  (1)載入驅動(使用Class.forName),建立連接(DriverManager)並返回連接(Connection)。
  (2)創建語句對象。(Connection 創建一個 Statement 或 PreparedStatement , 用於執行SQL語句)
  (3)執行SQL語句。(Statement 或 PreparedStatement執行SQL語句)
  (4)處理結果集。(SELECT產生結果集ResultSet)
  (5)關閉連接。(依次將ResultSet、Statement、PreparedStatement、Connection對象關閉,釋放資源。)

3、為什麼要釋放資源、關閉連接?

  (1)釋放資源:

    由於JDBC驅動在底層通常通過網路I/O實現SQL命令以及數據傳輸的,所以資源的釋放有利於系統的運行。且由於是操作I/O,所以代碼塊需要拋出異常(即代碼塊寫在try-catch語句或者通過throws拋出。)。

  (2)關閉連接:

    JDBC只有建立與資料庫的連接才能訪問資料庫,而與資料庫連接是非常重要的連接,若不能及時釋放,會浪費資源。同時每次使用createStatement()方法以及prepareStatement()方法都會在資料庫中打開一個游標(cursor),若不能即使關閉,可能導致程式拋出異常。

 

二、載入驅動、建立連接

  使用JDBC連接Oracle資料庫時,涉及I/O操作,需要捕獲異常,可以寫在try-catch中。如果拋出異常:java.lang.ClassNotFoundException,則可能是未導入連接資料庫的相關jar包。

1、oracle

//載入JDBC驅動,通過反射機制載入
Class.forName("oracle.jdbc.driver.OracleDriver");

//連接路徑,用於連接資料庫
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String userName = "LYH"; //預設用戶名 
String userPwd = "SYSTEM"; //密碼 

//建立連接
Connection conn = DriverManager.getConnection(url, userName, userPwd);

註:
Oracle連接方式:
     jdbc:oracle:thin:@<IP地址>:<port埠號>:<SID>     
 路徑,jdbc:oracle:thin:@是固定寫法,
localhost是主機號(IP地址),1521是埠號(Port),xe是SID(用於標識資料庫)。
LYH是資料庫(用戶)名
SYSTEM是密碼(口令) 

 

2、mysql(8.0之後的版本)

//載入JDBC驅動,通過反射機制載入
Class.forName("com.mysql.cj.jdbc.Driver");

//連接路徑,用於連接資料庫
//String url = "jdbc:mysql://localhost:3306/test";
String url = "jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false";
String userName = "LYH"; //預設用戶名 
String userPwd = "SYSTEM"; //密碼 

//建立連接
Connection conn = DriverManager.getConnection(url, userName, userPwd);

註:
mySql連接方式:
    jdbc:mysql://<IP地址>:<port埠號>/<dbname相當於SID>
    
JDBC連接mysql 5需用com.mysql.jdbc.Driver
即:
Class.forName("com.mysql.jdbc.Driver");
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false

 

3、sql server 2005

//載入JDBC驅動,通過反射機制載入
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver"); 

//連接路徑,用於連接資料庫 tempdb
String url = "jdbc:sqlserver://localhost:1433;DatabaseName=tempdb";
String userName = "sa"; //預設用戶名 
String userPwd = "123456"; //密碼 

//建立連接
Connection conn = DriverManager.getConnection(url, userName, userPwd); 

 

三、Statement、PreparedStatement 、CallableStatement、ResultSet、ResultSetMetaData

1、Statement

  (1)Statement主要用於執行靜態SQL語句(不帶參數),即內容固定不變的SQL語句。
  (2)Statement每執行一次都要對傳入的SQL語句編譯一次,效率較差。

創建Statement的方式:
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
Statement state = conn.createStatement();

1、執行DDL語句:
格式:
boolean flag = state.execute(sql);

2、執行DML語句:(返回值為DML影響的資料庫中的數據總條數)
格式:
int length = state.executeUpdate(sql);

3、執行DQL語句:(SELECT語句)
格式:
ResultSet rs = state.executeQuery(sql);

註:sql語句中不要帶分號(;)。

 

2、PreparedStatement(可以預防sql註入攻擊)

  (1) PreparedStatement可用於動態SQL語句,即SQL部分參數改變,其餘條件不變。適用於多次執行SQL語句的時候,以提高效率。
  (2)PreparedStatement是一個介面,繼承Statement。但其execute等三個方法不需要參數了。
  (3)PreparedStatement實例包含已編譯的SQL語句。以符號(?)作為占位符,?表示一個任何類型的參數。

創建PreparedStatement的方式:
Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
prepareStatement pstate = conn.createStatement();
sql = "select * from where id = ?";
pstate = conn.prepareStatement(sql); //預編譯sql語句

設定參數
pstate.setString(1, "12345");

1、執行DDL語句:
格式:
boolean flag = pstate.execute();

2、執行DML語句:(返回值為DML影響的資料庫中的數據總條數)
格式:
int length = pstate.executeUpdate();

3、執行DQL語句:(SELECT語句)
格式:
ResultSet rs = pstate.executeQuery();

註:sql語句中不要帶分號(;)。

 

3、 CallableStatement

  提供了用來調用資料庫存儲過程的介面,如果有輸出參數需要註冊,則說明是輸出參數。其調用存儲過程有兩種方式:帶結果參數(一種輸出參數,是存儲過程的返回值) 與 不帶結果參數。CallableStatement繼承了PreparedStatement處理輸入參數的方法,且還增加了調用資料庫的存儲過程和函數以及設置輸出類型參數的功能。

4、ResultSet、ResultSetMetaData

  ResultSet表示的是DQL語句的結果集,但其結果集仍在資料庫上(每次只取一定數量的數據放入記憶體,具體數量由不同的資料庫驅動決定),若想取數據,需使用next方法每次取一條,其是與資料庫一直連接的,當查詢結束後,最好將其關閉,要不然可能會拋出異常。

ResultSet獲取結果集
格式:
    ResultSet Statement.executeQuery(sql);
    ResultSet PreparedStatement.executeQuery();

ResultSetMetaData獲取結果集的元數據,元數據指數據的數據,用於描述數據的屬性、信息。
格式:
    ResultSetMetaData ResultSet.getMetaData();

 

5、PreparedStatement相比於Statement的優點

  (1)效率更高。

    使用PreparedStatement對象執行SQL語句時,命令會被資料庫進行編譯和解析,並將其放在命令緩衝區。每次執行PreparedStatement對象時,由於在緩衝區可以找到預編譯命令,雖然會被解析一次,但是不會被再次編譯,可以重覆使用,從而提高了效率。
  (2)代碼可讀性與可維護性更高。

    由於PreparedStatement使用setInt()或者setString()方法為參數進行賦值,而Statement需要在SQL語句中寫出,相比之下PreparedStatement更加清晰。
  (3)安全性更好。

    使用PreparedStatement可以有效防止SQL註入攻擊。所謂SQL註入攻擊,通常指的是將SQL語句通過插入到Web表單提交輸入功能變數名稱或者查詢頁面請求的查詢字元串,最終達到欺騙伺服器並且執行惡意SQL命令的目的。由於Statement是將輸入的字元串當成SQL語句進行拼接,再進行編譯,所以其不能防範SQL註入攻擊,而PreparedStatement是將輸入的字元串當成一個值來處理,避免了SQL註入攻擊的問題。

【sql註入舉例:】
SQL語句為:
String str = " SELECT *
FROM emp
WHERE name = '"
+ name + "' AND password = '"
+ password'";
即相當於SQL語句
SELECT *
FROM emp
WHERE name = '' AND password = '?'

如果輸入name = "aa", password = "bb' OR  '1' = '1"
那麼相當於SQL語句:
SELECT *
FROM emp
WHERE name = 'aa' AND password = 'bb' OR  '1' = '1'
那麼不管輸入是否正確,此WHERE條件均是成立的。即SQL註入攻擊成功。

 

四、使用連接池(DBCP)連接JDBC

1、什麼是連接池?

  連接池是管理與創建資料庫連接的緩衝池技術,將連接提前準備好,方便使用。連接池也是一個介面,具體由不同廠商進行實現。

2、未使用DBCP時

  一次資料庫連接對應著一次物理連接,而每次操作數據都要打開、關閉這個連接,會消耗大量資源。

3、使用DBCP時

  系統啟動並連接資料庫後,會自動創建一定數目的連接,並將這些連接組成一個連接池。每次連接時,會從連接池中取出一個連接,使用完後,將該連接放回連接池(註:此處不是關閉連接)。這樣可以使連接重覆使用,提高程式運行效率。

4、DBCP原則:

  (1)系統啟動時,連接到資料庫後,會初始化創建一定數目的連接,並將其組成一個池。
  (2)每次連接時,從池內取出已有連接,使用完後,將連接歸還(不是關閉連接)給連接池。
  (3)若池內無連接或達到最小連接數時,按增量增加新連接。
  (4)保持最小連接數,其有動態檢查、靜態檢查的方法。
    動態檢查:定時檢查池,若數量小於最小連接數,則補充連接。
    靜態檢查:當連接不足時,才檢查數量是否小於最小連接數。

5、連接池導包

  使用連接池,需要導包。

commons-dbcp-1.4.jar ;用於連接池的實現
commons-pool-1.5.jar ;連接池實現的依賴庫
commons-collections4-4.0.jar ;

 

五、實例:

1、使用普通的JDBC,直接在代碼中賦值

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

/**
 * 使用JDBC連接Oracle資料庫
 * 涉及I/O操作,需要捕獲異常,可以寫在try-catch中。
 * 
 * 如果拋出異常:java.lang.ClassNotFoundException,
 * 那麼表示未導入包,比如:Oracle 11g JDBC_ojdbc6.jar。
 *
 */
public class Test {
    public static void main(String[] args) {
        try {
            //1、載入驅動    
            Class.forName("oracle.jdbc.driver.OracleDriver");
            
            //建立連接,並返回Connection連接
            Connection conn = DriverManager.getConnection(
                    "jdbc:oracle:thin:@localhost:1521:xe", "LYH", "SYSTEM");
            
            //2、創建Statement對象
            Statement state = conn.createStatement();
            
            //定義sql語句
            String sql = "SELECT * FROM emp";
            
            //執行SQL查詢語句,結果集由ResultSet保存
            ResultSet rs = state.executeQuery(sql);
            
            //迴圈輸出結果集
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                System.out.println("id = " + id + ",name = " + name + ",salary = " + salary);
            }
            rs.close();//關閉SQL連接
                   conn.close();//關閉資料庫連接
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

 

2、使用properties的配置文件賦值

  通過修改配置文件來實現代碼功能的改變,可以不用修改class文件,便於開發。

【格式:】
    string1 = string2     # 尾部沒有分號(;)
# 表示註釋

java.util.Properties;該類用於讀取properties文件,並以Map集合的形式存儲文件內容。

【讀取文件步驟:】
1、先使用InputStream is(或其他輸入流)讀取文件。
2、再使用load(is) 載入文件。
3、使用getProperty(String key),通過等號左邊(key)值,獲取等號右邊(value)值。
4、有一個線程ThreadLocal<Connection>,其內部操作的為一個Map集合,相當於<Thread, Connection>,將線程作為key。
ThreadLocal有個set方法,會將當前線程作為key,並將給定的值放入Map中,這樣便於知道具體某個值是哪個線程所調用的,方便操作。

【舉例:】
//定義一個連接資料庫,並可以關閉資料庫的類。
//DefineConnection.class

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;


/**
 * 自定義一個連接資料庫的類,
 * 寫在Static塊中,用於從properties文件中獲取值,並載入驅動。
 * 
 * 聲明一個連接資料庫方法,一個關閉資料庫方法。
 *
 */
public class DefineConnection {
    private static String driver;  //保存驅動
    private static String url;  //保存路徑
    private static String user;  //保存資料庫(用戶)名
    private static String password;   //保存密碼(口令)
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); //用於管理不同的線程獲取的連接。
    
    static {
        try {
            //實例化一個Properties類,用於讀取properties文件
            Properties prop = new Properties();
            
            //通過相對路徑獲取文件
            InputStream is = DefineConnection.class.getClassLoader().getResourceAsStream("config.properties");
            
            //載入文件,並以類似Map的形式保存
            prop.load(is);
            
            //關閉輸入流
            is.close();
            
            //從文件中將等號左邊作為Key,獲取等號右邊的值
            driver = prop.getProperty("driver");
            url = prop.getProperty("url");
            user = prop.getProperty("user");
            password = prop.getProperty("password");
            
            //載入驅動
            Class.forName(driver);
            
             
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 用於連接資料庫
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception{
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            tl.set(conn);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw e; //通知調用者,調用出錯
        }
    }
    
    /**
     * 用於關閉資料庫
     * @param conn
     * @throws Exception
     */
    public static void closeConnection() throws Exception{
        try {
            /*
             * 通過ThreadLocal指定的線程,獲取指定的連接,並將其斷開。
             * 使用ThreadLocal可以不給方法加指定參數,就能獲取該值。
             * 適用於多個線程混合調用,獲取具體值的時候。
             */
            Connection conn = tl.get();
            if(conn != null) {
                conn.close();
                tl.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}



//定義一個測試類:
//Test.class

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        try {
            //獲取連接
            Connection conn = DefineConnection.getConnection();
            //獲取Statement對象
            Statement state = conn.createStatement();
            //定義SQL語句
            String sql = "SELECT * FROM emp";
            //執行SQL語句,得結果集
            ResultSet rs = state.executeQuery(sql);
            //遍歷結果集
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                System.out.println("id = " + id + ", name = " + name + ", salary = " + salary);
            }
            //關閉SQL連接
            rs.close();
            //關閉資料庫連接
            DefineConnection.closeConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

3、使用連接池

//config.properties
//配置文件,用於存儲資料庫的一些信息
driver=oracle.jdbc.driver.OracleDriver
user=LYH
password=SYSTEM
url=jdbc:oracle:thin:@localhost:1521:xe
max=30
initialsize=10

//自定義一個類,定義一個資料庫連接池,用於連接資料庫.
//DefineConnection.class 

import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

import org.apache.commons.dbcp.BasicDataSource;

/**
 * 自定義一個資料庫連接池,並使用其連接資料庫
 *
 */
public class DefineConnection {
    //聲明連接池
    private static BasicDataSource dbs;
    //聲明線程
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();;
    static {
        try {
            //實例化一個Properties類
            Properties prop = new Properties();
            //讀取一個類
            InputStream is = DefineConnection.class.getClassLoader().getResourceAsStream("config.properties");
        
            prop.load(is);
            //初始化連接池
            dbs = new BasicDataSource();
            
            //設置驅動
            dbs.setDriverClassName(prop.getProperty("driver"));
            //設置連接路徑
            dbs.setUrl(prop.getProperty("url"));
            //設置資料庫(用戶)名
            dbs.setUsername(prop.getProperty("user"));
            //設置密碼(口令)
            dbs.setPassword(prop.getProperty("password"));
            //設置連接最大值
            dbs.setMaxActive(Integer.parseInt(prop.getProperty("max")));
            //設置初始連接數
            dbs.setInitialSize(Integer.parseInt(prop.getProperty("initialsize")));
            
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 使用連接池連接資料庫
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception{
        try {
            //連接資料庫
            Connection conn = dbs.getConnection();
            tl.set(conn);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
    
    /**
     * 返回資料庫連接進連接池
     * @throws Exception
     */
    public static void closeConnection() throws Exception{
        try {
            Connection conn = tl.get();
            if(conn != null) {
                conn.close();//歸還連接給連接池,並關閉連接
                tl.remove();//刪除不需要的線程
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}


//定義一個測試類,使用自定義資料庫連接類,連接資料庫進行測試
//Test.class;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

public class Test {
    public static void main(String[] args) {
        try {
            Connection conn = DefineConnection.getConnection();
            Statement state = conn.createStatement();
            String sql = "SELECT id, name FROM emp";
            ResultSet rs = state.executeQuery(sql);
            while(rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                System.out.println("id = " + id + ", name = " + name);
            }
            rs.close();
            DefineConnection.closeConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }    
    }
}

 

 

六、JDBC事務隔離級別(5種)

1、為什麼有事務隔離?

  為瞭解決多個線程請求相同數據的情況,事務間通常用鎖進行隔離,從而衍生出事務隔離級別。事務隔離級別越高,避免衝突的操作越繁瑣。可以通過Connection對象的setTransactionLevel()方法來設置隔離級別,通過conn.getTransactionLevel()方法確定當前事務級別。

2、相關概念:

  (1)讀“臟”數據:指一個事務讀取了另一個事務尚未提交的數據,即讀取到無效數據。比如:A與B事務併發執行,事務A進行更新操作後,B讀取A尚未提交的數據,此時A由於某原因進行回滾,那麼此時B讀到的數據即為無效的臟數據。

  (2)不可重覆讀:指一個事務的操作導致另一個事務前後兩次讀取的數據不一致。比如:事務A與B併發執行,某時刻事務A更新了事務B讀取的數據,當B再次讀取數據時,會得到不同的數據。

  (3)虛讀:指一個事務的操作導致另一個事務前後兩次查詢的數據量不同。比如:事務A與B併發執行,某時刻事務A刪除或增加了事務B所查詢的表的記錄,那麼當事務B再次查詢該表時,會得到不存在或者缺少的記錄。

3、事務隔離級別(5種,以下級別由低到高):

  (1)TRANSACTION_NONE_JOB(transaction_none_job):不支持事務。

  (2)TRANSACTION_READ_UNCOMMITTED(transaction_read_uncommitted):未提交的讀,說明此時一個事務未提交前,可以看到另外一個事務的變化。允許 讀“臟”數據,不可重覆讀,虛讀。

  (3)TRANSACTION_READ_COMMITTED(transaction_read_committed):已提交的讀,此時不能讀取未提交的數據(即不能讀臟數據),但仍允許不可重覆讀,虛讀。

  (4)TRANSACTION_REPEATABLE_READ(transaction_repeatable_read):可重覆讀,此時讀取相同數據不會失敗,但仍允許虛讀。

  (5)TRANSACTION_SERIALIZABLE(transaction_serializable):可序列化,最高事務隔離級別。防止讀臟數據、不可重覆讀、虛讀。

 

七、批處理、分頁機制

1、批處理優勢

  (1)批處理:將一組發送到資料庫的語句(多條語句)作為一個單元處理。
  (2)批處理降低了應用程式和資料庫間的相互調用。
  (3)相比單個SQL語句處理,批處理更加高效。
  (4)批處理一般用於DML操作。

2、批處理相關方法

Statement方法:
    Statement.addBatch(String sql);將多條sql語句添加到Statement對象的SQL語句列表中。

PreparedStatement方法:
    PreparedStatement.addBatch();將多條預編譯sql語句添加到PreparedStatement對象的SQL語句列表中。

int[] executeBatch();返回值為每一條語句影響的數據量。 
    將Statement對象或PreparedStatement對象SQL語句列表中的所有SQL語句發給資料庫進行處理。

clearBatch()
    清空當前SQL語句列表。

 

3、分頁機制

  (1)第一種機制:
    每次只向資料庫中請求一頁的數據量,記憶體壓力小,適合大數據量的數據表。

  (2)第二種機制:(基於緩存的分頁技術,又稱假分頁)
    第一次一次性將數據全部取出到緩存,根據用戶輸入的頁數(page)和每頁記錄數(pagesize)來計算哪些數據顯示,將可滾動結果集的指針移到指定位置。只訪問資料庫一次,適合小數據量。

MySql的分頁實現:
SELECT *
FROM emp
LIMIT begin(從第幾條開始), pagesize(每頁條數);

Oracle分頁實現:
 SELECT *
 FROM(
    SELECT ROWNUM rn, t.* 
    FROM(
        SELECT * 
           FROM tableName 
           ORDER BY colName
        ) t 
 )
 WHERE rn BETWEEN ? AND ?
 
 【舉例:】
 import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;

public class Page {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("請輸入要查看的表名:");
        String tableName = scan.nextLine().trim();
        System.out.println("請輸入一頁顯示的頁數:");
        int pagesize = Integer.parseInt(scan.nextLine().trim());
        System.out.println("請輸入查看的頁數:");
        int page = Integer.parseInt(scan.nextLine().trim());
        System.out.println("請輸入排序的列名:");
        String colName = scan.nextLine().trim();
        try {
            Connection conn = DefineConnection.getConnection();
            /*
             * 分頁步驟:
             * 1、排序。
             * 2、編號。
             * 3、取範圍。
             * SELECT * FROM(
             *     SELECT ROWNUM rn, t.* FROM(
             *         SELECT * FROM tableName ORDER BY colName
             *     ) t 
             * )
             * WHERE rn BETWEEN ? AND ?
             */
            String sql = "SELECT * FROM( "
                        + "SELECT ROWNUM rn, t.* FROM( "
                        + "SELECT * FROM "+tableName
                        + " ORDER BY "+colName+" "
                        + ") t "
                        + ") "
                        + "WHERE rn BETWEEN ? AND ?";
            
            PreparedStatement ps = conn.prepareStatement(sql);
            int start = (page-1)*pagesize + 1;
            int end = page*pagesize;
            ps.setInt(1, start);
            ps.setInt(2, end);
            ResultSet rs = ps.executeQuery();
            while(rs.next()) {
                int rw = rs.getInt(1);
                int id = rs.getInt(2);
                String name = rs.getString(3);
                System.out.println("rw = "+rw +", id = " + id + ", name =" +name);
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                DefineConnection.closeConnection();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

 

4、自動返回主鍵

  (1)PreparedStatement支持一個方法,可以在執行插入操作後,獲取該語句在資料庫表中產生記錄中的每個欄位的值。因此,每次向表中插入數據時,可以獲取該主鍵作為外鍵插入,而不用單獨為獲取主鍵進行一次查詢。
  (2)用法:
    創建PreparedStatement,可以使用Connection重載方法,第二個參數要求傳入一個字元串數組,用來指定當通過ps執行插入操作後,記錄想獲取的欄位所在的值。

PreparedStatement ps = conn.prepareStatement(sql, new String[]{"id", "phone"});

ResultSet rs = ps.getGeneratedKeys();//獲取插入記錄時想返回的欄位值。
if(rs.next()){
    int id = rs.getInt("id");
    //也可以寫成int id = rs.getInt(1);
    String phone = rs.getString("phone");
}

 

八、補充

1、getString() 與 getObject()區別

  JDBC提供getString(), getInt(), getData()等方法從ResultSet中獲取數據,這些方法適用於數據量小且不考慮性能的情況。由於這些方法都是一次性將數據讀取到記憶體中,然後再從記憶體中讀取數據,如果數據量太大,可能造成寫入記憶體緩慢甚至不能完全寫入,這時將會拋出異常(以Oracle為例,ORA-01000)。

getObject()可以解決上面問題,其不會將數據一次性讀取到記憶體中,每次調用均會到資料庫中獲取數據,使用此方法不會因數據量過大而報錯。

2、JDBC事務採用方法

  (1)事務:指的是資料庫一個單獨執行的單元(一條或多條SQL語句組成的不可分割的單元),要麼完全執行,要麼不執行。

  (2)JDBC中一般採用commit()(用於提交) 和 rollback()(用於回滾)方法用來結束事務。位於java.sql.Connection類中。一般事務預設自動提交,即操作成功就提交,操作失敗就回滾。

  (3)可以通過調用setAutoCommit(false)方法來禁止自動提交,此時可以操作多個SQL語句後再提交,可在try-catch中調用rollback()方法進行事務回滾。此方法可以保持對資料庫多次操作後仍能保持數據一致性。

3、什麼是JDO?

  (1)JDO指Java Data Object(Java數據對象),用於存取某種數據倉庫中的對象的標準化API,從而使開發人員能夠間接地訪問資料庫。

  (2)JDO是JDBC的一個補充,提供了透明的對象存儲,即開發人員不需要考慮存儲數據對象的額外代碼(由資料庫開發商解決),只需考慮業務邏輯。

  (3)JDO比JDBC更加靈活、通用,提供了任何底層數據的存儲功能,可移植性更強。

4、什麼是DAO?

  (1)DAO(Data Access Object),指數據訪問對象。其方法一般不為靜態方法。

  (2)建立在資料庫和業務層之間,封裝所有對資料庫的訪問。通過DAO將java對象轉為資料庫數據,或將資料庫數據轉為java對象。

  (3)目的:將數據訪問業務與業務邏輯分開。操作資料庫變成面向對象化(Hibernate精髓)。

  (4)DAO封裝(面向介面編程):將所有對數據源的訪問操作抽象到一個公共API中。先建立一個介面,併在介面中定義應用程式會用到的所有事務方法。再建立介面的實現類,實現介面對應的所有方法,與資料庫進行交互。則在應用程式中,需要進行與數據源交互時,直接使用DAO介面,不用涉及任何資料庫的具體操作。
即DAO包括:一個DAO工廠類,一個DAO介面,一個實現DAO介面的具體類,數據傳遞對象。

  (5)DAO層需要定義對資料庫中表的訪問。對象關係映射(ORM,Object Relation Mapping)描述對象和數據表間的映射,將java程式中的對象對應到關係資料庫的表中。
即表與類(實體類)對應,表中欄位與類的屬性對應,記錄與對象對應。
實體類:用於描述資料庫中的一張表。

//自定義資料庫連接類:
//DefineConnection.class
//package DAO;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;


/**
 * 自定義一個連接資料庫的類,
 * 寫在Static塊中,用於從properties文件中獲取值,並載入驅動。
 * 
 * 聲明一個連接資料庫方法,一個關閉資料庫方法。
 *
 */
public class DefineConnection {
    private static String driver;  //保存驅動
    private static String url;  //保存路徑
    private static String user;  //保存資料庫(用戶)名
    private static String password;   //保存密碼(口令)
    private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); //用於管理不同的線程獲取的連接。
    
    static {
        try {
            //實例化一個Properties類,用於讀取properties文件
            Properties prop = new Properties();
            
            //通過相對路徑獲取文件
            InputStream is = DefineConnection.class.getClassLoader().getResourceAsStream("config.properties");
            
            //載入文件,並以類似Map的形式保存
            prop.load(is);
            
            //關閉輸入流
            is.close();
            
            //從文件中將等號左邊作為Key,獲取等號右邊的值
            driver = prop.getProperty("driver");
            url = prop.getProperty("url");
            user = prop.getProperty("user");
            password = prop.getProperty("password");
            
            //載入驅動
            Class.forName(driver);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 用於連接資料庫
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception{
        try {
            Connection conn = DriverManager.getConnection(url, user, password);
            tl.set(conn);
            return conn;
        } catch (Exception e) {
            e.printStackTrace();
            throw e; //通知調用者,調用出錯
        }
    }
    
    /**
     * 用於關閉資料庫
     * @param conn
     * @throws Exception
     */
    public static void closeConnection() throws Exception{
        try {
            /*
             * 通過ThreadLocal指定的線程,獲取指定的連接,並將其斷開。
             * 使用ThreadLocal可以不給方法加指定參數,就能獲取該值。
             * 適用於多個線程混合調用,獲取具體值的時候。
             */
            Connection conn = tl.get();
            if(conn != null) {
                // 恢復連接為自動提交事務,
                conn.setAutoCommit(true);
                conn.close();
                tl.remove();
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
}




//實體類:用於描述資料庫中的user1表。
//User1.class:
//package DAO;

/**
 * 實體類,用於表示資料庫的一張表,
 * 例如:
 * 實體類User1,用於表示資料庫中的user1表
 *
 */
public class User1 {
    private int id;
    private String name;
    private String password;
    private int money;
    private String email;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this
              
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 個人網站: lipeiguan.top 以後會慢慢轉移到個人網站, 歡迎大家收藏^ . ^ 寫在前面 我們在開發一個網站的時候, 經常需要實現網站的用戶系統. 這個時候我們需要實現用戶註冊、用戶登錄、用戶認證、註銷、修改密碼等一系列功能. 如果我們都是自己實現的話, 不是不可以, 只是有些浪費時間. ...
  • 老雷socket編程之PHP利用socket擴展實現聊天服務 socket聊天服務原理 PHP有兩個socket的擴展 sockets和streamssockets socket_create(AF_INET, SOCK_STREAM, SOL_TCP) socket_write socket_re ...
  • 老雷socket編程之常見網路協議 1.ip IP協議是將多個包交換網路連接起來,它在源地址和目的地址之間傳送一種稱之為數據包的東西, 它還提供對數據大小的重新組裝功能,以適應不同網路對包大小的要求。 2.TCP 傳輸控制協議 TCP(Transmission Control Protocol 傳輸 ...
  • [Think in Java]第2章 一切都是對象 如果我們說另一種不同的語言,那麼我們就會發覺有一個有些不同的世界 -- Luduing Wittgerstein 儘管Java基於C++,但相比之下,Java是一種更純粹的面向對象程式設計語言. Java語言假設我們只進行面向對象的程式設計(OOP ...
  • 棧:先入後出,後入先出 像電梯一樣,先進入電梯的,走到電梯最深處,後進入電梯的,站在電梯門口, 所以電梯打開的時候,後進入的會先走出來,先進入的會後走出來。 push,對應入電梯,把數據往裡面壓 pop, 對應出電梯,把數據往外拿 棧頂,對應電梯門口 棧底,對應電梯最深處 這裡使用鏈表實現棧。 先創 ...
  • 1.1原理 1.session是伺服器端的技術 2.session是基於cookie技術的 1.2session操作 1.預設情況下,會話不會自動開啟,通過session_start()開啟會話 2.通過session_id()獲取會話的編號 3、通過$_SESSION操作會話 4、會話可以保存除了 ...
  • 8-9 魔術師:創建一個包含魔術師名字的列表,並將其傳遞一個名為show_magicians()的函數,這個函數列印列表中每個魔術師的名字。 8-10 了不起的魔術師:在8-9的程式中,編寫一個名為make_great()的函數,對函數列表進行修改,在每個魔術師的名字中都加入字樣“the Great ...
  • 小猿圈作業: 編寫裝飾器,為多個函數加上認證的功能(用戶的賬號密碼來源於文件),要求登錄成功一次,後續的函數都無需再輸入用戶名和密碼 tip:account文件的格式 1 dadada 123456 2 da da 3 da1 da 4 da2 1234 5 da3 1111 編寫裝飾器,為多個函數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...