JAVA JDBC详解
一、相關概念
JDBC(Java Data Base Connectivity,java數據庫連接)是一種用于執行SQL語句的Java API,可以為多種關系數據庫提供統一訪問,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準,據此可以構建更高級的工具和接口,使數據庫開發人員能夠編寫數據庫應用程序。JDBC制定了統一訪問各類關系數據庫的標準接口,為各個數據庫廠商提供了標準接口的實現。
JDBC規范將驅動程序歸結為以下幾類(選自Core Java Volume Ⅱ——Advanced Features):
- 第一類驅動程序將JDBC翻譯成ODBC,然后使用一個ODBC驅動程序與數據庫進行通信。
- 第二類驅動程序是由部分Java程序和部分本地代碼組成的,用于與數據庫的客戶端API進行通信。
- 第三類驅動程序是純Java客戶端類庫,它使用一種與具體數據庫無關的協議將數據庫請求發送給服務器構件,然后該構件再將數據庫請求翻譯成數據庫相關的協議。
- 第四類驅動程序是純Java類庫,它將JDBC請求直接翻譯成數據庫相關的協議。
我們安裝好數據庫之后,我們的應用程序也是不能直接使用數據庫的,必須要通過相應的數據庫驅動程序,通過驅動程序去和數據庫打交道。其實也就是數據庫廠商的JDBC接口實現,即對Connection等接口的實現類的jar文件。
二、常用接口
Driver接口由數據庫廠家提供,作為java開發人員,只需要使用Driver接口就可以了。在編程中要連接數據庫,必須先裝載特定廠商的數據庫驅動程序,不同的數據庫有不同的裝載方法。如:
-
裝載MySql驅動:Class.forName(“com.mysql.jdbc.Driver”);
-
裝載Oracle驅動:Class.forName(“oracle.jdbc.driver.OracleDriver”);
-
完整:
Connection與特定數據庫的連接(會話),在連接上下文中執行sql語句并返回結果。DriverManager.getConnection(url, user, password)方法建立在JDBC URL中定義的數據庫Connection連接上。
-
連接MySql數據庫:Connection conn = DriverManager.getConnection(“jdbc:mysql://host:port/database”, “user”, “password”);
-
連接Oracle數據庫:Connection conn = DriverManager.getConnection(“jdbc:oracle:thin:@host:port:database”, “user”, “password”);
-
連接SqlServer數據庫:
Connection conn = DriverManager.getConnection(“jdbc:microsoft:sqlserver://host:port; DatabaseName=database”, “user”, “password”); -
完整實例:
常用方法:
- createStatement():創建向數據庫發送sql的statement對象。
- prepareStatement(sql):創建向數據庫發送預編譯sql的PrepareSatement對象。
- prepareCall(sql):創建執行存儲過程的callableStatement對象。
- setAutoCommit(boolean autoCommit):設置事務是否自動提交。
- commit() :在鏈接上提交事務。
- rollback() :在此鏈接上回滾事務。
用于執行靜態SQL語句并返回它所生成結果的對象。
三種Statement類:
- Statement:由createStatement創建,用于發送簡單的SQL語句(不帶參數)。
- PreparedStatement :繼承自Statement接口,由preparedStatement創建,用于發送含有一個或多個參數的SQL語句。PreparedStatement對象比Statement對象的效率更高,并且可以防止SQL注入,所以我們一般都使用PreparedStatement。
- CallableStatement:繼承自PreparedStatement接口,由方法prepareCall創建,用于調用存儲過程。
常用Statement方法:
- execute(String sql):運行語句,返回是否有結果集
- executeQuery(String sql):運行select語句,返回ResultSet結果集。
- executeUpdate(String sql):運行insert/update/delete操作,返回更新的行數。
- addBatch(String sql) :把多條sql語句放到一個批處理中。
- executeBatch():向數據庫發送一批sql語句執行。
ResultSet提供檢索不同類型字段的方法,常用的有:
- getString(int index)、getString(String columnName):獲得在數據庫里是varchar、char等類型的數據對象。
- getFloat(int index)、getFloat(String columnName):獲得在數據庫里是Float類型的數據對象。
- getDate(int index)、getDate(String columnName):獲得在數據庫里是Date類型的數據。
- getBoolean(int index)、getBoolean(String columnName):獲得在數據庫里是Boolean類型的數據。
- getObject(int index)、getObject(String columnName):獲取在數據庫里任意類型的數據。
ResultSet還提供了對結果集進行滾動的方法:
- next():移動到下一行 Previous():移動到前一行 absolute(int row):移動到指定行
- beforeFirst():移動resultSet的最前面。 afterLast() :移動到resultSet的最后面。
使用后依次關閉對象及連接:ResultSet → Statement → Connection
三、使用JDBC的步驟
加載JDBC驅動程序 → 建立數據庫連接Connection(可以從配置文件中獲取參數) → 創建執行SQL的語句Statement → 處理執行結果ResultSet → 釋放資源- 方式一:Class.forName(“com.MySQL.jdbc.Driver”)推薦這種方式,不會對具體的驅動類產生依賴。
- 方式二:DriverManager.registerDriver(com.mysql.jdbc.Driver)會造成DriverManager中產生兩個一樣的驅動,并會對具體的驅動類產生依賴。
Connection conn = DriverManager.getConnection(url, user, password);
URL用于標識數據庫的位置,通過URL地址告訴JDBC程序連接哪個數據庫,URL寫法
其他參數如:useUnicode=true&characterEncoding=utf8
5.釋放資源
//數據庫連接(Connection)非常耗資源,盡量晚創建,盡量早的釋放//都要加try catch 以防前面關閉出錯,后面的就不執行了 try {if (rs != null) {rs.close();} } catch (SQLException e) {e.printStackTrace(); } finally {try {if (st != null) {st.close();}} catch (SQLException e) {e.printStackTrace();} finally {try {if (conn != null) {conn.close();}} catch (SQLException e) {e.printStackTrace();}} }四、事務(ACID特點、隔離級別、提交commit、回滾rollback)
五、完整例子
import java.io.*; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;public class DBUtil {private final String dbConnFile = "resource/database/jdbc.properties";private Connection conn=null;private String dbDriver; //定義驅動private String dbURL; //定義URLprivate String userName; //定義用戶名private String password; //定義密碼//從配置文件取數據庫鏈接參數private void loadConnProperties(){Properties props = new Properties();try {props.load(new FileInputStream(dbConnFile));//根據配置文件路徑Conf加載配置文件} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}this.dbDriver = props.getProperty("driver");//從配置文件中取得相應的參數并設置類變量this.dbURL = props.getProperty("url");this.userName = props.getProperty("username");this.password = props.getProperty("password");}public boolean openConnection(){try {loadConnProperties();Class.forName(dbDriver);this.conn = DriverManager.getConnection(dbURL,userName,password);return true;} catch(ClassNotFoundException classnotfoundexception) {classnotfoundexception.printStackTrace();System.err.println("db: " + classnotfoundexception.getMessage());} catch(SQLException sqlexception) {System.err.println("db.getconn(): " + sqlexception.getMessage());}return false;}protected void finalize() throws Exception{try {if(null!=conn)conn.close();}catch (SQLException e) {e.printStackTrace();}}// 查詢并得到結果集public ResultSet execQuery(String sql) throws Exception {ResultSet rstSet = null;try {if (null == conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();rstSet = stmt.executeQuery(sql);} catch (SQLException e) {e.printStackTrace();}return rstSet;}// 插入一條新紀錄,并獲取標識列的值public ResultSet getInsertObjectIDs(String insertSql) throws Exception{ResultSet rst = null;try {if(null==conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();stmt.executeUpdate(insertSql, Statement.RETURN_GENERATED_KEYS);rst = stmt.getGeneratedKeys();} catch (SQLException e) {e.printStackTrace();}return rst;}//以參數SQL模式插入新紀錄,并獲取標識列的值public ResultSet getInsertObjectIDs(String insertSql, Object[] params) throws Exception {ResultSet rst = null;PreparedStatement pstmt = null ;try {if (null == conn)throw new Exception("Database not connected!");pstmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS);if(null != params){for (int i = 0; i < params.length; i++) {pstmt.setObject(i + 1, params[i]);}}pstmt.executeUpdate();rst = pstmt.getGeneratedKeys();} catch (SQLException e) {e.printStackTrace();}return rst;}// 插入、更新、刪除public int execCommand(String sql) throws Exception{int flag = 0;try {if(null==conn)throw new Exception("Database not connected!");Statement stmt = conn.createStatement();flag = stmt.executeUpdate(sql);stmt.close();} catch (SQLException e) {e.printStackTrace();}return flag;}/* // 存儲過程調用public void callStordProc(String sql, Object[] inParams, SqlParameter[] outParams) throws Exception {CallableStatement cst = null ;try {if (null == conn)throw new Exception("Database not connected!");cst = conn.prepareCall(sql);if(null != inParams){for (int i = 0; i < inParams.length; i++) {cst.setObject(i + 1, inParams[i]);}}if (null!=outParams){for (int i = 0; i < inParams.length; i++) {cst.registerOutParameter(outParams[i].getName(), outParams[i].getType());}}cst.execute();} catch (SQLException e) {e.printStackTrace();}} */// 釋放資源public void close(ResultSet rst) throws Exception {try {Statement stmt = rst.getStatement();rst.close();stmt.close();} catch (SQLException e) {e.printStackTrace();}}public PreparedStatement execPrepared(String psql) throws Exception {PreparedStatement pstmt = null ;try {if (null == conn)throw new Exception("Database not connected!");pstmt = conn.prepareStatement(psql);} catch (SQLException e) {e.printStackTrace();}return pstmt;}// 釋放資源public void close(Statement stmt) throws Exception {try {stmt.close();} catch (SQLException e) {e.printStackTrace();}}// 釋放資源public void close() throws SQLException, Exception{if(null!=conn){conn.close();conn=null;}}public Connection getConn() {return conn;}public static void main(String[] args) {} }六、統一總結
什么是JDBC?JDBC是Java DataBase Connectivity的縮寫,它是Java程序訪問數據庫的標準接口。使用Java程序訪問數據庫時,java代碼并不是直接通過TCP連接去訪問數據庫,而是通過JDBC接口來訪問,而JDBC接口則通過JDBC驅動來實現真正對數據庫的訪問。
例如,我們在Java代碼中如果要訪問MySQL,那么必須編寫代碼操作JDBC接口。注意到JDBC接口是Java標準庫自帶的,所以可以直接編譯。而具體的JDBC驅動是由數據庫廠商提供的,例如,MySQL的JDBC驅動由Oracle提供。因此,訪問某個具體的數據庫,我們只需要引入該廠商提供的JDBC驅動,就可以通過JDBC接口來訪問,這樣保證了Java程序編寫的是一套數據庫訪問代碼,卻可以訪問各種不同的數據庫,因為他們都提供了標準的JDBC驅動:
Java標準庫自帶的JDBC接口其實就是定義了一組接口,而某個具體的JDBC驅動其實就是實現了這些接口的類
實際上,一個MySQL的JDBC的驅動就是一個jar包,它本身也是純Java編寫的。我們自己編寫的代碼只需要引用Java標準庫提供的java.sql包下面的相關接口,由此再間接地通過MySQL驅動的jar包通過網絡訪問MySQL服務器,所有復雜的網絡通訊都被封裝到JDBC驅動中,因此,Java程序本身只需要引入一個MySQL驅動的jar包就可以正常訪問MySQL服務器:
所以我們首先得找一個MySQL的JDBC驅動。所謂JDBC驅動,其實就是一個第三方jar包,我們直接添加一個Maven依賴就可以了:
注意到這里添加依賴的scope是runtime,因為編譯Java程序并不需要MySQL的這個jar包,只有在運行期才需要使用。如果把runtime改成compile,雖然也能正常編譯,但是在IDE里寫程序的時候,會多出來一大堆類似com.mysql.jdbc.Connection這樣的類,非常容易與Java標準庫的JDBC接口混淆,所以堅決不要設置為compile。
- 獲取鏈接
核心代碼是DriverManager提供的靜態方法getConnection()。DriverManager會自動掃描classpath,找到所有的JDBC驅動,然后根據我們傳入的URL自動挑選一個合適的驅動。
因為JDBC連接是一種昂貴的資源,所以使用后要及時釋放。使用try (resource)來自動釋放JDBC連接是一個好方法
獲取到JDBC連接后,下一步我們就可以查詢數據庫了。查詢數據庫分以下幾步:
-
第一步,通過Connection提供的createStatement()方法創建一個Statement對象,用于執行一個查詢;
-
第二步,執行Statement對象提供的executeQuery("SELECT * FROM students")并傳入SQL語句,執行查詢并獲得返回的結果集,使用ResultSet來引用這個結果集;
-
第三步,反復調用ResultSet的next()方法并讀取每一行結果。
demo:
try (Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD)) {try (Statement stmt = conn.createStatement()) {try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) {while (rs.next()) {long id = rs.getLong(1); // 注意:索引從1開始long grade = rs.getLong(2);String name = rs.getString(3);int gender = rs.getInt(4);}}} }注意使用后依次關閉對象及連接:ResultSet → Statement → Connection
- SQL注入風險
使用Statement拼字符串非常容易引發SQL注入的問題,這是因為SQL參數往往是從方法參數傳入的。
//Statement String id = "5"; String sql = "delete from table where id=" + id; Statement st = conn.createStatement(); st.executeQuery(sql); //存在sql注入的危險 //如果用戶傳入的id為“5 or 1=1”,那么將刪除表中的所有記錄PreparedStatement 有效的防止sql注入(SQL語句在程序運行前已經進行了預編譯,
//當運行時動態地把參數傳給PreprareStatement時,即使參數里有敏感字符如 //or '1=1'數據庫也會作為一個參數一個字段的屬性值來處理而不會作為一個SQL指令) String sql = “insert into user (name,pwd) values(?,?)”; PreparedStatement ps = conn.preparedStatement(sql); ps.setString(1, “col_value”); //占位符順序從1開始 ps.setString(2, “123456”); //也可以使用setObject ps.executeQuery();我們在講多線程的時候說過,創建線程是一個昂貴的操作,如果有大量的小任務需要執行,并且頻繁地創建和銷毀線程,實際上會消耗大量的系統資源,往往創建和消耗線程所耗費的時間比執行任務的時間還長,所以,為了提高效率,可以用線程池。類似的,在執行JDBC的增刪改查的操作時,如果每一次操作都來一次打開連接,操作,關閉連接,那么創建和銷毀JDBC連接的開銷就太大了。為了避免頻繁地創建和銷毀JDBC連接,我們可以通過連接池(Connection Pool)復用已經創建好的連接。
JDBC連接池有一個標準的接口javax.sql.DataSource,注意這個類位于Java標準庫中,但僅僅是接口。要使用JDBC連接池,我們必須選擇一個JDBC連接池的實現。常用的JDBC連接池有:
- HikariCP
- C3P0
- BoneCP
- Druid
目前使用最廣泛的是HikariCP。我們以HikariCP為例,要使用JDBC連接池,先添加HikariCP的依賴如下:
<dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>2.7.1</version> </dependency>緊接著,我們需要創建一個DataSource實例,這個實例就是連接池:
HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/test"); config.setUsername("root"); config.setPassword("password"); config.addDataSourceProperty("connectionTimeout", "1000"); // 連接超時:1秒 config.addDataSourceProperty("idleTimeout", "60000"); // 空閑超時:60秒 config.addDataSourceProperty("maximumPoolSize", "10"); // 最大連接數:10 DataSource ds = new HikariDataSource(config);注意創建DataSource也是一個非常昂貴的操作,所以通常DataSource實例總是作為一個全局變量存儲,并貫穿整個應用程序的生命周期。
有了連接池以后,我們如何使用它呢?和前面的代碼類似,只是獲取Connection時,把DriverManage.getConnection()改為ds.getConnection():
try (Connection conn = ds.getConnection()) { // 在此獲取連接... } // 在此“關閉”連接通過連接池獲取連接時,并不需要指定JDBC的相關URL、用戶名、口令等信息,因為這些信息已經存儲在連接池內部了(創建HikariDataSource時傳入的HikariConfig持有這些信息)。一開始,連接池內部并沒有連接,所以,第一次調用ds.getConnection(),會迫使連接池內部先創建一個Connection,再返回給客戶端使用。當我們調用conn.close()方法時(在try(resource){...}結束處),不是真正“關閉”連接,而是釋放到連接池中,以便下次獲取連接時能直接返回。
因此,連接池內部維護了若干個Connection實例,如果調用ds.getConnection(),就選擇一個空閑連接,并標記它為“正在使用”然后返回,如果對Connection調用close(),那么就把連接再次標記為“空閑”從而等待下次調用。這樣一來,我們就通過連接池維護了少量連接,但可以頻繁地執行大量的SQL語句。通常連接池提供了大量的參數可以配置,例如,維護的最小、最大活動連接數,指定一個連接在空閑一段時間后自動關閉等,需要根據應用程序的負載合理地配置這些參數。此外,大多數連接池都提供了詳細的實時狀態以便進行監控。
參考文章1
參考文章2
參考文章3
總結
以上是生活随笔為你收集整理的JAVA JDBC详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IO流——流的分类、InputStrea
- 下一篇: 产品经理和程序员之间的“潜台词”,你能听