1. JDBC概念說明
JDBC是Java Database Connectivity的簡稱,是Java語言訪問數據庫的接口規範,具體詳細規範內容可以參考JSR221
2. SQL語句分類
2.1 DML語句:
Data Manipulation Language的簡稱,數據操作語言(對數據表中的數據進行操作的行爲);主要是由insert、update和delete三個關鍵詞完成!
2.2 DCL語句:
Data Control language的簡稱,數據控制語言(數據庫及數據表權限相關的行爲);主要是由grant和revoke兩個關鍵詞完成!
2.3 DDL語句:
Data Definition Language的簡稱,數據定義語言(對數據庫中數據表進行操作的行爲);主要是由create、alter、drop和truncate四個關鍵詞完成!
2.4 DQL語句:
Data Query Language的簡稱,數據庫查詢語言(對數據表中的數據進行查詢的行爲);主要是由select關鍵詞完成!
2.5 TCL語句:
Transaction Control Language的簡稱,事物控制語言(數據庫中事物相關操作的行爲);主要是由commit、rollback和savepoint三個關鍵詞完成!
2. JDBC使用說明
2.1 JDBC與數據庫的鏈接:
JDBC鏈接數據庫主要是通過DriverManager類和Connection類實現:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*通過DriverManager類的getConnection()靜態方法獲取數據庫鏈接,再通過數據庫鏈接訪問數據庫*/
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC", "root", "root");
}
return;
}
}
Ps:
- 示例環境使用的是mysql數據庫,用戶名爲root,密碼爲root,端口號爲3306
- 5.1.6及以上版本的mysql,已經不需要手動通過調用Class.forName("com.mysql.jdbc.Driver")方法進行mysql數據庫驅動的註冊了,已經可以通過JDK的SPI功能自動完成加載註冊驅動類!
- 不同類型的數據庫都會實現自己的Driver驅動類;在類加載的時候,會將Driver驅動類註冊到DriverManager類中,當使用DriverManager.getConnection()方法獲取對應數據庫的Connection連接類的時候,需要通過不同的URL進行區分!
- 不同數據庫的URL存在不同的格式:
RDBMS | JDBC驅動程序名稱 | URL格式 | 默認端口 | 舉例 |
---|---|---|---|---|
oracle | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@dbip:port:databasename | 1521 |
jdbc:oracle:thin:@10.10.10.10:1521:dataBase |
mysql | com.mysql.jdbc.Driver | jdbc:mysql://dbip:port/databasename | 3306 |
jdbc:mysql://10.10.10.10:3306/dataBase |
SQLServer |
com.microsoft.jdbc.sqlserver.SQLServerDriver |
jdbc:microsoft:sqlserver://dbip:port; DatabaseName=databasename |
1433 |
jdbc:sqlserver://10.10.10.10:1433;DatabaseName=dataBase |
DB2 |
com.ibm.db2.jdbc.app.DB2Driver | jdbc:db2://dbip:port/databasename | 5000 | jdbc:db2://10.10.10.10:5000/dataBase |
PostgreSQL |
org.postgresql.Driver | jdbc:postgresql://dbip:port/databasename | 5432 | jdbc:postgresql://10.10.10.10:5432/dataBase |
- DriverManager.getConnection()方法主要是通過URL及數據庫連接參數對數據庫進行連接,參數可以拼接到URL上(URL與參數直接通過?連接,key-value之間通過=連接,參數之間通過&連接),也可以作爲配置項傳入方法中;<mysql連接參數說明>
- 切記每個Connection連接類都是連接到數據庫中的一個具體的數據庫(database),但並不是當前Connection連接類只能操作這個database;只要用戶權限滿足依然可以切換到其他database上面進行操作!
2.2 JDBC操作數據庫:
JDBC操作數據庫主要是通過Statement、PreparedStatement、CallableStatement這三個類實現,這三個類分別是由Connection類的createStatement()方法、prepareStatement()方法、prepareCall()方法獲取!
示例數據表格式:
建表語句:
CREATE TABLE `person`(
`id` INT UNSIGNED AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`info` VARCHAR(40) NOT NULL,
`age` INT,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
2.2.1 Statement類:
Statement類提供瞭如下四個主要的方法:
- execute()方法:可以執行所有類型的語句,如果語句類型爲DQL語句的時候,返回值爲true,並可以通過Statement類的getResultSet()方法獲取到結果集;如果語句類型爲DML語句的時候,返回值爲false,並可以通過Statement類的getUpdateCount()方法獲取Sql指令影響的行數;如果語句類型爲其他類型,返回值爲false,並不任何結果可獲取!
- executeQuery()方法:只能執行DQL語句,並返回結果集!
- executeUpdate()方法:可以執行DML語句和DDL語句,當執行DML語句的時候,返回Sql指令影響函數;當執行DDL語句的時候,返回0!
- executeLargeUpdate()方法:與executeUpdate()方法類似,只是返回值從int改爲了long,當Sql指令影響行數大於Interge.MAX_VALUE的時候,可以考慮使用executeLargeUpdate()方法,但是mysql不支持!
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*獲取數據庫連接*/
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
Statement stmt = conn.createStatement();
/*executeQuery方法的使用*/
/*使用executeQuery方法進行數據庫查詢,並返回結果集*/
ResultSet rs = stmt.executeQuery("SELECT * FROM person");
/*遍歷結果集展示查詢結果數據*/
while (rs.next()) {
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("info"));
System.out.println(rs.getString("age"));
}
/*execute方法的使用*/
boolean b = stmt.execute("SELECT * FROM person");
ResultSet rs1 = null;
if (b) {
rs1 = stmt.getResultSet();
}
while ((null != rs1) && (rs1.next())) {
System.out.println(rs1.getString("id"));
System.out.println(rs1.getString("name"));
System.out.println(rs1.getString("info"));
System.out.println(rs1.getString("age"));
}
/*executeUpdate方法的使用*/
int n = stmt.executeUpdate("INSERT INTO person (name,info,age) VALUES ('qq','qq_info',20)");
System.out.println(n);
/*關閉所有資源*/
rs.close();
if (null != rs1) {
rs1.close();
}
stmt.close();
conn.close();
return;
}
}
Ps:ResultSet結果集、Connection連接類、Statement類用完後,都需要手動關閉;Statement類還提供了兩個方法,closeOnCompletion()方法和isCloseOnCompletion()方法;Statement類調用closeOnCompletion()方法後,當所有依賴Statement類的ResultSet結果集都關閉後,Statement類會自動關閉!同時可以使用isCloseOnCompletion()方法來判斷Statement類是否打開了這個功能!
2.2.2 PreparedStatement類:
PreparedStatement類與Statement類相似,也提供上述四個操作方法(只是沒有參數),使用原則也一致;只是在通過Connection類獲取PreparedStatement類的時候,使用帶佔位符(?符號)的Sql和設置預編譯參數,多進行一步預編譯過程!
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*獲取數據庫連接*/
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
/*DML示例*/
/*通過Sql指令+佔位符獲取PreparedStatement對象*/
PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO person (name,info,age) VALUES (?,?,?)");
/*給預編譯設置參數*/
preparedStatement.setString(1,"n2");
preparedStatement.setString(2,"info2");
preparedStatement.setInt(3,100);
/*執行executeUpdate方法*/
preparedStatement.executeUpdate();
/*關閉資源*/
preparedStatement.close();
/*DQL示例*/
preparedStatement = conn.prepareStatement("SELECT * FROM person WHERE name = ? ");
/*給預編譯設置參數*/
preparedStatement.setString(1,"n2");
/*執行executeQuery方法*/
ResultSet rs = preparedStatement.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("info"));
System.out.println(rs.getString("age"));
}
/*關閉所有資源*/
rs.close();
preparedStatement.close();
conn.close();
return;
}
}
Ps:
- PreparedStatement類在設置預編譯參數的時候,下標序號是從1開始,不是從0開始
- 更加推薦使用PreparedStatement方式,性能更好、無需拼接Sql、而且通過佔位符預編譯可以有效的防止Sql注入,更加安全!
- 佔位符只能替換Sql語句的普通值,不能替換庫名、表名、字段名和關鍵詞!!!
2.2.3 CallableStatement類:
CallableStatement類是JDBC提供用來調用數據庫存儲過程的一個操作類,使用說明如下:
存儲過程示例:
//存儲過程示例
delimiter $$
create procedure sum(a int, b int, out sum)
begin
set sum = a + b;
end$$
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*獲取數據庫連接*/
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
/*生成CallableStatement實例*/
CallableStatement call = conn.prepareCall("call fun(?,?,?)");
/*設置存儲過程輸入參數*/
call.setInt(1, 100);
call.setInt(2, 100);
/*設置存儲過程輸出類型*/
call.registerOutParameter(3,Types.INTEGER);
/*執行存儲過程*/
call.execute();
/*獲取存儲過程輸出值*/
System.out.println(call.getInt(3));
call.close();
conn.close();
return;
}
}
2.3 JDBC獲取結果集
2.3.1 ResultSet結果集:
在實例化PreparedStatement類和Statement類的時候,可以設置返回結果集的類型,是否可以更新、是否可以自由移動指針等等!
- resultSetType參數:
- ResultSet.TYPE_FORWARD_ONLY:設置指針只能向前移動
- ResultSet.TYPE_SCROLL_INSENSITIVE:設置指針可以自由移動,但是底層數據的改變不會影響ResultSet結果集的內容
- ResultSet.TYPE_SCROLL_SENSITIVE:設置指針可以自由移動,但是底層數據的改變會影響ResultSet結果集的內容
- resultSetConcurrency參數:
- ResultSet.CONCUR_READ_ONLY:設置ResultSet結果集爲只讀模式
- ResultSet.CONCUR_UPDATABLE:設置ResultSet結果集爲可更新模式
Statement createStatement(int resultSetType, int resultSetConcurrency,
int resultSetHoldability)
PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency)
ResultSet結果集常用方法:
/*光標移動相關方法*/
//向前滾動
rs.previous();
//向後滾動
rs.next();
//得到當前行號
rs.getRow();
//光標定位到n行
rs.absolute(n);
//相對移動n行
rs.relative(int n);
//將光標定位到結果集中首行
rs.first();
//將光標定位到結果集中尾行
rs.last();
//將光標定位到結果集中首行之前
rs.beforeFirst();
//將光標定位到結果集中尾行之後
rs.afterLast();
/*確認光標位置相關方法*/
//光標是否在首行
rs.isFirst();
//光標是否在尾行
rs.isLast();
//光標是否在首行之前
rs.isBeforeFirst();
//光標是否在尾行之後
rs.isAfterLast();
/*更新結果集相關方法*/
//將數據插入結果集和數據表中
rs.insertRow();
//將數據從結果集和數據表中刪除
rs.deleteRow();
//更新當前行某一列的數據
rs.updateXXX(int column, XXX data);
//更新當前行某一列的數據
rs.updateXXX(String columnName, XXX Data);
//將結果集中的更新的數據推送到數據表中
rs.updateRow();
當指針移動到指定行後,可以通過getXXX(int column)方法或者getXXX(String columnName)方法,獲取當前行,特定列的數據!也可以使用<T> T getObject(int colum, Class<T> type)方法或者<T> T getObject(String columnName, Class<T> type)獲取任意類型的值!
Ps:
- 使用可更新的結果集,要保證結果集中的數據全部來自一張數據表,並且結果集中有包含主鍵列,否則會更新失敗!
- ResultSet結果集是一個與數據庫長連接的結果集,使用過程中要保證Connection對象處於連接狀態,否則訪問ResultSet結果集的時候會報錯!
- 結果集的列數據獲取,下標都是從1開始的!
2.3.1 RowSet結果集:
ResultSet結果集是一個與數據庫長連接的結果集,RowSet是ResultSet的子接口,其實現類有JdbcRowSet、CachedRowSet、FilteredRowSet、JoinRowSet、WebRowSet!其中只有JdbcRowSet是長連接結果集,其餘的都是離線結果集!離線結果集便於存儲、傳輸、序列化,而且也提高了處理性能!
2.3.2 CachedRowSet離線結果集:
ResultSet結果集屬於數據庫長連接結果集,如果斷開與數據庫的連接,在訪問Result結果集,程序會報錯!所有更多的情況,是將ResultSet結果集在內存合理的範圍內轉換爲離線結果集,再提供給外部使用!
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
preparedStatement = conn.prepareStatement("SELECT * FROM person WHERE name = ? ");
preparedStatement.setString(1,"n1");
ResultSet rs = preparedStatement.executeQuery();
/*通過ResultSet結果集生成離線CachedRowSet結果集*/
RowSetFactory factory = RowSetProvider.newFactory();
CachedRowSet cachedRowSet = factory.createCachedRowSet();
cachedRowSet.populate(rs);
/*關閉所有資源*/
rs.close();
preparedStatement.close();
conn.close();
/*通過CachedRowSet結果集獲取數據*/
while (cachedRowSet.next()) {
System.out.println(cachedRowSet.getString("id"));
System.out.println(cachedRowSet.getString("name"));
System.out.println(cachedRowSet.getString("info"));
System.out.println(cachedRowSet.getString("age"));
}
return;
}
}
CachedRowSet結果集的分頁:
如果ResultSet結果集比較大的時候,就不能全量加載到CachedRowSet結果集,否則可能會造成內存不足的情況;所有需要對CachedRowSet結果集進行分頁操作;每次只從ResultSet結果集中獲取一部分數據!當然這樣的操作依然需要保證底層與數據庫處於連接狀態!
分頁相關方法:
2.4 JDBC操作事物:
JDBC對於數據庫事物相關操作都是基於Connection連接類的如下方法實現的
Ps:
- 通過setAutoCommit()方法關閉自動提交後,即開啓了事務;當前連接中的所有DML語句都不會立即生效,除非執行commit()方法顯示提交事務,或者通過當執行DDL語句、DCL語句、session退出的方式隱形提交事務;提交事務後,之前的DML語句纔會生效!
- 除了通過關閉自動提交開啓事務,也可以通過begin命令或者start transaction命令,開啓事務!
- 不管是隱性事務提交、顯性事務提交或者回滾操作,都會導致事務終止,不再繼續執行!
- 還可以根據保存點進行回滾操作,這種回滾操作,會保留保存點之前的事務行爲;由於這種回滾行爲保留了保存點之前的事務,所以已經記得及時commit事物,保證數據更新!
- 使用try-with-resource打開Connection連接類的時候,如果拋出一個未處理的SqlException異常,Connection類會自動回滾事務,並關閉事務!否則就需要人工去手動捕獲SqlException異常,在catch代碼塊中進行回滾操作!
2.5 Java8批量更新數據:
Java8後的版本新增了批量更新數據庫操作,可以執行DDL語句和DML語句;以前版本只能通過execute()方法一條條的去執行Sql,併發送給數據庫;現在可以使用addBatch()方法和executeBatch()方法批量執行Sql,再批量發送給數據庫!
public class JDBCTest {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
/*獲取當前自動提交狀態*/
boolean autoCommitFlag = conn.getAutoCommit();
Statement statement = conn.createStatement();
/*將自動提交狀態,設置爲flase,開啓事務*/
conn.setAutoCommit(false);
/*添加批量更新sql*/
statement.addBatch(sql1);
statement.addBatch(sql2);
statement.addBatch(sql3);
/*同時執行所有批量更新sql*/
statement.executeBatch();
/*提交事務*/
conn.commit();
/*還原自動提交狀態*/
conn.setAutoCommit(autoCommitFlag);
statement.close();
conn.close();
return;
}
}
Ps:
- 批量更新數據可以執行DDL語句和DML語句,針對DML語句建議在事務中處理,這樣可以保證批量處理數據的一致性!
- 在使用批量更新數據的時候,需要先確認當前數據庫是否支持!可以通過DatabaseMetaData的supportsBatchUpdates()方法來查看!
- 當批量更新數據量超過Interge.MAX_VALUE的時候,需要使用executeLargeUpdate()方法代替executeBatch()方法
2.6 JDBC獲取數據庫元數據
2.6.1 結果集的元數據:
通過ResultSet結果集的getMetaData()方法可以獲取對應結果集的元數據,可以通過ResultSetMetaData對象獲取到結果集的列相關信息!
public class JDBCTest {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("select * from person");
ResultSetMetaData data = resultSet.getMetaData();
/*獲取列個數*/
System.out.println(data.getColumnCount());
/*獲取列名*/
System.out.println(data.getColumnName(1));
/*獲取列類型*/
System.out.println(data.getColumnType(1));
resultSet.close();
statement.close();
conn.close();
return;
}
}
2.6.2 數據庫的元數據:
通過Connection連接類的getMetaData()方法,可以獲取到數據庫的元數據DatabaseMetaData對象,通過DatabaseMetaData對象可以獲取到數據庫的詳細信息!
public class JDBCTest {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
DatabaseMetaData data = conn.getMetaData();
/*獲取所有符合規則的數據表信息*/
ResultSet rs = data.getTables(null,null,"%",new String[]{"TABLE"});
while (rs.next()) {
for (int i = 1; i < rs.getMetaData().getColumnCount(); i++) {
System.out.println(rs.getMetaData().getColumnName(i) + " : " + rs.getString(i));
}
}
rs.close();
conn.close();
return;
}
}
Ps:DatabaseMetaData類其中的方法,基本上都是存在過濾條件的;可以使用null對象來忽略過濾條件,也可以使用"%"代替任意字符串、使用"_"代替任意一個字符!
2.6.3 使用系統表獲取數據庫信息:
常規數據都會有一個系統數據表,用來存放數據庫信息,可以通過系統數據表獲取對應的系統信息!
例如Mysql系統表如下:
2.7 數據庫連接池
頻繁的與數據庫的進行連接、關閉操作,會造成IO和性能的浪費;所以引入數據庫連接池的概念;常用的數據庫連接池有DBCP和C3P0!
2.7.1 DBCP連接池(說明文檔):
pom配置:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.0</version>
</dependency>
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*創建數據源*/
BasicDataSource ds = new BasicDataSource();
/*設置驅動類*/
ds.setDriverClassName("com.mysql.jdbc.Driver");
/*設置URL*/
ds.setUrl("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
/*設置數據庫連接池的初始池子大小*/
ds.setInitialSize(5);
/*設置連接池最大連接個數*/
ds.setMaxTotal(20);
/*設置連接池最小空閒個數*/
ds.setMinIdle(5);
/*通過連接池獲取數據庫連接*/
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
boolean b = stmt.execute("SELECT * FROM person");
ResultSet rs = null;
if (b) {
rs = stmt.getResultSet();
}
while ((null != rs) && (rs.next())) {
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("info"));
System.out.println(rs.getString("age"));
}
rs.close();
stmt.close();
conn.close();
return;
}
}
2.7.2 C3P0連接池(說明文檔):
pom配置:
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
代碼示例:
public class JDBCTest {
public static void main(String[] args) throws Exception {
/*創建數據源*/
ComboPooledDataSource ds = new ComboPooledDataSource();
/*設置驅動類*/
ds.setDriverClass("com.mysql.jdbc.Driver");
/*設置URL*/
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mysql_test?serverTimezone=UTC&user=root&password=root");
/*設置數據庫連接池的初始池子大小*/
ds.setInitialPoolSize(5);
/*設置連接池最大連接個數*/
ds.setMaxPoolSize(20);
/*設置連接池最小空閒個數*/
ds.setMinPoolSize(5);
/*通過連接池獲取數據庫連接*/
Connection conn = ds.getConnection();
Statement stmt = conn.createStatement();
boolean b = stmt.execute("SELECT * FROM person");
ResultSet rs = null;
if (b) {
rs = stmt.getResultSet();
}
while ((null != rs) && (rs.next())) {
System.out.println(rs.getString("id"));
System.out.println(rs.getString("name"));
System.out.println(rs.getString("info"));
System.out.println(rs.getString("age"));
}
rs.close();
stmt.close();
conn.close();
return;
}
}