JDBC入門講解
JDBC簡述
JDBC是Java DataBase Connectivity的簡稱,它可以賦予Java操作資料庫的能力。
JDBC並不是單純的一套介面,它更是一套規範,不同的資料庫廠商都需要遵守這個規範。
不同資料庫廠商根據自己的資料庫的特點給出了這套規範的具體實現,這個具體實現驅動。
而作為Java的使用者,我們只需要學習一套JDBC API就可以了。
Hello JDBC
環境資訊
-
jdk 1.8
-
mysql 8.0.13
-
IntelliJ IDEA 2019.1.4
操作步驟
下載資料庫驅動
因為我本地的資料庫是mysql 8.0.13,所以這裡使用的是mysql-connector-java-8.0.16
下載地址:https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.16
建立一個普通專案
新建lib目錄 → 將驅動複製到lib中 → 將lib目錄新增到專案庫中
編寫程式碼
- mysql 8.0建議使用com.mysql.cj.jdbc.Driver驅動
- mysql 8.0在連線資料庫時必須設定時區
package com.kdy; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * Author: kdy * Date: Created in 2021/7/12 22:03 */ public class HelloJDBC { public static void main(String[] args) throws ClassNotFoundException, SQLException { // 1.載入驅動;mysql 8.0建議使用com.mysql.cj.jdbc.Driver驅動 Class.forName("com.mysql.cj.jdbc.Driver"); // 2.配置連線資訊 // useUnicode=true:支援useUnicode編碼支援中文 // characterEncoding=utf-8:設定字符集編碼防止亂碼 // serverTimezone=Hongkong:設定時區,mysql8.0必須設定時區 String url = "jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8"; String username = "root"; String password = "123456"; // 3.建立資料庫連線,Connection就代表資料庫 Connection connection = DriverManager.getConnection(url, username, password); // 4.建立執行SQL的物件,Statement代表執行SQL的物件 Statement statement = connection.createStatement(); // 5.執行SQL,檢視返回結果;ResultSet代表返回的結果集 String sql = "select * from user"; ResultSet resultSet = statement.executeQuery(sql); while (resultSet.next()){ System.out.println("-------kdy------"); System.out.println("id is " + resultSet.getObject("id")); System.out.println("name is " + resultSet.getObject("name")); System.out.println("pwd is " + resultSet.getObject("pwd")); System.out.println("-------kdy------"); } // 6.釋放連線 resultSet.close(); statement.close(); connection.close(); } }
程式碼優化
在src目錄下建立配置檔案db.properties,將配置資訊全部提取到配置檔案中
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
編寫工具類JDBCUtils
package com.utils; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; /** * Author: kdy * Date: Created in 2021/7/12 23:51 */ public class JDBCUtils { private static String driver = null; private static String url = null; private static String username = null; private static String password = null; static { try{ InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("db.properties"); Properties properties = new Properties(); properties.load(in); driver = properties.getProperty("driver"); url = properties.getProperty("url"); username = properties.getProperty("username"); password = properties.getProperty("password"); // 資料庫驅動只用載入一次,所以放在這個位置載入 Class.forName(driver); } catch (IOException e) { System.err.println("資料庫配置檔案db.properties載入失敗"); } catch (ClassNotFoundException e) { System.err.println("資料庫驅動" + driver + "載入失敗"); } } // 獲取資料庫連線 public static Connection getConnection(){ Connection connection = null; try { connection = DriverManager.getConnection(url, username, password); } catch (SQLException e) { System.err.println("獲取資料庫連線失敗"); } return connection; } // 釋放資料庫連線 public static void closed(Connection conn, Statement state, ResultSet result){ if(result != null){ try { result.close(); } catch (SQLException e) { System.err.println("資源ResultSet釋放失敗"); } } if(state != null){ try { state.close(); } catch (SQLException e) { System.err.println("資源Statement釋放失敗"); } } if(conn != null){ try { conn.close(); } catch (SQLException e) { System.err.println("資源Connection釋放失敗"); } } } }
功能實現
package com.kdy;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* Author: kdy
* Date: Created in 2021/7/13 0:13
*/
public class HelloJDBCoptimization {
public static void main(String[] args) throws Exception {
Connection connection = JDBCUtils.getConnection();
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("select * from user");
while (resultSet.next()){
System.out.println("-------kdy-coptimization------");
System.out.println("id is " + resultSet.getObject("id"));
System.out.println("name is " + resultSet.getObject("name"));
System.out.println("pwd is " + resultSet.getObject("pwd"));
}
JDBCUtils.closed(connection,statement,resultSet);
}
}
JDBC API
介面 | 說明 |
---|---|
DriverManager | 用於管理一組JDBC驅動程式的基本服務 |
Connection | 與特定資料庫的連線(會話)。 執行SQL語句並在連線的上下文中返回結果 |
Statement | 用於執行靜態SQL語句並返回其生成的結果的物件 |
ResultSet | 表示資料庫結果集的資料表,通常通過執行查詢資料庫的語句生成 |
DriverManager
DriverManager主要功能有兩個
- 註冊驅動
- 獲取資料庫連線Connection
JDBC 2.0 API中新增的DataSource
介面提供了另一種連線到資料來源的方法,具體內容見資料庫連線池
關於獲取Connection沒有什麼可以說的,通過getConnection()方法就可以獲取Connection物件
我們來看看DriverManager是如何完成載入驅動的,因為我們上述程式碼在載入驅動時並沒有發現DriverManager的參與,其實驅動載入的標準寫法應該是
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
但是我們通過原始碼可以發現com.mysql.cj.jdbc.Driver()類實際上是一個靜態程式碼塊,其中的內容就包括了標準寫法,所以我們只需要操作類載入即可自動完成驅動的註冊
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
Connection
在建立資料庫連線時我們需要注意url的編寫,不同資料庫的url寫法不一致;且mysql 8.0在連線時必須設定好時區
生成的Connection物件就代表著資料庫,通過這個物件可以完成事務的自動提交、事務回滾等操作
Statement
Statement物件用於將Java中的SQL傳遞給資料庫,用於執行
Statement一共有三種類型
- Statement:用於執行不帶引數的簡單SQL語句
- PreparedStatement(從 Statement 繼承):用於執行帶或不帶引數的預編譯SQL語句
- CallableStatement(從PreparedStatement 繼承):用於執行資料庫儲存過程的呼叫
Statement常用方法
方法 | 說明 |
---|---|
ResultSet executeQuery(String sql) | 執行查詢操作,返回的資料會放在ResultSet物件中 |
boolean execute(String sql) | 執行任意型別sql,返回boolean,表示是否返回ResultSet物件 |
int executeUpdate(String sql) | 執行插入、刪除、更新等操作,返回值為影響的行數 |
PreparedStatement
如果通過Statement去執行SQL很容易出現SQL注入的問題
為了解決這一問題我們可以通過PreparedStatement來執行SQL語法
PreparedStatement防止SQL注入的本質就是把引數的引數當作字元
具體使用方式如下
package com.kdy;
import com.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Author: kdy
* Date: Created in 2021/7/13 0:48
*/
public class PreparedStatementTest {
public static void main(String[] args) throws SQLException {
Connection conn = JDBCUtils.getConnection();
// 預編譯sql,先寫sql不執行,使用?佔位符代替引數
String sql = "select * from user where id = ? and name = ?";
PreparedStatement preState = conn.prepareStatement(sql);
// 手動給引數進行賦值,第一個引數表示給第幾個?賦值
preState.setInt(1,1);
preState.setString(2,"張三");
// 執行sql
ResultSet resultSet = preState.executeQuery();
while (resultSet.next()){
System.out.println("-------kdy-preparedStatementTest------");
System.out.println("id is " + resultSet.getObject("id"));
System.out.println("name is " + resultSet.getObject("name"));
System.out.println("pwd is " + resultSet.getObject("pwd"));
}
JDBCUtils.closed(conn,preState,resultSet);
}
}
ResultSet
Statement執行查詢後會返回ResultSet
ResultSet中會存放查詢後返回資訊,存放的資料會像資料庫一樣排列,且有一個記錄指標
我們可以通過裡面的指標操作行資料,通過列的序列或者列名操作列資料
ResultSet 常用方法
方法 | 說明 |
---|---|
boolean next() | 將指標向下移動並返回此位置是否有值 |
boolean previous() | 將指標向上移動並返回此位置是否有值 |
int getInt(int colIndex) | 根據列的id獲取資料,資料以int的形式返回 |
int getInt(int colLabel) | 根據列的名字獲取資料,資料以int的形式返回 |
...... | ...... |
JDBC事務
首先需要開啟事務conn.setAutoCommit(false);關閉資料庫的自動提交,自動開啟事務
其次編寫業務程式碼
如果全部成功則提交conn.commit();
如果存在失敗的則回滾conn.rollback();【預設失敗就會回滾】
資料庫連線池
在DriverManager類的文件註釋上有這樣一句話
JDBC 2.0 API中新增的
DataSource
介面提供了另一種連線到資料來源的方法,具體內容見資料庫連線池
所以DataSource可以取代DriverManager,他們都是連線到資料庫的方法
資料庫連線池DatSources負責分配、管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是再重新建立一個;釋放空閒時間超過最大空閒時間的資料庫連線來避免因為沒有釋放資料庫連線而引起的資料庫連線遺漏。這項技術能明顯提高對資料庫操作的效能。
編寫連線池只需要實現DataSources介面即可,當然我們可以使用一些開源的資料來源實現,常見的資料來源實現有DBCP、C3P0、Druid
DBCP
首先我們需要兩個jar包commons-dbcp.jar和commons-pool.jar
- commons-dbcp.jar:https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp/1.4
- commons-pool.jar:https://mvnrepository.com/artifact/commons-pool/commons-pool/1.6
核心配置檔案
# 連線設定,這些名字都是DBCP資料來源定義好的
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8
username=root
password=123456
# 初始化連線
initialSize=10
# 最大連線數量
maxActive=50
# 最大空閒連線
maxIdle=20
# 最小空閒連線
minIdle=5
# 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒
maxWait=60000
# JDBC驅動建立連線時附帶的連線屬性屬性的格式必須為這樣:【屬性名=property;】
# 注意:user 與 password 兩個屬性會被明確地傳遞,因此這裡不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=UTF8
# 指定由連線池所建立的連線的自動提交(auto-commit)狀態。
defaultAutoCommit=true
# driver default 指定由連線池所建立的連線的只讀(read-only)狀態。
# 如果沒有設定該值,則“setReadOnly”方法將不被呼叫。(某些驅動並不支援只讀模式,如:Informix)
# defaultReadOnly=
# driver default 指定由連線池所建立的連線的事務級別(TransactionIsolation)。
# 可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
修改我們的獲取Connection的工具類程式碼
package com.utils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Author: kdy
* Date: Created in 2021/7/14 11:25
*/
public class JDBCUtils_DBCP {
private static DataSource dataSource = null;
static {
try{
InputStream in = JDBCUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 獲取資料庫連線
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
System.err.println("獲取資料庫連線失敗");
}
return connection;
}
// 釋放資料庫連線
public static void closed(Connection conn, Statement state, ResultSet result){
if(result != null){
try {
result.close();
} catch (SQLException e) {
System.err.println("資源ResultSet釋放失敗");
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
System.err.println("資源Statement釋放失敗");
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
System.err.println("資源Connection釋放失敗");
}
}
}
}
C3P0
首先我們需要兩個jar包c3p0.jar和commons-pool.jar
- c3p0.jar:https://mvnrepository.com/artifact/com.mchange/c3p0/0.9.5.5
- mchange-commons-java.jar:https://mvnrepository.com/artifact/com.mchange/mchange-commons-java/0.2.19
核心配置檔案,c3p0會自動掃描src目錄下的配置檔案
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- 沒有name,如果沒有指定使用哪個配置,則使用這個預設配置 -->
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="checkoutTimeout">30000</property>
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">3</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">2</property>
<property name="maxStatements">200</property>
</default-config>
<!-- 命名的配置,可以通過方法呼叫實現 -->
<named-config name="mysql">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybatis?serverTimezone=Hongkong&useUnicode=true&characterEncoding=utf-8</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 如果池中資料連線不夠時一次增長多少個 -->
<property name="acquireIncrement">5</property>
<!-- 初始化資料庫連線池時連線的數量 -->
<property name="initialPoolSize">20</property>
<!-- 資料庫連線池中的最大的資料庫連線數 -->
<property name="maxPoolSize">25</property>
<!-- 資料庫連線池中的最小的資料庫連線數 -->
<property name="minPoolSize">5</property>
</named-config>
</c3p0-config>
修改我們的獲取Connection的工具類程式碼
package com.utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Author: kdy
* Date: Created in 2021/7/14 12:19
*/
public class JDBCUtils_C3P0 {
private static ComboPooledDataSource dataSource = null;
static {
try{
// 使用xml配置
dataSource = new ComboPooledDataSource("mysql");
// 使用java配置
// dataSource = new ComboPooledDataSource();
// dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
// dataSource.setUser("root");
// ....
} catch (Exception e) {
e.printStackTrace();
}
}
// 獲取資料庫連線
public static Connection getConnection(){
Connection connection = null;
try {
connection = dataSource.getConnection();
} catch (SQLException e) {
System.err.println("獲取資料庫連線失敗");
}
return connection;
}
// 釋放資料庫連線
public static void closed(Connection conn, Statement state, ResultSet result){
if(result != null){
try {
result.close();
} catch (SQLException e) {
System.err.println("資源ResultSet釋放失敗");
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
System.err.println("資源Statement釋放失敗");
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
System.err.println("資源Connection釋放失敗");
}
}
}
}
GitHub程式碼地址:https://gitee.com/jwyddr2/cnblogs-kdy/tree/master/jdbc/jdbc-kdy
參考
https://www.bilibili.com/video/BV1NJ411J79W?p=44