JDBC
為簡化開發人員對資料庫的操作,提供的一種 Java 資料庫連接配接規範,稱為JDBC。
對開發人員隻需了解接口操作即可。
需要 java.sql,javax.sql 以及一個資料庫驅動包。
JDBC項目的建立
1. 建立一個新的項目
2. 将 jdbc 驅動 jar 包添加至一個新的 lib 目錄中,右鍵點選
Add as Library
添加至項目的庫中
3. 編寫測試代碼
//mysql 8.0.23 jdbc 8.0.23
public class JdbcFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加載驅動
Class.forName("com.mysql.cj.jdbc.Driver");
//2.使用者資訊和url
String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true";
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的對象去執行SQL語句,并産生結果
String sql = "SELECT * FROM users";
ResultSet resultSet = statement.executeQuery(sql); // 傳回的結果集,封裝了查詢出的全部結果
while(resultSet.next()){
System.out.println("id=" + resultSet.getObject("id"));
System.out.println("name=" + resultSet.getObject("NAME"));
System.out.println("password=" + resultSet.getObject("PASSWORD"));
System.out.println("email=" + resultSet.getObject("email"));
System.out.println("birthday=" + resultSet.getObject("birthday"));
}
//6.釋放連接配接
resultSet.close(); //最後使用的先釋放
statement.close();
connection.close();
}
}
測試代碼詳解
1. 加載驅動
Class.forName("com.mysql.cj.jdbc.Driver"); // 固定寫法,為MySQL8.0以上使用,8.0以下删除cj
2. url
String url = "jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true";
// mysql 預設端口号3306
// jdbc:mysql://主機位址:端口号/資料庫名?參數1&參數2&參數3
// oracle 預設端口号1521
// jdbc:oracle:thin:@主機位址:端口号:sid
3. 連接配接對象
Connection connection = DriverManager.getConnection(url,username,password);
// connection 代表資料庫,資料庫能做的 connection 都能做
connection.rollback() // 事務復原
connection.commit() // 事務送出
connection.setAutoCommit() // 資料庫設定自動送出
4. 執行對象
Statement statement = connection.createStatement();
// statement 執行SQL語句
statement.execute() // 可以執行任何SQL語句,相應效率較低
statement.executeQuery() // 執行查詢操作,傳回一個結果集 ResultSet
statement.executeUpdate() // 執行更新、插入、删除操作,傳回一個受影響的行數
statement.executeBatch() // 批處理,執行多個SQL
5. ResultSet查詢的結果集
ResultSet resultSet = statement.execute(sql);
// 結果集中包含所有的查詢結果
// 獲得指定的資料類型
resultSet.getObject() // 在不知道列類型時使用
resultSet.getInt()
resultSet.getString()
resultSet.getFloat()
resultSet.getDate()
...
// 可以對結果集 resultSet 進行一系列操作
resultSet.next() // 移動到下一個資料
resultSet.beforeFirst() // 移動到最前面
resultSet.afterLast() // 移動到最後面
resultSet.previous() // 移動到前一行
resultSet.absolute(row) // 移動到指定行
6. 釋放資源
// 使用完畢後一定要釋放連接配接,特别是connection十分占用
resultSet.close();
statement.close();
connection.close();
提取工具類
- 将使用者資訊和 url 提取至建立配置檔案 db.properties 中
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
- 編寫方法
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{
//通過獲得Jdbc類加載器,擷取資源:db.properties并産生輸入流,在src目錄下直接調用,否則需要寫路徑
InputStream input = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
//建立Properties對象并加載輸入流,便于使用Properties的方法
Properties properties = new Properties();
properties.load(input);
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
// 驅動隻用加載一次
Class.forName(driver); //已提取了driver資訊,不需要再寫包名
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
//擷取連接配接方法 getConnection
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
//釋放連接配接方法 release
public static void release(Connection conn, Statement st, ResultSet rs){
//按順序釋放
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
使用提取的工具類
插入語句,增删改同理
public class TestInsert {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//擷取連接配接
conn = JdbcUtils.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立
//建立statement對象
st = conn.createStatement();
//通過st執行sql
String sql = "INSERT INTO ...";
int i = st.executeUpdate(sql); //傳回受影響行數
if(i > 0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
查詢語句
public class TestSelect {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();
String sql = "SELECT * FROM ...";
rs = st.executeQuery(sql);
while(rs.next()){
System.out.println(rs.getString("name"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
SQL 的注入問題
SQL 注入的本質是 SQL 語句會被拼接,如需要判斷使用者名及密碼是否和資料庫中相同時,隻需要輸入特定使用者名就可以欺騙資料庫。
注入舉例
查詢登入賬号密碼的語句如下
String sql = "SELECT * FROM users WHERE `name` = '" + username + "' AND `password` = '" password + "'";
對應SQL語句為
SELECT * FROM users WHERE `name` = 'username' AND `password` = 'password'
當輸入的 username 和 password為
' or 1=1 '
時,相應的 SQL 語句為
SELECT * FROM users WHERE `name` = '' or 1=1 '' AND `password` = '' or 1=1 ''
由于 1=1 恒成立,那麼查詢出來的結果即為所有的賬戶和對應的密碼。
PreparedStatement 對象
PreparedStatement 對象可以防止 SQL 注入,且效率更高。
PreparedStatement 的本質,把傳入的參數當做字元處理。
//使用PreparedStatement插入
public class TestPreparedStatement {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pst = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
//差別
//使用 ? 占位符代替參數
String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(?,?,?,?,?)";
pst = conn.prepareStatement(sql); //預編譯 sql,先寫 sql 不執行
//手動給參數指派
pst.setInt(1,1002); //給第一個占位符賦 int 類型的值,插入 id 為1002
pst.setString(2,"小明"); //給第二個占位符賦 String 類型的值,插入 name 為小明
pst.setString(3,"123456");
pst.setString(4,"[email protected]");
//sql.Date 資料庫 java.sql.Date
//util.Date Java Date().getTime() 獲得時間戳
pst.setDate(5,new java.sql.Date(new Date().getTime()));
//執行 sql
int i = pst.executeUpdate(); //不需要傳參
if(i > 0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
JdbcUtils.release(conn,pst,rs);
}
}
}
資料庫連接配接池
資料庫連接配接——執行——釋放一個流程中,連接配接和釋放非常浪費系統資源,是以出現池化技術,預先準備好一些資源,需要調用這些準備好的資源時可以避免連接配接釋放,直接使用。
連接配接池實作接口 DataSource,對此較為常用的兩個實作類:DBCP,C3P0,Druid
使用該資料庫連接配接池後,在項目開發中不需要編寫連接配接資料庫代碼。
DBCP
需要 commons-dbcp、commons-pool 兩種jar包
配置檔案
dbcpconfig.properties
#連接配接設定
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true
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
方法實作
public class JdbcUtils_DBCP {
private static DataSource dataSource = null;
static {
try{
InputStream input = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
//建立Properties對象并加載輸入流,便于使用Properties的方法
Properties properties = new Properties();
properties.load(input);
//建立資料源 工廠模式建立資料源
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//擷取連接配接方法,getConnection()
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); //從資料源擷取連接配接
}
//釋放連接配接,與自寫工具類相同
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
方法調用
public class TestDBCP {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//擷取連接配接
conn = JdbcUtils_DBCP.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立
//建立statement對象
st = conn.createStatement();
//通過st執行sql
String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(4,'趙六','123456','[email protected]','1988-12-08')";
int i = st.executeUpdate(sql); //傳回受影響行數
if(i > 0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils_DBCP.release(conn,st,rs);
}
}
}
C3P0
C3P0支援多套資料源,需要 c3p0,mchange-commons-java 兩種 jar 包
c3p9-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
<!--
如果在代碼中使用如下寫法
ComboPooledDataSource ds = new ComboPooledDataSource();
則使用預設的配置讀取連接配接池對象 -->
<default-config>
<!-- 連接配接參數 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">root123456</property>
<!-- 連接配接池參數 -->
<!--初始化的申請的連接配接數量-->
<property name="initialPoolSize">5</property>
<!--最大的連接配接數量-->
<property name="maxPoolSize">10</property>
<!--連接配接逾時時間-->
<property name="checkoutTimeout">3000</property>
</default-config>
<!--
如果在代碼中使用如下寫法
ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");
則使用如下配置讀取連接配接池對象 -->
<named-config name="MySQL">
<!-- 連接配接參數 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbcstudy?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&useSSL=true</property>
<property name="user">root</property>
<property name="password">root123456</property>
<!-- 連接配接池參數 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>
</named-config>
</c3p0-config>
public class JdbcUtils_C3P0 {
private static DataSource dataSource = null;
static {
try{
//xml 不需要讀取,自動比對
//建立資料源
dataSource = new ComboPooledDataSource();
} catch (Exception e) {
e.printStackTrace();
}
}
//擷取連接配接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection(); //從資料源擷取連接配接
}
//釋放連接配接
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
public class TestC3P0 {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//擷取連接配接
conn = JdbcUtils_C3P0.getConnection();//在try中的變量,在finally中無法釋放,需要在外部建立
//建立statement對象
st = conn.createStatement();
//通過st執行sql
String sql = "INSERT INTO users(id, `name`, `password`, `email`, `birthday`) values(4,'趙六','123456','[email protected]','1988-12-08')";
int i = st.executeUpdate(sql); //傳回受影響行數
if(i > 0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils_C3P0.release(conn,st,rs);
}
}
}