JavaSE學習筆記(22.Java之JDBC的使用)

1. JDBC概念說明

JDBC是Java Database Connectivity的簡稱,是Java語言訪問數據庫的接口規範,具體詳細規範內容可以參考JSR221

 

2. SQL語句分類

2.1 DML語句:

Data Manipulation Language的簡稱,數據操作語言(對數據表中的數據進行操作的行爲);主要是由insert、update和delete三個關鍵詞完成!

2.2 DCL語句:

Data Control language的簡稱,數據控制語言(數據庫及數據表權限相關的行爲);主要是由grant和revoke兩個關鍵詞完成!

2.3 DDL語句:

Data Definition Language的簡稱,數據定義語言(對數據庫中數據表進行操作的行爲);主要是由create、alter、drop和truncate四個關鍵詞完成!

2.4 DQL語句:

Data Query Language的簡稱,數據庫查詢語言(對數據表中的數據進行查詢的行爲);主要是由select關鍵詞完成!

2.5 TCL語句:

Transaction Control Language的簡稱,事物控制語言(數據庫中事物相關操作的行爲);主要是由commit、rollback和savepoint三個關鍵詞完成!

 

2. JDBC使用說明

2.1 JDBC與數據庫的鏈接:

JDBC鏈接數據庫主要是通過DriverManager類和Connection類實現:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*通過DriverManager類的getConnection()靜態方法獲取數據庫鏈接,再通過數據庫鏈接訪問數據庫*/
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC", "root", "root");
        }

        return;
    }
}

Ps:

  • 示例環境使用的是mysql數據庫,用戶名爲root,密碼爲root,端口號爲3306
  • 5.1.6及以上版本的mysql,已經不需要手動通過調用Class.forName("com.mysql.jdbc.Driver")方法進行mysql數據庫驅動的註冊了,已經可以通過JDK的SPI功能自動完成加載註冊驅動類!
  • 不同類型的數據庫都會實現自己的Driver驅動類;在類加載的時候,會將Driver驅動類註冊到DriverManager類中,當使用DriverManager.getConnection()方法獲取對應數據庫的Connection連接類的時候,需要通過不同的URL進行區分!
  • 不同數據庫的URL存在不同的格式:
RDBMS JDBC驅動程序名稱 URL格式 默認端口 舉例
oracle oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@dbip:port:databasename 1521

jdbc:oracle:thin:@10.10.10.10:1521:dataBase

mysql com.mysql.jdbc.Driver jdbc:mysql://dbip:port/databasename 3306

jdbc:mysql://10.10.10.10:3306/dataBase

SQLServer

com.microsoft.jdbc.sqlserver.SQLServerDriver 

jdbc:microsoft:sqlserver://dbip:port;

DatabaseName=databasename

1433

jdbc:sqlserver://10.10.10.10:1433;DatabaseName=dataBase

DB2

com.ibm.db2.jdbc.app.DB2Driver  jdbc:db2://dbip:port/databasename 5000 jdbc:db2://10.10.10.10:5000/dataBase

PostgreSQL

org.postgresql.Driver jdbc:postgresql://dbip:port/databasename 5432 jdbc:postgresql://10.10.10.10:5432/dataBase
  • DriverManager.getConnection()方法主要是通過URL及數據庫連接參數對數據庫進行連接,參數可以拼接到URL上(URL與參數直接通過?連接,key-value之間通過=連接,參數之間通過&連接),也可以作爲配置項傳入方法中;<mysql連接參數說明>
  • 切記每個Connection連接類都是連接到數據庫中的一個具體的數據庫(database),但並不是當前Connection連接類只能操作這個database;只要用戶權限滿足依然可以切換到其他database上面進行操作!


2.2 JDBC操作數據庫:

JDBC操作數據庫主要是通過Statement、PreparedStatement、CallableStatement這三個類實現,這三個類分別是由Connection類的createStatement()方法、prepareStatement()方法、prepareCall()方法獲取!

示例數據表格式:

建表語句:

CREATE TABLE `person`(
   `id` INT UNSIGNED AUTO_INCREMENT,
   `name` VARCHAR(100) NOT NULL,
   `info` VARCHAR(40) NOT NULL,
   `age` INT,
   PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2.1 Statement類:

Statement類提供瞭如下四個主要的方法:

  • execute()方法:可以執行所有類型的語句,如果語句類型爲DQL語句的時候,返回值爲true,並可以通過Statement類的getResultSet()方法獲取到結果集;如果語句類型爲DML語句的時候,返回值爲false,並可以通過Statement類的getUpdateCount()方法獲取Sql指令影響的行數;如果語句類型爲其他類型,返回值爲false,並不任何結果可獲取!
  • executeQuery()方法:只能執行DQL語句,並返回結果集!
  • executeUpdate()方法:可以執行DML語句和DDL語句,當執行DML語句的時候,返回Sql指令影響函數;當執行DDL語句的時候,返回0!
  • executeLargeUpdate()方法:與executeUpdate()方法類似,只是返回值從int改爲了long,當Sql指令影響行數大於Interge.MAX_VALUE的時候,可以考慮使用executeLargeUpdate()方法,但是mysql不支持!

代碼示例:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*獲取數據庫連接*/
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        Statement stmt = conn.createStatement();
        /*executeQuery方法的使用*/
        /*使用executeQuery方法進行數據庫查詢,並返回結果集*/
        ResultSet rs = stmt.executeQuery("SELECT * FROM person");
        /*遍歷結果集展示查詢結果數據*/
        while (rs.next()) {
            System.out.println(rs.getString("id"));
            System.out.println(rs.getString("name"));
            System.out.println(rs.getString("info"));
            System.out.println(rs.getString("age"));
        }

        /*execute方法的使用*/
        boolean b = stmt.execute("SELECT * FROM person");
        ResultSet rs1 = null;
        if (b) {
            rs1 = stmt.getResultSet();
        }

        while ((null != rs1) && (rs1.next())) {
            System.out.println(rs1.getString("id"));
            System.out.println(rs1.getString("name"));
            System.out.println(rs1.getString("info"));
            System.out.println(rs1.getString("age"));
        }

        /*executeUpdate方法的使用*/
        int n = stmt.executeUpdate("INSERT INTO person (name,info,age) VALUES ('qq','qq_info',20)");
        System.out.println(n);

        /*關閉所有資源*/
        rs.close();
        if (null != rs1) {
            rs1.close();
        }
        stmt.close();
        conn.close();
        return;
    }
}

Ps:ResultSet結果集、Connection連接類、Statement類用完後,都需要手動關閉;Statement類還提供了兩個方法,closeOnCompletion()方法和isCloseOnCompletion()方法;Statement類調用closeOnCompletion()方法後,當所有依賴Statement類的ResultSet結果集都關閉後,Statement類會自動關閉!同時可以使用isCloseOnCompletion()方法來判斷Statement類是否打開了這個功能!

2.2.2 PreparedStatement類:

PreparedStatement類與Statement類相似,也提供上述四個操作方法(只是沒有參數),使用原則也一致;只是在通過Connection類獲取PreparedStatement類的時候,使用帶佔位符(?符號)的Sql和設置預編譯參數,多進行一步預編譯過程!

代碼示例:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*獲取數據庫連接*/
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        /*DML示例*/
        /*通過Sql指令+佔位符獲取PreparedStatement對象*/
        PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO person (name,info,age) VALUES (?,?,?)");
        /*給預編譯設置參數*/
        preparedStatement.setString(1,"n2");
        preparedStatement.setString(2,"info2");
        preparedStatement.setInt(3,100);
        /*執行executeUpdate方法*/
        preparedStatement.executeUpdate();
        /*關閉資源*/
        preparedStatement.close();

        /*DQL示例*/
        preparedStatement = conn.prepareStatement("SELECT * FROM person WHERE name = ? ");
        /*給預編譯設置參數*/
        preparedStatement.setString(1,"n2");
        /*執行executeQuery方法*/
        ResultSet rs = preparedStatement.executeQuery();
        while (rs.next()) {
            System.out.println(rs.getString("id"));
            System.out.println(rs.getString("name"));
            System.out.println(rs.getString("info"));
            System.out.println(rs.getString("age"));
        }
        /*關閉所有資源*/
        rs.close();
        preparedStatement.close();
        conn.close();
        return;
    }
}

Ps:

  • PreparedStatement類在設置預編譯參數的時候,下標序號是從1開始,不是從0開始
  • 更加推薦使用PreparedStatement方式,性能更好、無需拼接Sql、而且通過佔位符預編譯可以有效的防止Sql注入,更加安全!
  • 佔位符只能替換Sql語句的普通值,不能替換庫名、表名、字段名和關鍵詞!!!

2.2.3 CallableStatement類:

CallableStatement類是JDBC提供用來調用數據庫存儲過程的一個操作類,使用說明如下:

存儲過程示例:

//存儲過程示例
delimiter $$
create procedure sum(a int, b int, out sum)
begin
set sum = a + b;
end$$

代碼示例:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*獲取數據庫連接*/
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        /*生成CallableStatement實例*/
        CallableStatement call = conn.prepareCall("call fun(?,?,?)");
        /*設置存儲過程輸入參數*/
        call.setInt(1, 100);
        call.setInt(2, 100);
        /*設置存儲過程輸出類型*/
        call.registerOutParameter(3,Types.INTEGER);
        /*執行存儲過程*/
        call.execute();
        /*獲取存儲過程輸出值*/
        System.out.println(call.getInt(3));
        
        call.close();
        conn.close();
        return;
    }
}

 

2.3 JDBC獲取結果集

2.3.1 ResultSet結果集:

在實例化PreparedStatement類和Statement類的時候,可以設置返回結果集的類型,是否可以更新、是否可以自由移動指針等等!

  • resultSetType參數:
  1. ResultSet.TYPE_FORWARD_ONLY:設置指針只能向前移動
  2. ResultSet.TYPE_SCROLL_INSENSITIVE:設置指針可以自由移動,但是底層數據的改變不會影響ResultSet結果集的內容
  3. ResultSet.TYPE_SCROLL_SENSITIVE:設置指針可以自由移動,但是底層數據的改變會影響ResultSet結果集的內容
  • resultSetConcurrency參數:
  1. ResultSet.CONCUR_READ_ONLY:設置ResultSet結果集爲只讀模式
  2. ResultSet.CONCUR_UPDATABLE:設置ResultSet結果集爲可更新模式
Statement createStatement(int resultSetType, int resultSetConcurrency,
                              int resultSetHoldability)
PreparedStatement prepareStatement(String sql, int resultSetType,
                                       int resultSetConcurrency)

ResultSet結果集常用方法:

        /*光標移動相關方法*/
        //向前滾動
        rs.previous();
        //向後滾動
        rs.next();
        //得到當前行號
        rs.getRow();
        //光標定位到n行
        rs.absolute(n);
        //相對移動n行
        rs.relative(int n);
        //將光標定位到結果集中首行
        rs.first();
        //將光標定位到結果集中尾行
        rs.last();
        //將光標定位到結果集中首行之前
        rs.beforeFirst();
        //將光標定位到結果集中尾行之後
        rs.afterLast();

        /*確認光標位置相關方法*/
        //光標是否在首行
        rs.isFirst();
        //光標是否在尾行
        rs.isLast();
        //光標是否在首行之前
        rs.isBeforeFirst();
        //光標是否在尾行之後
        rs.isAfterLast();

        /*更新結果集相關方法*/
        //將數據插入結果集和數據表中
        rs.insertRow();
        //將數據從結果集和數據表中刪除
        rs.deleteRow();
        //更新當前行某一列的數據
        rs.updateXXX(int column, XXX data);
        //更新當前行某一列的數據
        rs.updateXXX(String columnName, XXX Data);
        //將結果集中的更新的數據推送到數據表中
        rs.updateRow();

當指針移動到指定行後,可以通過getXXX(int column)方法或者getXXX(String columnName)方法,獲取當前行,特定列的數據!也可以使用<T> T getObject(int colum, Class<T> type)方法或者<T> T getObject(String columnName, Class<T> type)獲取任意類型的值!

Ps:

  • 使用可更新的結果集,要保證結果集中的數據全部來自一張數據表,並且結果集中有包含主鍵列,否則會更新失敗!
  • ResultSet結果集是一個與數據庫長連接的結果集,使用過程中要保證Connection對象處於連接狀態,否則訪問ResultSet結果集的時候會報錯!
  • 結果集的列數據獲取,下標都是從1開始的!

2.3.1 RowSet結果集:

ResultSet結果集是一個與數據庫長連接的結果集,RowSet是ResultSet的子接口,其實現類有JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet、WebRowSet!其中只有JdbcRowSet是長連接結果集,其餘的都是離線結果集!離線結果集便於存儲、傳輸、序列化,而且也提高了處理性能!

2.3.2 CachedRowSet離線結果集:

ResultSet結果集屬於數據庫長連接結果集,如果斷開與數據庫的連接,在訪問Result結果集,程序會報錯!所有更多的情況,是將ResultSet結果集在內存合理的範圍內轉換爲離線結果集,再提供給外部使用!

代碼示例:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        preparedStatement = conn.prepareStatement("SELECT * FROM person WHERE name = ? ");
        preparedStatement.setString(1,"n1");
        ResultSet rs = preparedStatement.executeQuery();

        /*通過ResultSet結果集生成離線CachedRowSet結果集*/
        RowSetFactory factory = RowSetProvider.newFactory();
        CachedRowSet cachedRowSet = factory.createCachedRowSet();
        cachedRowSet.populate(rs);

        /*關閉所有資源*/
        rs.close();
        preparedStatement.close();
        conn.close();

        /*通過CachedRowSet結果集獲取數據*/
        while (cachedRowSet.next()) {
            System.out.println(cachedRowSet.getString("id"));
            System.out.println(cachedRowSet.getString("name"));
            System.out.println(cachedRowSet.getString("info"));
            System.out.println(cachedRowSet.getString("age"));
        }

        return;
    }
}

CachedRowSet結果集的分頁:

如果ResultSet結果集比較大的時候,就不能全量加載到CachedRowSet結果集,否則可能會造成內存不足的情況;所有需要對CachedRowSet結果集進行分頁操作;每次只從ResultSet結果集中獲取一部分數據!當然這樣的操作依然需要保證底層與數據庫處於連接狀態!

分頁相關方法:

 

2.4 JDBC操作事物:

JDBC對於數據庫事物相關操作都是基於Connection連接類的如下方法實現的

Ps:

  • 通過setAutoCommit()方法關閉自動提交後,即開啓了事務;當前連接中的所有DML語句都不會立即生效,除非執行commit()方法顯示提交事務,或者通過當執行DDL語句、DCL語句、session退出的方式隱形提交事務;提交事務後,之前的DML語句纔會生效!
  • 除了通過關閉自動提交開啓事務,也可以通過begin命令或者start transaction命令,開啓事務!
  • 不管是隱性事務提交、顯性事務提交或者回滾操作,都會導致事務終止,不再繼續執行!
  • 還可以根據保存點進行回滾操作,這種回滾操作,會保留保存點之前的事務行爲;由於這種回滾行爲保留了保存點之前的事務,所以已經記得及時commit事物,保證數據更新!
  • 使用try-with-resource打開Connection連接類的時候,如果拋出一個未處理的SqlException異常,Connection類會自動回滾事務,並關閉事務!否則就需要人工去手動捕獲SqlException異常,在catch代碼塊中進行回滾操作!

 

2.5 Java8批量更新數據:

Java8後的版本新增了批量更新數據庫操作,可以執行DDL語句和DML語句;以前版本只能通過execute()方法一條條的去執行Sql,併發送給數據庫;現在可以使用addBatch()方法和executeBatch()方法批量執行Sql,再批量發送給數據庫!

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        /*獲取當前自動提交狀態*/
        boolean autoCommitFlag = conn.getAutoCommit();
        Statement statement = conn.createStatement();
        /*將自動提交狀態,設置爲flase,開啓事務*/
        conn.setAutoCommit(false);
        /*添加批量更新sql*/
        statement.addBatch(sql1);
        statement.addBatch(sql2);
        statement.addBatch(sql3);
        /*同時執行所有批量更新sql*/
        statement.executeBatch();
        /*提交事務*/
        conn.commit();
        /*還原自動提交狀態*/
        conn.setAutoCommit(autoCommitFlag);
        statement.close();
        conn.close();
        return;
    }
}

Ps:

  • 批量更新數據可以執行DDL語句和DML語句,針對DML語句建議在事務中處理,這樣可以保證批量處理數據的一致性!
  • 在使用批量更新數據的時候,需要先確認當前數據庫是否支持!可以通過DatabaseMetaData的supportsBatchUpdates()方法來查看!
  • 當批量更新數據量超過Interge.MAX_VALUE的時候,需要使用executeLargeUpdate()方法代替executeBatch()方法

 

2.6 JDBC獲取數據庫元數據

2.6.1 結果集的元數據:

通過ResultSet結果集的getMetaData()方法可以獲取對應結果集的元數據,可以通過ResultSetMetaData對象獲取到結果集的列相關信息!

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        Statement statement = conn.createStatement();
        ResultSet resultSet = statement.executeQuery("select * from person");
        ResultSetMetaData data = resultSet.getMetaData();
        /*獲取列個數*/
        System.out.println(data.getColumnCount());
        /*獲取列名*/
        System.out.println(data.getColumnName(1));
        /*獲取列類型*/
        System.out.println(data.getColumnType(1));

        resultSet.close();
        statement.close();
        conn.close(); 

        return;
    }
}

2.6.2 數據庫的元數據:

通過Connection連接類的getMetaData()方法,可以獲取到數據庫的元數據DatabaseMetaData對象,通過DatabaseMetaData對象可以獲取到數據庫的詳細信息!

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        DatabaseMetaData data = conn.getMetaData();
        /*獲取所有符合規則的數據表信息*/
        ResultSet rs = data.getTables(null,null,"%",new String[]{"TABLE"});
        while (rs.next()) {
            for (int i = 1; i < rs.getMetaData().getColumnCount(); i++) {
                System.out.println(rs.getMetaData().getColumnName(i) + " : " + rs.getString(i));
            }
        }
        rs.close();
        conn.close();
        return;
    }
}

Ps:DatabaseMetaData類其中的方法,基本上都是存在過濾條件的;可以使用null對象來忽略過濾條件,也可以使用"%"代替任意字符串、使用"_"代替任意一個字符!

2.6.3 使用系統表獲取數據庫信息:

常規數據都會有一個系統數據表,用來存放數據庫信息,可以通過系統數據表獲取對應的系統信息!

例如Mysql系統表如下:

 

2.7 數據庫連接池

頻繁的與數據庫的進行連接、關閉操作,會造成IO和性能的浪費;所以引入數據庫連接池的概念;常用的數據庫連接池有DBCP和C3P0!

2.7.1 DBCP連接池(說明文檔):

pom配置:

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-dbcp2</artifactId>
	<version>2.7.0</version>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-pool2</artifactId>
	<version>2.8.0</version>
</dependency>

代碼示例: 

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*創建數據源*/
        BasicDataSource ds = new BasicDataSource();
        /*設置驅動類*/
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        /*設置URL*/
        ds.setUrl("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        /*設置數據庫連接池的初始池子大小*/
        ds.setInitialSize(5);
        /*設置連接池最大連接個數*/
        ds.setMaxTotal(20);
        /*設置連接池最小空閒個數*/
        ds.setMinIdle(5);
        /*通過連接池獲取數據庫連接*/
        Connection conn = ds.getConnection();

        Statement stmt = conn.createStatement();
        boolean b = stmt.execute("SELECT * FROM person");
        ResultSet rs = null;
        if (b) {
            rs = stmt.getResultSet();
        }
        while ((null != rs) && (rs.next())) {
            System.out.println(rs.getString("id"));
            System.out.println(rs.getString("name"));
            System.out.println(rs.getString("info"));
            System.out.println(rs.getString("age"));
        }
        rs.close();
        stmt.close();
        conn.close();
        return;
    }
}

2.7.2 C3P0連接池(說明文檔):

pom配置:

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.2</version>
</dependency>

代碼示例:

public class JDBCTest {
    public static void main(String[] args) throws Exception  {
        /*創建數據源*/
        ComboPooledDataSource ds = new ComboPooledDataSource();
        /*設置驅動類*/
        ds.setDriverClass("com.mysql.jdbc.Driver");
        /*設置URL*/
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
        /*設置數據庫連接池的初始池子大小*/
        ds.setInitialPoolSize(5);
        /*設置連接池最大連接個數*/
        ds.setMaxPoolSize(20);
        /*設置連接池最小空閒個數*/
        ds.setMinPoolSize(5);
        /*通過連接池獲取數據庫連接*/
        Connection conn = ds.getConnection();

        Statement stmt = conn.createStatement();
        boolean b = stmt.execute("SELECT * FROM person");
        ResultSet rs = null;
        if (b) {
            rs = stmt.getResultSet();
        }
        while ((null != rs) && (rs.next())) {
            System.out.println(rs.getString("id"));
            System.out.println(rs.getString("name"));
            System.out.println(rs.getString("info"));
            System.out.println(rs.getString("age"));
        }
        rs.close();
        stmt.close();
        conn.close();
        return;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章