1 概述
在Java中,資料庫存取技術可分為如下幾類:
- JDBC直接通路資料庫
- JDO技術(Java Data Object)
-
第三方O/R工具,如Hibernate, Mybatis 等
JDBC是java通路資料庫的基石,JDO, Hibernate等隻是更好的封裝了JDBC。
1.1 什麼是JDBC
JDBC(Java Database Connectivity)是一個
獨立于特定資料庫管理系統(DBMS)、通用的SQL資料庫存取和操作的公共接口
(一組API),定義了用來通路資料庫的标準Java類庫,使用這個類庫可以以一種标準的方法、友善地通路資料庫資源
JDBC為通路不同的資料庫提供了一種
統一的途徑
,為開發者屏蔽了一些細節問題。
JDBC的目标是使Java程式員使用JDBC可以連接配接任何
提供了JDBC驅動程式
的資料庫系統,這樣就使得程式員無需對特定的資料庫系統的特點有過多的了解,進而大大簡化和加快了開發過程。
2 JDBC程式編寫步驟
- 注冊驅動
- 擷取連接配接
- 執行增删改查
- 釋放資源
2.1 示範完整步驟
準備工作:引入JDBC驅動程式
驅動程式由資料庫提供商提供下載下傳。 MySQL的驅動下載下傳位址:http://dev.mysql.com/downloads/
以此點選Connector/J→選擇Platform Independent→下載下傳.zip檔案→下載下傳好的zip裡的jar就是要的jar
- (1)把mysql-connector-java-8.0.21.jar拷貝到項目中lib目錄中
-
(2)添加到項目的類路徑下
在驅動jar上右鍵–>Build Path–>Add to Build Path
注意
:如果是Dynamic Web Project(動态的web項目)話,則是把驅動jar放到WebContent(有的開發工具叫WebRoot)目錄中的WEB-INF目錄中的lib目錄下即可
(一)加載并注冊驅動
加載并注冊驅動:
加載驅動,把驅動類加載到記憶體
注冊驅動,把驅動類的對象交給DriverManager管理,用于後面建立連接配接等使用。
- 1、Class.forName( )
DriverManager.registerDriver(new Driver());//這是舊方法
Class.forName("com.mysql.cj.jdbc.Driver");//這是新方法
因為 Driver 接口的驅動程式類都包含了靜态代碼塊,在這個靜态代碼塊中,會調用 DriverManager.registerDriver() 方法來注冊自身的一個執行個體,是以可以換一種方式來加載驅動。(即隻要想辦法讓驅動類的這段靜态代碼塊執行即可注冊驅動類,而要讓這段靜态代碼塊執行,隻要讓該類被類加載器加載即可)
調用 Class 類的靜态方法 forName(),向其傳遞要加載的 JDBC 驅動的類名
//通過反射,加載與注冊驅動類,
解耦合(不直接依賴)
Class.forName(“com.mysql.jdbc.Driver”);
-
2、服務提供者架構(例如:JDBC的驅動程式)自動注冊(有版本要求)
符合JDBC 4.0規範的驅動程式包含了一個檔案META-INF/services/java.sql.Driver,在這個檔案中提供了JDBC驅動實作的類名。例如:mysql-connector-java-5.1.40-bin.jar檔案中就可以找到java.sql.Driver檔案,用文本編輯器打開檔案就可以看到:com.mysql.jdbc.Driver類。
JVM的服務提供者架構在啟動應用時就會注冊服務,例如:MySQL的JDBC驅動就會被注冊,而原代碼中的Class.forName(“com.mysql.jdbc.Driver”)仍然可以存在,但是不會起作用。
但是注意mysql-connector-java-5.0.8-bin.jar版本的jar中沒有,如下
(二)擷取資料庫連結
可以通過 DriverManager 類建立到資料庫的連接配接Connection:
DriverManager 試圖從已注冊的 JDBC 驅動程式集中選擇一個适當的驅動程式。
- public static Connection getConnection(String url)
- public static Connection getConnection(String url,String user, String password)
- public static Connection getConnection(String url,Properties info)其中Properties info通常至少應該包括 "user" 和 "password" 屬性
例如:
JDBC URL 用于辨別一個被注冊的驅動程式,驅動程式管理器通過這個 URL 選擇正确的驅動程式,進而建立到資料庫的連接配接。JDBC URL的标準由三部分組成,各部分間用冒号分隔。
jdbc:<子協定>:<子名稱>
- 協定:JDBC URL中的協定總是jdbc
- 子協定:子協定用于辨別一個資料庫驅動程式
-
子名稱:一種辨別資料庫的方法。子名稱可以依不同的子協定而變化,用子名稱的目的是為了定位資料庫提供足夠的資訊
例如:
MySQL的連接配接URL編寫方式:
jdbc:mysql://主機名稱:mysql服務端口号/資料庫名稱?參數=值&參數=值
jdbc:mysql://localhost:3306/testdb
jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程式與伺服器端的字元集不一緻,會導緻亂碼,那麼可以通過參數指定伺服器端的字元集)
jdbc:mysql://localhost:3306/testdb?user=root&password=123456
"jdbc:mysql://localhost:3306/girls?serverTimezone=UTC","root","root"
MySQL的連接配接URL之利用properties檔案
在src下建立一個檔案:jdbc.properties
(三)操作或通路資料庫
資料庫連接配接被用于向資料庫伺服器發送指令和 SQL 語句,并接受資料庫伺服器傳回的結果。
其實一個資料庫連接配接就是一個Socket連接配接。
在 java.sql 包中有 3 個接口分别定義了對資料庫的調用的不同方式:
- Statement:用于執行靜态 SQL 語句并傳回它所生成結果的對象。
- PrepatedStatement:SQL 語句被預編譯并存儲在此對象中,然後可以使用此對象多次高效地執行該語句。
- CallableStatement:用于執行 SQL 存儲過程
3.1Statement
通過調用 Connection 對象的 createStatement() 方法建立該對象
該對象用于執行靜态的 SQL 語句,并且傳回執行結果
Statement 接口中定義了下列方法用于執行 SQL 語句:
- int excuteUpdate(String sql):執行更新操作INSERT、UPDATE、DELETE,傳回值為被改變的行數
- ResultSet excuteQuery(String sql):執行查詢操作SELECT
executeUpdate
package mysql;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.jdbc.Driver;
public class sqltest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1 加載驅動
//DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.cj.jdbc.Driver");
//2.擷取連接配接
Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/girls?serverTimezone=UTC","root","root");
//3.執行增删改查
//3-1 編寫sql語句
String sql="UPDATE boys SET boyName='盧本偉' where id=1";
//3-2 擷取執行sql語句的執行對象
Statement statement= connection.createStatement();
//3-3 使用指令對象指向sql語句
int update=statement.executeUpdate(sql);//這一句隻能增删改,傳回受影響行數
//3-4處理執行結果
System.out.println(update>0?"yes":"no");
//4 關閉連接配接,後開先關閉,對稱
statement.close();
connection.close();
}
}
ResultSet
通過調用 Statement 對象的 excuteQuery() 方法建立該對象
ResultSet 對象以邏輯表格的形式封裝了執行資料庫操作的結果集,ResultSet 接口由資料庫廠商實作
ResultSet 對象維護了一個指向目前資料行的遊标,初始的時候,
遊标在第一行之前一行
,可以通過 ResultSet 對象的 next() 方法移動到下一行
ResultSet 接口的常用方法:
- boolean next()
- getXxx(String columnLabel):columnLabel使用 SQL AS 子句指定的列标簽。如果未指定 SQL AS 子句,則标簽是列名稱
- getXxx(int index) :索引從1開始
package mysql;
import java.sql.Connection;
import java.sql.Date;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.jdbc.Driver;
public class sqltest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1 加載驅動
//DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.cj.jdbc.Driver");
//2.擷取連接配接
Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/girls?serverTimezone=UTC","root","root");
//3.執行增删改查
//3-1 編寫sql語句
String sql="select id,name,sex,borndate from beauty";
//3-2 擷取執行sql語句的執行對象
Statement statement= connection.createStatement();
//3-3 使用指令對象指向sql語句
ResultSet set=statement.executeQuery(sql);
boolean flag= set.next();
int id = set.getInt("id");
String name = set.getString("name");
String sex = set.getString("sex");
Date date = set.getDate("borndate");
System.out.println(id+"\t"+name+"\t"+sex+"\t"+date);
//4 關閉連接配接,後開先關閉,對稱
set.close();
statement.close();
connection.close();
}
}
3.2 PrepatedStatement
可以通過調用 Connection 對象的 preparedStatement(String sql) 方法擷取 PreparedStatement 對象
PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句
- PreparedStatement 對象所代表的 SQL 語句中的參數用問号(?)來表示,調用 PreparedStatement 對象的 setXxx() 方法來設定這些參數. setXxx() 方法有兩個參數,第一個參數是要設定的 SQL 語句中的參數的索引(從 1 開始),第二個是設定的 SQL 語句中的參數的值
- ResultSet executeQuery()執行查詢,并傳回該查詢生成的 ResultSet 對象。
- int executeUpdate():執行更新,包括增、删、改
↑來驗證賬戶密碼會導緻sql注入問題,因為如果兩個參數中含有’ "等符号,會出現文法錯誤
↑就可以避免sql注入問題
2.2 封裝JDBCUtils.java
因為很多步驟都是重複的,是以可以将重複的操作封裝起來
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 此類是封裝JDBC連接配接的工具類
*
* 功能:
* 1、擷取連接配接
* 2、釋放資源
*
*/
public class JDBCUtils {
static String user;
static String password;
static String url;
static String driver;
static{
try {
Properties info = new Properties();
info.load(new FileInputStream("src\\jdbc.properties"));
user = info.getProperty("user");
password = info.getProperty("password");
url = info.getProperty("url");
driver = info.getProperty("driver");
//1.注冊驅動
Class.forName(driver);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 功能:擷取可用的連接配接對象
* @return 連接配接
* @throws Exception
*/
public static Connection getConnection(){
try {
return DriverManager.getConnection(url, user, password);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 功能:釋放資源
* @param set
* @param statement
* @param connection
* @throws Exception
*/
public static void close(ResultSet set,Statement statement,Connection connection){
try {
if (set!=null) {
set.close();//如果是查詢操作,需要關閉set,否則這裡傳個null
}
if (statement!=null) {
statement.close();
}
if (connection!=null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
至此以下為完整步驟
3 JDBC事務
package com.atguigu.jdbc2;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import com.atguigu.jdbc1.JDBCUtils;
/**
* 此類用于示範JDBC中的事務
* @author liyuting
*
* 使用步驟:
* 1、開啟新事務
* 取消隐式事務自動送出的功能
* setAutoCommit(false);
* 2、編寫組成事務的一組sql語句
*
* 3、結束事務
* commit();送出
* rollback();復原
*
* 細節:
* 要求開啟事務的連接配接對象和擷取指令的連接配接對象必須是同一個!否則事務無效
*
*
*
*
*
* 案例:轉賬案例
* 張三豐給滅絕轉5000
*
*
*/
public class TestTransaction {
//不用事務
@Test
public void testNoTransaction() throws Exception{
//1.擷取連接配接
Connection connection = JDBCUtils.getConnection();
//2.執行sql語句
PreparedStatement statement = connection.prepareStatement("update account set balance = ? where stuname=?");
//操作1:張三豐的錢-5000
statement.setDouble(1, 5000);
statement.setString(2, "張三豐");
statement.executeUpdate();
int i = 1/0;//模拟異常
//操作2:張三豐的錢-5000
statement.setDouble(1, 15000);
statement.setString(2, "滅絕師太");
statement.executeUpdate();
//3.釋放資源
JDBCUtils.close(null, statement, connection);
}
//使用事務
@Test
public void testTransaction(){
Connection connection = null;
PreparedStatement statement = null;
try {
//1.擷取連接配接
connection = JDBCUtils.getConnection();
//①事務的使用步驟1:開啟事務,意思是取消每條語句自動commit
connection.setAutoCommit(false);
//②事務的使用步驟2:編寫sql語句,并且執行
statement = connection.prepareStatement("update account set balance = ? where stuname=?");
//操作1:張三豐的錢-5000
statement.setDouble(1, 5000);
statement.setString(2, "張三豐");
statement.executeUpdate();
// int i = 1/0;//模拟異常
//操作2:張三豐的錢-5000
statement.setDouble(1, 15000);
statement.setString(2, "滅絕師太");
statement.executeUpdate();
//③事務的使用步驟3:結束事務
connection.commit();
} catch (SQLException e) {
try {
//復原事務
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
JDBCUtils.close(null, statement, connection);
}
}
}
4 批處理
當需要成批插入或者更新記錄時。可以采用Java的批量更新機制,這一機制允許多條語句一次性送出給資料庫批量處理。通常情況下比單獨送出處理更有效率。
JDBC的批量處理語句包括下面兩個方法:
- addBatch():添加需要批量處理的SQL語句或參數
- executeBatch():執行批量處理語句;
-
clearBatch():清空批處理包的語句
分為兩種情況:
- 多條SQL語句的批量處理;
- 一個SQL語句的批量傳參;
5 Blob類型資料
MySQL中,BLOB是一個二進制大型對象,是一個可以存儲大量資料的容器,它能容納不同大小的資料。能夠存
MySQL的四種BLOB類型(除了在存儲的最大資訊量上不同外,他們是等同的)
實際使用中根據需要存入的資料大小定義不同的BLOB類型。
需要注意的是:如果存儲的檔案過大,資料庫的性能會下降
package com.atguigu.jdbc2;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import com.atguigu.jdbc1.JDBCUtils;
/**
* 此類用于示範Blob類型資料的存取
* @author liyuting
* 相關API:
*
* setBlob(占位符索引,InputStream對象)
* getBlob(列索引或列名)
* getBinaryStream(列索引或列名)
*
*/
public class TestBlob {
//存圖檔
@Test
public void testSave() throws SQLException, Exception{
//1.擷取連接配接
Connection connection = JDBCUtils.getConnection();
//2.執行修改語句
PreparedStatement statement = connection.prepareStatement("update beauty set photo=? where id = 1");
statement.setBlob(1, new FileInputStream("src\\6.jpg"));
int update = statement.executeUpdate();
//3.關閉連接配接
JDBCUtils.close(null, statement, connection);
}
//讀圖檔
@Test
public void testRead() throws SQLException, Exception{
//1.擷取連接配接
Connection connection = JDBCUtils.getConnection();
//2.執行修改語句
PreparedStatement statement = connection.prepareStatement("select photo from beauty where id = 1");
ResultSet set = statement.executeQuery();
if(set.next()){
//方式1:
// Blob blob = set.getBlob("photo");
// InputStream binaryStream = blob.getBinaryStream();
//方式2:
InputStream inputStream = set.getBinaryStream("photo");
FileOutputStream fos = new FileOutputStream("src\\beauty.jpg");
int len;
byte[] b = new byte[1024];
while((len=inputStream.read(b))!=-1){
fos.write(b,0,len);
}
fos.close();
inputStream.close();
}
//3.關閉連接配接
JDBCUtils.close(null, statement, connection);
}
}
6 資料庫連接配接池
(1)資料庫連接配接池的必要性
不使用資料庫連接配接池存在的問題:
- 普通的JDBC資料庫連接配接使用 DriverManager 來擷取,每次向資料庫建立連接配接的時候都要将 Connection 加載到記憶體中,再驗證IP位址,使用者名和密碼(得花費0.05s~1s的時間)。需要資料庫連接配接的時候,就向資料庫要求一個,執行完成後再斷開連接配接。這樣的方式将會消耗大量的資源和時間。資料庫的連接配接資源并沒有得到很好的重複利用.若同時有幾百人甚至幾千人線上,頻繁的進行資料庫連接配接操作将占用很多的系統資源,嚴重的甚至會造成伺服器的崩潰。
- 對于每一次資料庫連接配接,使用完後都得斷開。否則,如果程式出現異常而未能關閉,将會導緻資料庫系統中的記憶體洩漏,最終将導緻重新開機資料庫。
-
這種開發不能控制被建立的連接配接對象數,系統資源會被毫無顧及的配置設定出去,如連接配接過多,也可能導緻記憶體洩漏,伺服器崩潰。
為解決傳統開發中的資料庫連接配接問題,可以采用資料庫連接配接池技術(connection pool)。
資料庫連接配接池的基本思想就是為資料庫連接配接建立一個“緩沖池”。預先在緩沖池中放入一定數量的連接配接,當需要建立資料庫連接配接時,隻需從“緩沖池”中取出一個,使用完畢之後再放回去。資料庫連接配接池負責配置設定、管理和釋放資料庫連接配接,它允許應用程式重複使用一個現有的資料庫連接配接,而不是重建立立一個。連接配接池的最大資料庫連接配接數量限定了這個連接配接池能占有的最大連接配接數,當應用程式向連接配接池請求的連接配接數超過最大連接配接數量時,這些請求将被加入到等待隊列中。
資料庫連接配接池技術的優點:
- 資源重用:
- 由于資料庫連接配接得以重用,避免了頻繁建立,釋放連接配接引起的大量性能開銷。在減少系統消耗的基礎上,另一方面也增加了系統運作環境的平穩性。
- 更快的系統反應速度
- 資料庫連接配接池在初始化過程中,往往已經建立了若幹資料庫連接配接置于連接配接池中備用。此時連接配接的初始化工作均已完成。對于業務請求處理而言,直接利用現有可用連接配接,避免了資料庫連接配接初始化和釋放過程的時間開銷,進而減少了系統的響應時間
- 新的資源配置設定手段
- 對于多應用共享同一資料庫的系統而言,可在應用層通過資料庫連接配接池的配置,實作某一應用最大可用資料庫連接配接數的限制,避免某一應用獨占所有的資料庫資源
- 統一的連接配接管理,避免資料庫連接配接洩露
- 在較為完善的資料庫連接配接池實作中,可根據預先的占用逾時設定,強制回收被占用連接配接,進而避免了正常資料庫連接配接操作中可能出現的資源洩露
(2)多種開源的資料庫連接配接池
JDBC 的資料庫連接配接池使用 javax.sql.DataSource 來表示,DataSource 隻是一個接口,該接口通常由伺服器(Weblogic, WebSphere, Tomcat)提供實作,也有一些開源組織提供實作:
-
是Apache提供的資料庫連接配接池,速度相對c3p0較快,但因自身存在BUG,Hibernate3已不再提供支援DBCP
-
是一個開源組織提供的一個資料庫連接配接池,速度相對較慢,穩定性還可以C3P0
-
是sourceforge下的一個開源項目資料庫連接配接池,有監控連接配接池狀态的功能,穩定性較c3p0差一點Proxool
-
是一個開源組織提供的資料庫連接配接池,速度快BoneCP
-
Druid
是阿裡提供的資料庫連接配接池,據說是集DBCP 、C3P0 、Proxool 優點于一身的資料庫連接配接池,但是速度不知道是否有BoneCP快
DataSource 通常被稱為資料源,它包含連接配接池和連接配接池管理兩個部分,習慣上也經常把 DataSource 稱為連接配接池
注意:
- 資料源和資料庫連接配接不同,資料源無需建立多個,它是産生資料庫連接配接的工廠,是以整個應用隻需要一個資料源即可。
-
當資料庫通路結束後,程式還是像以前一樣關閉資料庫連接配接:conn.close(); 但conn.close()并沒有關閉資料庫的實體連接配接,它僅僅把資料庫連接配接釋放,歸還給了資料庫連接配接池。
(3)Druid(德魯伊)資料源
Druid是阿裡巴巴開源平台上一個資料庫連接配接池實作,它結合了C3P0、DBCP、Proxool等DB池的優點,同時加入了日志監控,可以很好的監控DB池連接配接和SQL的執行情況,可以說是針對監控而生的DB連接配接池,據說是目前最好的連接配接池。
- 方式一
package com.atguigu.druid;
import java.sql.Connection;
import com.alibaba.druid.pool.DruidDataSource;
public class TestDruid {
public static void main(String[] args) throws Exception {
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/0319db");
ds.setUsername("root");
ds.setPassword("123456");
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setInitialSize(10);//預設值0,應該在maxActive和minIdle之間
ds.setMaxActive(20);//預設值8
ds.setMinIdle(1);//預設值0
//maxIdle是Druid為了友善DBCP使用者遷移而增加的,maxIdle是一個混亂的概念。連接配接池隻應該有maxPoolSize和minPoolSize,druid隻保留了maxActive和minIdle,分别相當于maxPoolSize和minPoolSize。
ds.setMaxIdle(5);
// 擷取連接配接時最大等待時間,機關毫秒。配置了maxWait之後,預設啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。
ds.setMaxWait(1000);
//配置間隔多久才進行一次檢測,檢測需要關閉的空閑連接配接,機關是毫秒
ds.setTimeBetweenEvictionRunsMillis(60000);
//配置一個連接配接在池中最小生存的時間,機關是毫秒
ds.setMinEvictableIdleTimeMillis(300000);
//是否緩存preparedStatement,也就是PSCache。PSCache對支援遊标的資料庫性能提升巨大,比如說oracle。在mysql下建議關閉。
ds.setPoolPreparedStatements(true);
// 要啟用PSCache,必須配置大于0,當大于0時,poolPreparedStatements自動觸發修改為true。在Druid中,不會存在Oracle下PSCache占用記憶體過多的問題,可以把這個數值配置大一些,比如說100
ds.setMaxPoolPreparedStatementPerConnectionSize(10);
//配置多個英文逗号分隔
//通過别名的方式配置擴充插件,常用的插件有: 監控統計用的filter:stat日志用的filter:log4j防禦sql注入的filter:wall
ds.setFilters("stat,wall");
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
- 方式二,使用properties檔案
package com.atguigu.utils;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
/**
* 此類是通過德魯伊資料庫連接配接池擷取連接配接對象
* @author liyuting
*
*/
public class JDBCUtilsByDruid {
static DataSource ds;
static{
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//1.建立了一個指定參數的資料庫連接配接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws Exception{
//2.從資料庫連接配接池中擷取可用的連接配接對象
return ds.getConnection();
}
/**
* 功能:釋放資源
* @param set
* @param statement
* @param connection
* @throws Exception
*/
public static void close(ResultSet set,Statement statement,Connection connection){
try {
if (set!=null) {
set.close();
}
if (statement!=null) {
statement.close();
}
if (connection!=null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
properties檔案👇
url=jdbc:mysql://localhost:3306/0319db
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
配置 | 預設 | 說明 |
---|---|---|
name | 配置這個屬性的意義在于,如果存在多個資料源,監控的時候可以通過名字來區分開來。 如果沒有配置,将會生成一個名字,格式是:”DataSource-” + System.identityHashCode(this) | |
jdbcUrl | 連接配接資料庫的url,不同資料庫不一樣。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 連接配接資料庫的使用者名 | |
password | 連接配接資料庫的密碼。如果你不希望密碼直接寫在配置檔案中,可以使用ConfigFilter。詳細看這裡:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter | |
driverClassName | 根據url自動識别 這一項可配可不配,如果不配置druid會根據url自動識别dbType,然後選擇相應的driverClassName(建議配置下) | |
initialSize | 初始化時建立實體連接配接的個數。初始化發生在顯示調用init方法,或者第一次getConnection時 | |
maxActive | 8 | 最大連接配接池數量 |
maxIdle | 8 | |
minIdle | 最小連接配接池數量 | |
maxWait | 擷取連接配接時最大等待時間,機關毫秒。配置了maxWait之後,預設啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 | |
poolPreparedStatements | false | 是否緩存preparedStatement,也就是PSCache。PSCache對支援遊标的資料庫性能提升巨大,比如說oracle。在mysql下建議關閉。 |
maxOpenPreparedStatements | -1 | |
validationQuery | 用來檢測連接配接是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用。 | |
testOnBorrow | true | 申請連接配接時執行validationQuery檢測連接配接是否有效,做了這個配置會降低性能。 |
testOnReturn | false | 歸還連接配接時執行validationQuery檢測連接配接是否有效,做了這個配置會降低性能 |
testWhileIdle | false | 建議配置為true,不影響性能,并且保證安全性。申請連接配接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接配接是否有效。 |
timeBetweenEvictionRunsMillis | 有兩個含義: 1)Destroy線程會檢測連接配接的間隔時間2)testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明 | |
numTestsPerEvictionRun | 不再使用,一個DruidDataSource隻支援一個EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 實體連接配接初始化的時候執行的sql | |
exceptionSorter | 根據dbType自動識别 當資料庫抛出一些不可恢複的異常時,抛棄連接配接 | |
filters | 屬性類型是字元串,通過别名的方式配置擴充插件,常用的插件有: 監控統計用的filter:stat日志用的filter:log4j防禦sql注入的filter:wall | |
proxyFilters | 類型是List,如果同時配置了filters和proxyFilters,是組合關系,并非替換關系 |
7 利用Druid的CRUD封裝
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import com.atguigu.bean.Boys;
/**
* 此類用于封裝通用的增删改查方法
* @author liyuting
* 功能:
* 1、執行增删改
* 2、執行查詢
*
*
*/
public class CRUDUtils {
/**
* 功能:增删改
* 針對于任何表的任何增删改語句
* @return
* @throws Exception
*/
public static int update(String sql,Object...params){
try {
//1.擷取連接配接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.執行sql語句
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1, params[i]);
}
int update = statement.executeUpdate();
return update;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* orm:object relation mapping
* @param sql
* @param params
* @return
*
* 隻針對Boys表,查詢單條
* @throws Exception
*/
public static Boys querySingle(String sql,Object...params) throws Exception{
Connection connection=null;
PreparedStatement statement=null;
ResultSet set = null;
try {
//1.擷取連接配接
connection = JDBCUtilsByDruid.getConnection();
//2.執行查詢
statement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1, params[i]);
}
set = statement.executeQuery();
if(set.next()){
int id = set.getInt("id");
String boyName = set.getString("boyname");
int userCP = set.getInt("userCP");
Boys bo = new Boys(id,boyName,userCP);
return bo;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(set, statement, connection);
}
}
/**
* orm:object relation mapping
* @param sql
* @param params
* @return
*
* 隻針對Boys表,查詢多條
* @throws Exception
*/
public static List<Boys> queryMulti(String sql,Object...params) throws Exception{
Connection connection=null;
PreparedStatement statement=null;
ResultSet set = null;
try {
//1.擷取連接配接
connection = JDBCUtilsByDruid.getConnection();
//2.執行查詢
statement = connection.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1, params[i]);
}
set = statement.executeQuery();
List<Boys> list = new ArrayList<>();
while(set.next()){
int id = set.getInt("id");
String boyName = set.getString("boyname");
int userCP = set.getInt("userCP");
Boys bo = new Boys(id,boyName,userCP);
list.add(bo);
}
return list;
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(set, statement, connection);
}
}
}
boys類👇
package com.atguigu.bean;
public class Boys {
private int id;
private String boyName;
private int userCP;
public Boys() {
super();
}
@Override
public String toString() {
return "Boys [id=" + id + ", boyName=" + boyName + ", userCP=" + userCP + "]";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBoyName() {
return boyName;
}
public void setBoyName(String boyName) {
this.boyName = boyName;
}
public int getUserCP() {
return userCP;
}
public void setUserCP(int userCP) {
this.userCP = userCP;
}
public Boys(int id, String boyName, int userCP) {
super();
this.id = id;
this.boyName = boyName;
this.userCP = userCP;
}
}
👇調用執行個體
package com.atguigu.utils;
import java.util.List;
import org.junit.Test;
import com.atguigu.bean.Boys;
public class TestCRUDUtils {
@Test
public void testUpdate(){
// int update = CRUDUtils.update("update beauty set sex = ? where name='柳岩'", "男");
//
// System.out.println(update>0?"success":"failure");
int update = CRUDUtils.update("delete from admin where id>5");
System.out.println(update>0?"success":"failure");
}
@Test
public void testQuery() throws Exception{
// Boys boy = CRUDUtils.querySingle("select * from boys where id = ?", 2);
// System.out.println(boy);
List<Boys> list = CRUDUtils.queryMulti("select * from boys");
for (Boys boys : list) {
System.out.println(boys);
}
}
}
8 DBUtils
commons-dbutils
是 Apache 組織提供的一個開源 JDBC工具類庫,它是對JDBC的簡單封裝,學習成本極低,并且使用dbutils能極大簡化jdbc編碼的工作量,同時也不會影響程式的性能。
1、DbUtils類
DbUtils :提供如關閉連接配接、裝載JDBC驅動程式等正常工作的工具類,裡面的所有方法都是靜态的。主要方法如下:
- public static void close(…) throws java.sql.SQLException: DbUtils類提供了三個重載的關閉方法。這些方法檢查所提供的參數是不是NULL,如果不是的話,它們就關閉Connection、Statement和ResultSet。
- public static void closeQuietly(…): 這一類方法不僅能在Connection、Statement和ResultSet為NULL情況下避免關閉,還能隐藏一些在程式中抛出的SQLEeception。
- public static void commitAndClose(Connection conn)throws SQLException 用來送出連接配接的事務,然後關閉連接配接
- public static void commitAndCloseQuietly(Connection conn): 用來送出連接配接的事務,然後關閉連接配接,并且在關閉連接配接時不抛出SQL異常。
- public static void rollback(Connection conn)throws SQLException允許conn為null,因為方法内部做了判斷
- public static void rollbackAndClose(Connection conn)throws SQLException
- rollbackAndCloseQuietly(Connection)
- public static boolean loadDriver(java.lang.String driverClassName):這一方裝載并注冊JDBC驅動程式,如果成功就傳回true。使用該方法,你不需要捕捉這個異常ClassNotFoundException。
2、QueryRunner類
該類封裝了SQL的執行,是線程安全的。
(1)可以實作增、删、改、查、批處理、
(2)考慮了事務處理需要共用Connection。
(3)該類最主要的就是簡單化了SQL查詢,它與ResultSetHandler組合在一起使用可以完成大部分的資料庫操作,能夠大大減少編碼量。
QueryRunner類提供了兩個構造方法:
- QueryRunner():預設的構造方法
-
QueryRunner(DataSource ds):需要一個 javax.sql.DataSource 來作參數的構造方法。
(1)更新
-
public int update(Connection conn, String sql, Object… params) throws SQLException:用來執行一個更新(插入、更新或删除)操作。
(2)插入
-
public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException:隻支援INSERT語句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 傳回值: An object generated by the handler.即自動生成的鍵值
(3)批處理
- public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE語句
-
public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException:隻支援INSERT語句
(4)使用QueryRunner類實作查詢
-
public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:執行一個查詢操作,在這個查詢中,對象數組中的每個元素值被用來作為查詢語句的置換參數。該方法會自行處理 PreparedStatement 和 ResultSet 的建立和關閉。
3、ResultSetHandler接口
該接口用于處理 java.sql.ResultSet,将資料按要求轉換為另一種形式。ResultSetHandler 接口提供了一個單獨的方法:Object handle (java.sql.ResultSet rs)該方法的傳回值将作為QueryRunner類的query()方法的傳回值。
該接口有如下實作類可以使用:
- ArrayHandler:把結果集中的第一行資料轉成對象數組。
- ArrayListHandler:把結果集中的每一行資料都轉成一個數組,再存放到List中。
- BeanHandler:将結果集中的第一行資料封裝到一個對應的JavaBean執行個體中。
- BeanListHandler:将結果集中的每一行資料都封裝到一個對應的JavaBean執行個體中,存放到List裡。
- ColumnListHandler:将結果集中某一列的資料存放到List中。
- KeyedHandler(name):将結果集中的每一行資料都封裝到一個Map裡,再把這些map再存到一個map裡,其key為指定的key。
- MapHandler:将結果集中的第一行資料封裝到一個Map裡,key是列名,value就是對應的值。
-
MapListHandler:将結果集中的每一行資料都封裝到一個Map裡,然後再存放到List
4、表與JavaBean
👇舉例代碼
package com.atguigu.jdbc4;
import java.sql.Connection;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
import com.atguigu.utils.Admin;
import com.atguigu.utils.Boys;
import com.atguigu.utils.JDBCUtilsByDruid;
/**
* 此類用于示範DBUtils的使用
* @author liyuting
* 功能:封裝了和資料庫存取相關的一些方法
* 通用的增删改查等等
*
* QueryRunner類:
* update(connection,sql,params):執行任何增删改語句
* query(connection,sql,ResultSetHandler,params):執行任何查詢語句
* ResultSetHandler接口
* BeanHandler:将結果集的第一行,封裝成對象,并傳回 new BeanHandler<>(XX.class)
* BeanListHandler:将結果集中的所有行,封裝成對象的集合,并傳回 new BeanListHandler<>(XX.class)
* ScalarHandler:将結果集中的第一行第一列,以Object形式傳回 new ScalarHandler()
*
*
* 使用步驟:
*
* 1、導入jar包commons-dbutils-1.3.jar
* 2、看幫助
* 3、使用
*
*/
public class TestDBUtils {
@Test
public void testUpadte() throws Exception{
//1.擷取連接配接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.執行增删改
QueryRunner qr = new QueryRunner();
int update = qr.update(connection, "update boys set boyname=? where id=4", "慕容複");
System.out.println(update>0?"success":"failure");
//3.關閉連接配接
JDBCUtilsByDruid.close(null, null, connection);
}
@Test
public void testQuerySingle() throws Exception{
//1.擷取連接配接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.執行增删改
QueryRunner qr = new QueryRunner();
// Admin admin = qr.query(connection, "select * from admin where id=?", new BeanHandler<>(Admin.class),3);
// System.out.println(admin);
Boys boys = qr.query(connection, "select * from boys where usercp=?", new BeanHandler<>(Boys.class),300);
System.out.println(boys);
//3.關閉連接配接
JDBCUtilsByDruid.close(null, null, connection);
}
@Test
public void testQueryMulti() throws Exception{
//1.擷取連接配接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.執行增删改
QueryRunner qr = new QueryRunner();
// Admin admin = qr.query(connection, "select * from admin where id=?", new BeanHandler<>(Admin.class),3);
// System.out.println(admin);
List<Admin> list2 = qr.query(connection, "select * from admin", new BeanListHandler<>(Admin.class));
for (Admin admin : list2) {
System.out.println(admin);
}
//
// List<Boys> list = qr.query(connection, "select * from boys where usercp>?", new BeanListHandler<>(Boys.class),10);
//
// for (Boys boys : list) {
// System.out.println(boys);
// }
//3.關閉連接配接
JDBCUtilsByDruid.close(null, null, connection);
}
@Test
public void testScalar() throws Exception{
//1.擷取連接配接
Connection connection = JDBCUtilsByDruid.getConnection();
//2.執行查詢單個值
QueryRunner qr = new QueryRunner();
Object query = qr.query(connection, "select * from admin", new ScalarHandler());
System.out.println(query);
//3.關閉
JDBCUtilsByDruid.close(null, null, connection);
}
}
9 DAO和增删改查通用方法
DAO:Data Access Object通路資料資訊的類和接口,包括了對資料的CRUD(Create、Retrival、Update、Delete),而不包含任何業務相關的資訊
作用:為了實作功能的子產品化,更有利于代碼的維護和更新。
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import com.atguigu.utils.JDBCUtilsByDruid;
public class BasicDao<T> {
QueryRunner qr = new QueryRunner();
/*
* 功能: 通用的增删改方法,針對于任何表
*/
public int update(String sql,Object...param){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, param);
return update;
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 功能:傳回單個對象,針對于任何表
* @param sql
* @param clazz
* @param params
* @return
*/
public T querySingle(String sql,Class<T> clazz,Object...params){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//執行查詢
return qr.query(connection, sql, new BeanHandler<T>(clazz), params);
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 功能:傳回多個對象,針對于任何表
* @param sql
* @param clazz
* @param params
* @return
*/
public List<T> queryMulti(String sql,Class<T> clazz,Object...params){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//執行查詢
return qr.query(connection, sql, new BeanListHandler<T>(clazz), params);
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(null, null, connection);
}
}
/**
* 功能:傳回單個值
* @param sql
* @param params
* @return
*/
public Object scalar(String sql,Object...params){
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//執行查詢
return qr.query(connection, sql, new ScalarHandler(),params);
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
JDBCUtilsByDruid.close(null, null, connection);
}
}
}
👆隻是基礎的增删查改,适合于任何的類,還需要對每一張表定義一個實體類,然後用不同的類DAO繼承basicdao
比如說adminDAO,Admin指定了泛型
package com.atguigu.dao;
import com.atguigu.bean.Admin;
public class AdminDao extends BasicDao<Admin> {
}
然後再AdminService裡面
package com.atguigu.service;
import com.atguigu.dao.AdminDao;
public class AdminService {
AdminDao dao = new AdminDao();
public boolean login(String username,String password){
Long count = (Long)dao.scalar("select count(*) from admin where username=? and password=?", username,password);
return count>0;
}
}
👇最後再在view層調用
package com.atguigu.view;
import java.util.Scanner;
import com.atguigu.service.AdminService;
public class StudentView {
AdminService as = new AdminService();
public static void main(String[] args) {
new StudentView().login();
}
public void login(){
Scanner input = new Scanner(System.in);
System.out.println("請輸入使用者名:");
String username = input.next();
System.out.println("請輸入密碼:");
String password = input.next();
if( as.login(username, password)){
System.out.println("登入成功!");
showMainMenu();
}else{
System.out.println("登入失敗!");
}
}
/**
* 功能:顯示主菜單
*/
private void showMainMenu() {
System.out.println("顯示主菜單");
}
}