本篇博客主要介紹JDBC的簡單使用。
什麼是JDBC
JDBC,即Java DataBase Connectivity,Java數據庫連接。是一種用於執行SQL語句的Java API,它是Java中的數據庫連接規範。這個API由java.sql.*
,javax.sql.*
包中的一些類和接口組成,它爲Java開發人員操作數據庫提供了一個標準的API,可以爲多種關係型數據庫提供統一訪問。
數據庫編程必備條件
- 編程語言:如Java、C++、Python等;
- 數據庫:如MySQL、Oracle、SQL Server等;
- 數據庫驅動包:不同的數據庫,對應不同的編程語言提供了不同的數據庫驅動包,如:MySQL提供了Java的驅動包mysql-connector-java,如果需要使用Java操作MySQL則需要該驅動包。同樣的,如果要操作Oracle數據庫則需要Oracle數據庫驅動包。
JDBC工作原理
JDBC爲多種數據庫提供了統一訪問方式,作爲特定廠商數據庫訪問API的一種高級抽象,它主要包含一些通用的接口類。
JDBC訪問數據庫層次結構:
JDBC優勢
- Java語言訪問數據庫操作完全面向抽象接口編程;
- 開發數據庫應用不用限定在特定數據庫廠商的API;
- 程序的可移植性大大增強。
JDBC的簡單使用
首先,我們準備數據庫驅動包,並添加到項目的依賴中。
MySQL數據庫驅動包
在項目中創建文件夾lib,並將依賴包mysql-connector-java-5.1.47.jar複製到lib中;右鍵項目選擇Open Modules Settings;
點擊Dependencies,然後點擊右邊的加號,選擇JARs or directiories…
選擇前面拷貝到lib中的.jar文件,然後點擊Apply,最後點擊OK。
然後,我們建立一個數據庫連接。
// 加載JDBC驅動程序:反射,這樣調用初始化com.mysql.jdbc.Driver類
// 即將該類加載到JVM方法區,並執行該類的靜態方法塊、靜態屬性
Class.forName("com.mysql.jdbc.Driver");
// 創建數據庫連接
// MySQL數據庫連接的URL參數格式如下:
// jbdc:mysql://服務器地址:端口號/數據庫名?參數名=參數值
Connection connection = DriverManager.getConnection(url, user, password);
然後,我們創建操作命令。
Statement statement = connection.createStatement();
然後,我們執行SQL語句。
ResultSet resultSet = statement.executeQuery(
"select id, name from student"
);
最後,我們來處理結果集。
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.printf("id: %d, name: %s\n", id, name);
}
還有,不要忘了釋放資源。
// 關閉結果集
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 關閉命令
if (statement != null) {
try {
statement.close();
} catch (SQLExecption e) {
e.printStackTrace();
}
}
// 關閉連接命令
if (connection != null) {
try {
connection.close();
} catch (SQLExecption e) {
e.printStackTrace();
}
}
完整代碼如下:
import java.sql.*;
public class Test {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
private static final String USER_NAME = "root";
private static final String PASSWORD = "1";
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
statement = connection.createStatement();
String sql = "select id, name from student";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.printf("id: %d, name: %s\n", id, name);
}
} finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
}
JDBC使用步驟總結如下
- 創建數據庫連接connection;
- 創建操作命令Statement;
- 使用操作命令來執行SQL;
- 處理結果集ResultSet;
- 釋放資源。
兩種創建數據庫連接的方式
通過DriverManager(驅動管理類)的靜態方法獲取
我們直接來看代碼:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
private static final String USER_NAME = "root";
private static final String PASSWORD = "1";
public static Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
return connection;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
throw new RuntimeException("數據庫連接失敗");
}
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection();
statement = connection.createStatement();
String sql = "select id, name from student";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.printf("id: %d, name: %s\n", id, name);
}
} finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
通過DataSource對象獲取
通過DataSource獲取,要求是同一個DataSource對象,所以這裏,我們有兩種實現方式:
使用靜態屬性的方式實現
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
private static final String USER_NAME = "root";
private static final String PASSWORD = "1";
private static DataSource dataSource = new MysqlDataSource();
static {
((MysqlDataSource)dataSource).setUrl(URL);
((MysqlDataSource)dataSource).setUser(USER_NAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
public static Connection getConnection() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
throw new RuntimeException("數據庫連接失敗");
}
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection();
statement = connection.createStatement();
String sql = "select id, name from student";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.printf("id: %d, name: %s\n", id, name);
}
} finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
使用單例模式實現
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/java12?useSSL=false";
private static final String USER_NAME = "root";
private static final String PASSWORD = "1";
private DBUtil() {}
private static DataSource dataSource = null;
public static DataSource getDataSource() {
if (dataSource == null) {
dataSource = new MysqlDataSource();
((MysqlDataSource)dataSource).setUrl(URL);
((MysqlDataSource)dataSource).setUser(USER_NAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
return dataSource;
}
}
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
DataSource dataSource = DBUtil.getDataSource();
connection = dataSource.getConnection();
statement = connection.createStatement();
String sql = "select id, name from student";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.printf("id: %d, name: %s\n", id, name);
}
} finally {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
if (connection != null)
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
兩種方式的區別
- 使用DriverManager類來獲取的Connection連接,是無法重複利用的,每次使用完以後釋放資源時,通過connection.close()都是關閉物理連接;
- DataSource提供連接池的支持。連接池在初始化時將創建一定量的數據庫連接,這些連接是可以重複利用的,每次使用完數據庫連接,釋放資源調用connection.close()都是將Connection連接對象回收。
DBUtil類
我們設計一個DBUtil類用來獲取數據庫連接。包含上述幾種方式:
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import javax.sql.DataSource;
import java.sql.*;
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
private static final String USER_NAME = "root";
private static final String PASSWORD = "1";
public static Connection getConnection1() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
throw new RuntimeException("數據庫連接失敗");
}
private static DataSource dataSource = new MysqlDataSource();
static {
((MysqlDataSource)dataSource).setUrl(URL);
((MysqlDataSource)dataSource).setUser(USER_NAME);
((MysqlDataSource)dataSource).setPassword(PASSWORD);
}
public static Connection getConnection2() {
try {
return dataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
throw new RuntimeException("數據庫連接失敗");
}
private DBUtil() {}
private static DataSource dataSource2 = null;
public static DataSource getDataSource() {
if (dataSource2 == null) {
dataSource2 = new MysqlDataSource();
((MysqlDataSource)dataSource2).setUrl(URL);
((MysqlDataSource)dataSource2).setUser(USER_NAME);
((MysqlDataSource)dataSource2).setPassword(PASSWORD);
}
return dataSource2;
}
public static void Close(Connection connection, Statement statement, ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException("數據庫資源釋放失敗");
}
}
}
可以看到,我們在DBUtil類中添加了一個Close方法,該方法用來關閉數據庫的一些資源在數據庫不再使用的時候。
因爲前面的關閉是有問題的,假如resultSet.close()
操作拋了異常,那麼後續的兩個關閉操作將不會被執行,此時就會造成資源泄露。因此我們在DBUtil中封裝一個方法專門用來釋放資源,如果中間有資源釋放失敗,就向上層拋一個運行時異常來提醒調用者資源釋放失敗。
JDBC常用的接口和類
Statement對象
Statement對象主要是將SQL語句發送到數據庫中。JDBC API中主要提供了三種Statement對象:
- Statement:用於執行不帶參數的簡單SQL語句;
- PreparedStatement:用於執行帶或者不帶參數的SQL語句;SQL語句會預編譯在數據庫系統;執行速度快於Statement對象;
- CallableStatement:用於執行數據庫存儲過程的調用。
SQL注入問題
下面我們來封裝一個類,用戶可以通過傳入一個姓名然後就可以獲得一個指定姓名的信息。
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Query {
public static void main(String[] args) {
query("範莊元");
}
public static void query(String stuName) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
statement = connection.createStatement();
String sql = "select id, name, chinese, math, english " +
"from exam_result where name='" + stuName + "'";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal chinese = resultSet.getBigDecimal("chinese");
BigDecimal math = resultSet.getBigDecimal("math");
BigDecimal english = resultSet.getBigDecimal("english");
System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
從代碼可以看出,藉助query方法我們可以通過傳入一個姓名來獲得該學生的信息,但是這個query方法是有問題的,我們來將代碼修改一下,來觀察一下:
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Query {
public static void main(String[] args) {
query("範莊元' or '1' = '1");
}
public static void query(String stuName) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
statement = connection.createStatement();
String sql = "select id, name, chinese, math, english " +
"from exam_result where name='" + stuName + "'";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal chinese = resultSet.getBigDecimal("chinese");
BigDecimal math = resultSet.getBigDecimal("math");
BigDecimal english = resultSet.getBigDecimal("english");
System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
可以看到我們通過傳入"範莊元' or '1' = '1"
這麼一個字符串就會導致查詢到表中所有的數據,這種問題我們稱之爲SQL注入。
什麼是SQL注入?
- SQL注入是將Web頁面的原URL、表單域或數據包輸入的參數,修改拼接成SQL語句,傳遞給Web服務器,進而傳給數據庫服務器以執行數據庫命令;
- 如果Web應用程序的開發人員對用戶輸入的數據或cookie等內容不進行過濾或驗證(即存在注入點)就直接傳輸給數據庫,就可能導致拼接的SQL被執行,獲取數據庫的信息或對數據庫的提權,發生SQL注入攻擊。
如何解決上述問題呢?我們可以使用PreparedStatement,下面來看代碼:
import java.math.BigDecimal;
import java.sql.*;
public class Query {
public static void main(String[] args) {
query("範莊元' or '1'='1");
}
public static void query(String stuName) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
String sql = "select id, name, chinese, math, english " +
"from exam_result where name=?";
// 此處會講SQL語句送往數據庫進行預編譯
statement = connection.prepareStatement(sql);
statement.setString(1, stuName);
resultSet = statement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
BigDecimal chinese = resultSet.getBigDecimal("chinese");
BigDecimal math = resultSet.getBigDecimal("math");
BigDecimal english = resultSet.getBigDecimal("english");
System.out.printf("(%s)[%s]{%s, %s, %s}\n", id, name, chinese, math, english);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
此時就解決了上述的問題。
ResultSet對象
ResultSet對象被稱爲結加粗樣式果集,它代表符合SQL語句條件的所有行,並且它通過一套getXXX方法提供了對這些行中數據的訪問。可以將它理解成List<Map<String, Object>>
;
ResultSet裏的數據一行一行排列,每行有多個字段,並且有一個記錄指針,指針所指的數據行叫做當前數據行,我們只能來操作當前的數據行。我們如果想要取得某一條記錄,就要使用ResultSet的next()方法,如果我們想要得到ResultSet裏的所有記錄,就應該使用while循環。
兩種執行SQL的方法
executeQuery()
方法執行後返回結果集,通常用於select語句;executeUpdate()
方法返回值是一個整數,指示受影響的行數,通常用於update、insert、delete語句。
下面我們封裝三個類來方便實現insert、update、delete操作:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Insert {
public static void main(String[] args) {
insert(9, "張欣", 99, 99, 99);
}
public static void insert(
int stdId, String stuName,
int stuChinese, int stuMath, int stuEnglish
) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
String sql = "insert into exam_result(id, name, chinese, math, english) " +
"values (?, ?, ?, ?, ?)";
statement = connection.prepareStatement(sql);
statement.setInt(1, stdId);
statement.setString(2, stuName);
statement.setInt(3, stuChinese);
statement.setInt(4, stuMath);
statement.setInt(5, stuEnglish);
int ret = statement.executeUpdate();
if (ret == 1) {
System.out.printf("Insert Successful, %d row affected!", ret);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Update {
public static void main(String[] args) {
update("張蔚瀾", 66, 66, 66);
}
public static void update(
String stuName, int stuChinese, int stuMath, int stuEnglish
) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
String sql = "update exam_result " +
"set chinese = ?, math = ?, english = ? " +
"where name = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, stuChinese);
statement.setInt(2, stuMath);
statement.setInt(3, stuEnglish);
statement.setString(4, stuName);
int ret = statement.executeUpdate();
if (ret == 1) {
System.out.printf("Update Successful, %d row affected!", ret);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Delete {
public static void main(String[] args) {
delete("張欣");
}
public static void delete(String stuName) {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
try {
connection = DBUtil.getConnection2();
String sql = "delete from exam_result where name = ?";
statement = connection.prepareStatement(sql);
statement.setString(1, stuName);
int ret = statement.executeUpdate();
if (ret == 1) {
System.out.printf("Delete Successful, %d row affected!", ret);
}
} finally {
DBUtil.Close(connection, statement, resultSet);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}