如何更新網(wǎng)站緩存濟(jì)南網(wǎng)絡(luò)優(yōu)化廠家
本文全面的介紹了JDBC的相關(guān)知識(shí),包括其基礎(chǔ)與高級(jí)應(yīng)用、Service層的事務(wù)管理、ThreadLocal(本地線程變量)、 數(shù)據(jù)庫(kù)連接池、Commons Dbutils的原理及其使用!
文章目錄
- 一、JDBC基礎(chǔ)
- 1.1JDBC介紹
- 1.2JDBC的核心類與接口
- 1.java.sql.DriverManager類 (`驅(qū)動(dòng)管理器`)
- 2.java.sql.Connection接? (`數(shù)據(jù)庫(kù)連接`)
- 3.java.sql.Connection接? (`數(shù)據(jù)庫(kù)連接`)
- 4.java.sql.ResultSet接? (`結(jié)果集`)
- 1.3SQL注?問(wèn)題
- 1.4 JDBC開發(fā)步驟
- 二、JDBC高級(jí)應(yīng)用
- 2.1 三層架構(gòu)與面向結(jié)構(gòu)編程
- 2.1 封裝
- 2.2 Service層的事務(wù)管理
- 2.3 ThreadLocal(本地線程變量)
- 2.4 數(shù)據(jù)庫(kù)連接池
- 2.5 Commons Dbutils(Apache提供的dao層工具類)
- (1)原理(自定義DaoUtils實(shí)現(xiàn)通用)
- (2)Apache的DbUtils
一、JDBC基礎(chǔ)
1.1JDBC介紹
- JDBC (全稱Java DataBase Contectivity) :Java與數(shù)據(jù)庫(kù)的連接,數(shù)據(jù)庫(kù)編程。
- JDBC 是Java語(yǔ)?(JDK)為完成數(shù)據(jù)庫(kù)的訪問(wèn)操作提供的?套統(tǒng)?的標(biāo)準(zhǔn)
- 驅(qū)動(dòng)包下載: 下載驅(qū)動(dòng)jar包,下載地址:https://mvnrepository.com/,打開?址搜索 mysql 。
- (開發(fā)者通過(guò)JDK提供的規(guī)范與數(shù)據(jù)庫(kù)廠商提供的驅(qū)動(dòng),將驅(qū)動(dòng)類加載到程序中使用,進(jìn)而達(dá)到通過(guò)程序操作數(shù)據(jù)庫(kù))
1.2JDBC的核心類與接口
1.java.sql.DriverManager類 (
驅(qū)動(dòng)管理器
)
- 注冊(cè)驅(qū)動(dòng)
- 創(chuàng)建數(shù)據(jù)庫(kù)連接
(1)注冊(cè)驅(qū)動(dòng)
- 在Driver類中的靜態(tài)初始化塊中,注冊(cè)驅(qū)動(dòng):DriverManager.registerDriver(new
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!");}} }
- 在我們的應(yīng)?程序中?動(dòng)注冊(cè)驅(qū)動(dòng)的代碼也可以省略
【Class.forName(“com.mysql.cj.jdbc.Driver”);】
如果我們沒(méi)有?動(dòng)注冊(cè)驅(qū)動(dòng),驅(qū)動(dòng)管理器在獲取連接的時(shí)候發(fā)現(xiàn)沒(méi)有注冊(cè)驅(qū)動(dòng)則讀取 驅(qū)動(dòng)jar/META-INF/servicesjava.sql.Driver?件中配置的驅(qū)動(dòng)類路徑進(jìn)?注冊(cè)
不推薦
(2)獲取連接
// url 數(shù)據(jù)庫(kù)服務(wù)器的地址 // username 數(shù)據(jù)庫(kù)連接??名 // password 數(shù)據(jù)庫(kù)連接密碼 Connection connection = DriverManager.getConnection(url, "root","123456");
2.java.sql.Connection接? (
數(shù)據(jù)庫(kù)連接
)Connection對(duì)象表?Java應(yīng)?程序與數(shù)據(jù)庫(kù)之間的連接
- 通過(guò)Connection接?對(duì)象,獲取執(zhí)?SQL語(yǔ)句的Statement對(duì)象
- 完成數(shù)據(jù)的事務(wù)管理
(1) 獲取Statement對(duì)象
- Statement接?: 編譯執(zhí)?靜態(tài)SQL指令
Statement statement = connection.createStatement();
- PreparedStatement接?:繼承了Statement接?,
預(yù)編譯動(dòng)態(tài)SQL指令(解決SQL注?問(wèn)題)
PreparedStatement preparedStatement = connection.prepareStatement(sql);
- CallableStatement接?:繼承了PreparedStatement接?,可以調(diào)?存儲(chǔ)過(guò)程
CallableStatement callableStatement = connection.prepareCall(sql);
(2)事務(wù)管理
//開啟事務(wù)(關(guān)閉事務(wù)?動(dòng)提交) connection.setAutoCommit(false); //事務(wù)回滾 connection.rollback(); //提交事務(wù) connection.commit();
3.java.sql.Connection接? (
數(shù)據(jù)庫(kù)連接
)—用于編譯、執(zhí)行SQL指令
// 執(zhí)?DML操作的SQL指令(返回值是受影響行數(shù)) int row = statement.executeUpdate(sql); // 執(zhí)?DQL操作的SQL指令(返回結(jié)果集) ResultSet rs = statement.executeQuery(sql);
4.java.sql.ResultSet接? (
結(jié)果集
)— ResultSet接?對(duì)象,表?查詢操作返回的結(jié)果集,提供了便利的?法?于獲取結(jié)果集中的數(shù)據(jù)
//res.next()用于判斷下一個(gè)位置是否還有值,初始時(shí)位于首元素之前while (res.next()){//res.getInt("tid"):通過(guò)數(shù)據(jù)庫(kù)字段名獲取數(shù)據(jù)//res.getInt(2):通過(guò)字段列標(biāo)獲取數(shù)據(jù)(列標(biāo)從1開始)teacher=new Teacher(res.getInt("tid"),res.getString("tname"),res.getString("gender"),res.getDate("workingdate"),res.getString("workgrade"));}
1.3SQL注?問(wèn)題
(1)什么是SQL注?問(wèn)題
- 在JDBC操作SQL指令編寫過(guò)程中,如果SQL指令中需要數(shù)據(jù),我們可以通過(guò)字符串拼接的形式將參數(shù)拼接到SQL指令中,如
String sql = "delete from books where book_id="+s;
(s就是拼接到SQL中的變量)- 使?字符串拼接變量的形式來(lái)設(shè)置SQL語(yǔ)句中的數(shù)據(jù),可能會(huì)導(dǎo)致
因變量值的改變引起SQL指令的原意發(fā)?改變
,這就被稱為SQL注?。SQL注?問(wèn)題是需要避免的- 例如:
如果s的值為1
,SQL指令 : delete from books where book_id=1,
如果s的值為1 or 1=1
,SQL指令:delete from books where book_id=1 or 1=1, 那么SQL中的條件則是?個(gè)恒等式(sql指令發(fā)生變化)
(2)如何解決SQL注?問(wèn)題
使?PreparedStatement進(jìn)?SQL預(yù)編譯解決SQL注?問(wèn)題:
- 在編寫SQL指令時(shí),如果SQL指令中需要參數(shù),?律使?
?
參數(shù)占位符- 如果SQL指令中有
?
,在JDBC操作步驟中不再使?Statement,?是從Conection對(duì)象獲取PreparedStatement對(duì)SQL指令進(jìn)?預(yù)編譯PreparedStatement preparedStatement = connection.prepareStatement(sql);
- 預(yù)編譯完成之后,通過(guò)PreparedStatement對(duì)象給預(yù)編譯后的SQL指令的
?
賦值
- prepareadStatement.setInt(參數(shù)占位符序號(hào),值);
- prepareadStatement.setString(參數(shù)占位符序號(hào),值);
- SQL指令中的所有
?
完成賦值之后,通過(guò)PreparedStatement執(zhí)?SQL執(zhí)?SQL時(shí)不再加載SQL語(yǔ)句
- int row = prepareadStatement.executeUpdate();
- ResultSet rs = preparedStatement.executeQuery();
1.4 JDBC開發(fā)步驟
- 加載驅(qū)動(dòng)
// 1.加載驅(qū)動(dòng)類
Class.forName("com.mysql.jdbc.Driver");
- 創(chuàng)建連接
//2.建立連接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/javaTest", "root", "123456");
- 編寫sql指令
//3.編寫sql語(yǔ)句String sql="insert into student values(2,'李四')";
- 創(chuàng)建執(zhí)行體
//4.創(chuàng)建執(zhí)行體stmt = conn.createStatement();
- 執(zhí)行sql指令
//5.執(zhí)行sql語(yǔ)句int row = stmt.executeUpdate(sql);
- 處理執(zhí)行結(jié)果(集)
//6.處理結(jié)果if (row>-1){System.out.println("插入成功!");}else{System.out.println("插入失敗!");}
- 釋放占用資源
//釋放資源try {stmt.close();conn.close();} catch (SQLException throwables) {throwables.printStackTrace();}
二、JDBC高級(jí)應(yīng)用
2.1 三層架構(gòu)與面向結(jié)構(gòu)編程
(1)三層架構(gòu)
三層架構(gòu)是指:視圖層 View、服務(wù)層 Service,與持久層 Dao。它們分別完成不同的功能。
- View 層:用于接收用戶提交請(qǐng)求。
- Service 層:用以實(shí)現(xiàn)系統(tǒng)的業(yè)務(wù)邏輯
- Dao 層:用以實(shí)現(xiàn)直接對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作。
(2)面向接口 (抽象) 編程
面向接口編程:程序設(shè)計(jì)時(shí),考慮易修改、易擴(kuò)展,為Service層和DAO層設(shè)計(jì)接口,便于未來(lái)更換實(shí)現(xiàn)類
在三層架構(gòu)程序設(shè)計(jì)中,采用面向接口(抽象)編程。
- 實(shí)現(xiàn)方式
- 上層對(duì)下層的調(diào)用,是通過(guò)下層接口實(shí)現(xiàn)的
- 而下層對(duì)上層的真正服務(wù)提供者,是下層接口的實(shí)現(xiàn)類
- 特點(diǎn):
- 服務(wù)標(biāo)準(zhǔn)(接口:規(guī)范相同)是
相同
的,服務(wù)提供者(實(shí)現(xiàn)類)可以更換
。這就實(shí)現(xiàn)了層間解耦合與編程的靈活性
2.1 封裝
(1)DAO封裝 — (DAO Data Access Object 數(shù)據(jù)訪問(wèn)對(duì)象)
將對(duì)數(shù)據(jù)庫(kù)中同?張數(shù)據(jù)表的JDBC操作?法封裝到同?個(gè)Java類中,這個(gè)類就是訪問(wèn)此數(shù)據(jù)表的
數(shù)據(jù)訪問(wèn)對(duì)象
(2)DTO封裝 — ( Data Transfer Object 數(shù)據(jù)傳輸對(duì)象(實(shí)體類))
在Java程序中創(chuàng)建?個(gè)屬性與數(shù)據(jù)庫(kù)表匹配的類,通過(guò)此類的對(duì)象封裝查詢到的數(shù)據(jù),我們把?于傳遞JDBC增刪查改操作的數(shù)據(jù)的對(duì)象稱之為
數(shù)據(jù)傳輸對(duì)象
2.2 Service層的事務(wù)管理
(1)事務(wù)的概念
事務(wù)是指是程序中一系列嚴(yán)密的邏輯操作,而且所有操作必須全部成功完成,否則在每個(gè)操作中所作的所有更改都會(huì)被撤消。
(2)事務(wù)的四大特性
- 原子性(Atomicity):操作這些指令時(shí),要么全部執(zhí)行成功,要么全部不執(zhí)行。只要其中一個(gè)指令執(zhí)行失敗,所有的指令都執(zhí)行失敗,數(shù)據(jù)進(jìn)行回滾,回到執(zhí)行指令前的數(shù)據(jù)狀態(tài)。
- 一致性(Consistency): 事務(wù)的執(zhí)行使數(shù)據(jù)從一個(gè)狀態(tài)轉(zhuǎn)換為另一個(gè)狀態(tài),但是對(duì)于整個(gè)數(shù)據(jù)的完整性保持穩(wěn)定。
- 隔離性(Isolation): 隔離性是當(dāng)多個(gè)用戶并發(fā)訪問(wèn)數(shù)據(jù)庫(kù)時(shí),比如操作同一張表時(shí),數(shù)據(jù)庫(kù)為每一個(gè)用戶開啟的事務(wù),不能被其他事務(wù)的操作所干擾,
多個(gè)并發(fā)事務(wù)之間要相互隔離。
- 持久性(Durability): 當(dāng)事務(wù)正確完成后,它對(duì)于數(shù)據(jù)的改變是永久性的。
(3)事務(wù)的隔離級(jí)別
第一種隔離級(jí)別:Read uncommitted(讀未提交)
如果一個(gè)事務(wù)已經(jīng)開始寫數(shù)據(jù),則另外一個(gè)事務(wù)不允許同時(shí)進(jìn)行寫操作,但允許其他事務(wù)讀此行數(shù)據(jù),該隔離級(jí)別可以通過(guò)“排他寫鎖”,但是不排斥讀線程實(shí)現(xiàn)。這樣就避免了更新丟失,卻可能出現(xiàn)臟讀,也就是說(shuō)事務(wù)B讀取到了事務(wù)A未提交的數(shù)據(jù)
解決了更新丟失,但還是可能會(huì)出現(xiàn)臟讀
第二種隔離級(jí)別:Read committed(讀提交)
如果是一個(gè)讀事務(wù)(線程),則允許其他事務(wù)讀寫,如果是寫事務(wù)將會(huì)禁止其他事務(wù)訪問(wèn)該行數(shù)據(jù),該隔離級(jí)別避免了臟讀,但是可能出現(xiàn)不可重復(fù)讀。事務(wù)A事先讀取了數(shù)據(jù),事務(wù)B緊接著更新了數(shù)據(jù),并提交了事務(wù),而事務(wù)A再次讀取該數(shù)據(jù)時(shí),數(shù)據(jù)已經(jīng)發(fā)生了改變。
解決了更新丟失和臟讀問(wèn)題
第三種隔離級(jí)別:Repeatable read(可重復(fù)讀取)
可重復(fù)讀取是指
在一個(gè)事務(wù)內(nèi)
,多次讀同一個(gè)數(shù)據(jù),在這個(gè)事務(wù)還沒(méi)結(jié)束時(shí),其他事務(wù)不能訪問(wèn)該數(shù)據(jù)(包括了讀寫),這樣就可以在同一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是一樣的,因此稱為是可重復(fù)讀隔離級(jí)別,讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫事務(wù)(但允許讀事務(wù)),寫事務(wù)則禁止任何其他事務(wù)(包括了讀寫)
,這樣避免了不可重復(fù)讀和臟讀,但是有時(shí)可能會(huì)出現(xiàn)幻讀。(讀取數(shù)據(jù)的事務(wù))可以通過(guò)“共享讀鏡”和“排他寫鎖”實(shí)現(xiàn)。解決了更新丟失、臟讀、不可重復(fù)讀、但是還會(huì)出現(xiàn)幻讀
第四種隔離級(jí)別:Serializable(序列化)
提供嚴(yán)格的事務(wù)隔離,它要求事務(wù)序列化執(zhí)行,事務(wù)只能一個(gè)接著一個(gè)地執(zhí)行,但不能并發(fā)執(zhí)行,如果僅僅通過(guò)“行級(jí)鎖”是無(wú)法實(shí)現(xiàn)序列化的,必須通過(guò)其他機(jī)制保證新插入的數(shù)據(jù)不會(huì)被執(zhí)行查詢操作的事務(wù)訪問(wèn)到。序列化是最高的事務(wù)隔離級(jí)別,同時(shí)代價(jià)也是最高的,性能很低,一般很少使用,在該級(jí)別下,事務(wù)順序執(zhí)行,不僅可以避免臟讀、不可重復(fù)讀,還避免了幻讀
解決了更新丟失、臟讀、不可重復(fù)讀、幻讀(虛讀)
隔離級(jí)別 臟讀 不可重復(fù)度 幻讀 Read uncommitted(讀未提交) √ √ √ Read committed(讀提交) × √ √ Repeatable read(可重復(fù)讀取) × × √ Serializable(序列化) × × ×
- 臟讀
- 所謂臟讀是指一個(gè)事務(wù)中訪問(wèn)到了另外一個(gè)事務(wù)未提交的數(shù)據(jù)
- 幻讀
- 一個(gè)事務(wù)讀取2次,得到的記錄條數(shù)不一致
- 不可重復(fù)讀
- 一個(gè)事務(wù)讀取同一條記錄2次,得到的結(jié)果不一致
MySQL事務(wù)管理:
- start transaction (開啟事務(wù))
- end transaction (結(jié)束事務(wù))
- rollback (事務(wù)回滾)
- commit (提交事務(wù))
(4)JDBC事務(wù)管理
- ?個(gè)事務(wù)中的多個(gè)DML操作需要
基于同?個(gè)數(shù)據(jù)庫(kù)連接
- 創(chuàng)建連接之后,設(shè)置事務(wù)?動(dòng)提交(關(guān)閉?動(dòng)提交connection.setAutoCommit(false);
- 當(dāng)當(dāng)前事務(wù)中的
所有DML操作
完成之后?動(dòng)提交 connection.commit();- 當(dāng)事務(wù)中的任何?個(gè)步驟出現(xiàn)異常,在catch代碼塊中執(zhí)?事務(wù)回滾
connection.rollback();
(5)Service層簡(jiǎn)介
DAO負(fù)責(zé)特定的數(shù)據(jù)庫(kù)操作,業(yè)務(wù)由service層進(jìn)?管理
- 業(yè)務(wù):指的是完成某一功能(軟件提供的一個(gè)功能)
- 例如:A給B轉(zhuǎn)帳(其包含A賬戶減錢,B賬戶加錢),其整體為一個(gè)業(yè)務(wù)的操作
- Servcie進(jìn)?業(yè)務(wù)處理,Service業(yè)務(wù)處理過(guò)程如果需要數(shù)據(jù)庫(kù)操作,則調(diào)?DAO完成
- Service層的一個(gè)業(yè)務(wù),可能需要調(diào)用一個(gè)或若干個(gè)DAO層對(duì)數(shù)據(jù)庫(kù)進(jìn)行處理
(6)Service層事務(wù)管理
事務(wù)管理要滿?以下條件:
- 多個(gè)DML操作需使?同?個(gè)數(shù)據(jù)庫(kù)連接
- 第?個(gè)DML操作之前設(shè)置事務(wù)?動(dòng)提交
- 所有DML操作執(zhí)?完成之后提交事務(wù)
- 出現(xiàn)異常則進(jìn)?事務(wù)回滾
1.需要解決的問(wèn)題
- Servcie層事務(wù)可能涉及多個(gè)DAO層,其中多個(gè)數(shù)據(jù)庫(kù)的DML操作是相互獨(dú)?的,如何保證所有DML要么同時(shí)成功,要么同時(shí)失敗呢?
2.解決辦法:
讓Service事務(wù)中的多個(gè)DML使?同?個(gè)數(shù)據(jù)庫(kù)連接
方式一:在Service獲取連接對(duì)象,將連接對(duì)象傳遞到DAO中
- 分析: DAO類中的Connection對(duì)象需要通過(guò)Service傳遞給進(jìn)來(lái),這種對(duì)象傳遞本來(lái)也?可厚?,但是當(dāng)我們通過(guò)?向接?開發(fā)時(shí)(
?向接?,是為了能夠靈活的定義實(shí)現(xiàn)類
),容易造成接?的冗余(接?污染) - 接口污染典型示例: 不同的數(shù)據(jù)庫(kù)的數(shù)據(jù)庫(kù)連接對(duì)象是不同的,MySQL的連接對(duì)象是
Connection
但Oracle數(shù)據(jù)庫(kù)則不是
方式二:使?ThreadLocal容器,實(shí)現(xiàn)多個(gè)DML操作使?相同的連接
- 不使用自定義List集合的原因:
- 存儲(chǔ)Connection的容器可以使?List集合,使?List集合做容器,在多線程并發(fā)編程中會(huì)出現(xiàn)資源競(jìng)爭(zhēng)問(wèn)題,多個(gè)并發(fā)的線程使?的是同?個(gè)數(shù)據(jù)庫(kù)連接對(duì)象(
我們的要求是同?個(gè)事務(wù)中使?同?個(gè)連接,?并?多個(gè)線程共享同一個(gè)連接
) - 為了解決并發(fā)編程的連接對(duì)象共享問(wèn)題,我們可以
使?ThreadLocal作為數(shù)據(jù)庫(kù)連接對(duì)象的容器
- 存儲(chǔ)Connection的容器可以使?List集合,使?List集合做容器,在多線程并發(fā)編程中會(huì)出現(xiàn)資源競(jìng)爭(zhēng)問(wèn)題,多個(gè)并發(fā)的線程使?的是同?個(gè)數(shù)據(jù)庫(kù)連接對(duì)象(
2.3 ThreadLocal(本地線程變量)
(1)ThreadLocal簡(jiǎn)介
ThreadLocal
叫做本地線程變量,意思是說(shuō),ThreadLocal
中填充的的是當(dāng)前線程的變量,該變量對(duì)其他線程而言是封閉且隔離的,ThreadLocal
為變量在每個(gè)線程中創(chuàng)建了一個(gè)副本,這樣每個(gè)線程都可以訪問(wèn)自己內(nèi)部的副本變量。
(2)ThreadLocal的應(yīng)用
- 在進(jìn)行對(duì)象跨層傳遞的時(shí)候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束。
- 線程間數(shù)據(jù)隔離
- 進(jìn)行事務(wù)操作,用于存儲(chǔ)線程事務(wù)信息。
- 數(shù)據(jù)庫(kù)連接,
Session
會(huì)話管理。
(3)ThreadLocal常用的方法
set()方法:
ThreadLocal對(duì)象.set()
會(huì)為ThreadLocal對(duì)象調(diào)用set()方法所在的線程中的ThreadLocal.ThreadLocalMap threadLocals = null;
進(jìn)行賦值,賦值類型為一個(gè)Map,Map的鍵為當(dāng)前的ThreadLocal對(duì)象,值為所傳入的值.set()的源碼
public void set(T value) { //獲取當(dāng)前ThreadLocal對(duì)象所在的線程 Thread t = Thread.currentThread(); //獲取所在線程中存儲(chǔ)的threadLocals(ThreadLocalMap) ThreadLocalMap map = getMap(t); //判斷map是否為空 if (map != null)//不為空,則替換值map.set(this, value); else//為空則為當(dāng)前線程創(chuàng)建ThreadLocalMap對(duì)象并為threadLocals賦值createMap(t, value); }void createMap(Thread t, T firstValue) { //為線程t中的threadLocals創(chuàng)建一個(gè)ThreadLocalMap對(duì)象進(jìn)行賦值 t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocalMap
為ThreadLocal
的一個(gè)靜態(tài)內(nèi)部類,里面定義了Entry
來(lái)保存數(shù)據(jù)。而且是繼承的弱引用。在Entry
內(nèi)部使用ThreadLocal
作為key
,使用我們?cè)O(shè)置的value
作為value
。對(duì)于每個(gè)線程內(nèi)部有個(gè)
ThreadLocal.ThreadLocalMap
變量,存取值的時(shí)候,也是從這個(gè)容器中來(lái)獲取。
- get()方法
public T get() {//獲取當(dāng)前ThreadLocal對(duì)象所在的線程Thread t = Thread.currentThread();//獲取所在線程中存儲(chǔ)的threadLocals(ThreadLocalMap)ThreadLocalMap map = getMap(t);//判斷線程中存儲(chǔ)的ThreadLocalMap是否為空if (map != null) {//不為空,則以當(dāng)前ThreadLocal對(duì)象為鍵獲取Entry對(duì)象ThreadLocalMap.Entry e = map.getEntry(this);//判斷Entry對(duì)象是個(gè)為空if (e != null) {//不為空則返回Entry對(duì)象的值@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//線程中ThreadLocalMap為空或者未存儲(chǔ)以當(dāng)前ThreadLocal對(duì)象為鍵的Entry對(duì)象時(shí)設(shè)置初始值return setInitialValue(); }//設(shè)置為線程中ThreadLocalMap的初始值 private T setInitialValue() {//設(shè)置當(dāng)前值為nullT value = initialValue();//獲取當(dāng)前ThreadLocal對(duì)象所在的線程Thread t = Thread.currentThread();//獲取所在線程中存儲(chǔ)的threadLocals(ThreadLocalMap)ThreadLocalMap map = getMap(t);//判讀map是否為空if (map != null)//map不為空向map中添加一個(gè)以當(dāng)前ThreadLocal對(duì)象為鍵。值為空的Entry對(duì)象map.set(this, value);else//map為空則為當(dāng)前線程的threadLocals進(jìn)行創(chuàng)建對(duì)象初始化createMap(t, value);return value; }protected T initialValue() {return null; }
(4)ThreadLocal的內(nèi)存泄流問(wèn)題
內(nèi)存泄漏原因
? 當(dāng)ThreadLocal為null時(shí),也就是要被垃圾回收器回收了,但是此時(shí)我們的ThreadLocalMap(thread 的內(nèi)部屬性)生命周期和Thread的一樣,它不會(huì)回收,這時(shí)候就出現(xiàn)了一個(gè)現(xiàn)象。那就是ThreadLocalMap的key沒(méi)了,但是value還在,這就造成了內(nèi)存泄漏。解決方法:
? 用完ThreadLocal
后,執(zhí)行remove
操作,避免出現(xiàn)內(nèi)存溢出情況。所以 如同lock
的操作 最后要執(zhí)行解鎖操作一樣,ThreadLocal
使用完畢一定記得執(zhí)行remove
方法,清除當(dāng)前線程的數(shù)值。如果不remove
當(dāng)前線程對(duì)應(yīng)的VALUE
,就會(huì)一直存在這個(gè)值。
(5)強(qiáng)引用,弱引用,軟引用
- 強(qiáng)引用:普通的引用,強(qiáng)引用指向的對(duì)象不會(huì)被回收;
- 軟引用:僅有軟引用指向的對(duì)象,只有發(fā)生gc且內(nèi)存不足,才會(huì)被回收;
- 弱引用:僅有弱引用指向的對(duì)象,只要發(fā)生gc就會(huì)被回收。
(6)多線程環(huán)境下ThreadLocal在事務(wù)中操作可能出現(xiàn)的問(wèn)題
在闡述此問(wèn)題時(shí),需要簡(jiǎn)要介紹一下mysql數(shù)據(jù)庫(kù)的事務(wù)默認(rèn)隔離級(jí)別(Repeatable read(可重復(fù)讀取))
- 第三種隔離級(jí)別:Repeatable read(可重復(fù)讀取)
可重復(fù)讀取是指在一個(gè)事務(wù)內(nèi)
,多次讀同一個(gè)數(shù)據(jù),在這個(gè)事務(wù)還沒(méi)結(jié)束時(shí),其他事務(wù)不能訪問(wèn)該數(shù)據(jù)(包括了讀寫),這樣就可以在同一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是一樣的,因此稱為是可重復(fù)讀隔離級(jí)別,讀取數(shù)據(jù)的事務(wù)將會(huì)禁止寫事務(wù)(但允許讀事務(wù)),寫事務(wù)則禁止任何其他事務(wù)(包括了讀寫)
,這樣避免了不可重復(fù)讀和臟讀,但是有時(shí)可能會(huì)出現(xiàn)幻讀。
- 場(chǎng)景
- 存在兩個(gè)線程,因?yàn)門hreadLocal保存數(shù)據(jù)庫(kù)連接變量,可以保證兩個(gè)線程各自擁有自己的數(shù)據(jù)庫(kù)連接,一般在操作各自線程任務(wù)的事務(wù)時(shí)不會(huì)出現(xiàn)沖突和干擾
- 現(xiàn)在有如下場(chǎng)景,兩個(gè)線程同時(shí)操作同一個(gè)數(shù)據(jù)中的同一張表,a線程使1號(hào)用戶給3號(hào)用戶轉(zhuǎn)賬,b線程使1號(hào)用戶給2號(hào)客戶轉(zhuǎn)賬,a、b線程同時(shí)運(yùn)行搶奪cpu的執(zhí)行權(quán),
此時(shí)程序中只會(huì)有一個(gè)線程執(zhí)行成功,另一個(gè)線程執(zhí)行失敗
(原因:mysql默認(rèn)為Repeatable read(可重復(fù)讀取)隔離級(jí)別,ab兩線程同時(shí)操作寫,違反了mysql的事務(wù)隔離級(jí)別)
- 根本原因:每個(gè)線程操作的數(shù)據(jù)庫(kù)中
表的副本
,在對(duì)數(shù)據(jù)庫(kù)的表操作過(guò)程中會(huì)有以下驗(yàn)證:當(dāng)a線程拿到表的副本后,會(huì)記錄拿到副本當(dāng)時(shí)表內(nèi)容狀態(tài)T
,當(dāng)它修改完自己拿到的副本后準(zhǔn)備提交給數(shù)據(jù)庫(kù)時(shí),會(huì)將自己記錄的表狀態(tài)T
與提交前數(shù)據(jù)庫(kù)中此表狀態(tài)進(jìn)行比對(duì),如果二者不一致,則
線程a不會(huì)提交自己的副本給數(shù)據(jù)庫(kù),并會(huì)報(bào)錯(cuò)(比對(duì)結(jié)果,表明在a線程修改表內(nèi)容的期間,有其他線程對(duì)表進(jìn)行了更改,所以會(huì)報(bào)錯(cuò)
)
2.4 數(shù)據(jù)庫(kù)連接池
1.引入數(shù)據(jù)庫(kù)連接池的原因
- 如果每個(gè)JDBC操作需要數(shù)據(jù)庫(kù)連接都重新創(chuàng)建,使?完成之后都銷毀,我們的JVM會(huì)因?yàn)轭l繁的創(chuàng)建、銷毀連接?占?額外的系統(tǒng)資源。
- 數(shù)據(jù)庫(kù)連接本質(zhì)上是可被重?的資源(當(dāng)?個(gè)JDBC操作完成之后,其創(chuàng)建的連接是可以被其他JDBC操作使?的),基于這個(gè)特性:
- 我們可以創(chuàng)建?個(gè) 存放數(shù)據(jù)庫(kù)連接的容器 (連接池),連接池是有最?容量的
- 當(dāng)我們要進(jìn)?JDBC操作時(shí),直接從這個(gè)容器中獲取連接:
- 如果容器中沒(méi)有空閑的連接且連接池中連接的個(gè)數(shù)沒(méi)有達(dá)到最?值,則創(chuàng)建新的數(shù)據(jù)庫(kù)連接存?連接池并給這個(gè)操作使?,使?完成之后?需關(guān)閉連接直接歸還這個(gè)容器中即可
- 如果容器中沒(méi)有空閑的連接且連接池中連接的個(gè)數(shù)達(dá)到最?值,當(dāng)前操作就會(huì)進(jìn)?等待,等待連接池中的某個(gè)連接被歸還,歸還之后再使?
- 如果容器中有空閑連接,則?需創(chuàng)建新的連接,直接從容器中獲取這個(gè)空閑連接進(jìn)?使?
2.概念
- 連接池:存放數(shù)據(jù)庫(kù)連接對(duì)象的容器
- 連接池作?:對(duì)數(shù)據(jù)庫(kù)連接進(jìn)?管理,減少因重復(fù)創(chuàng)建、銷毀連接導(dǎo)致的系統(tǒng)開銷
3.常用的線程池
功能 | dbcp | druid | c3p0 | tomcat-jdbc | HikariCP |
---|---|---|---|---|---|
是否支持PSCache | 是 | 是 | 是 | 否 | 否 |
監(jiān)控 | jmx | jmx/log/http | jmx,log | jmx | jmx |
擴(kuò)展性 | 弱 | 好 | 弱 | 弱 | 弱 |
sql攔截及解析 | 無(wú) | 支持 | 無(wú) | 無(wú) | 無(wú) |
代碼 | 簡(jiǎn)單 | 中等 | 復(fù)雜 | 簡(jiǎn)單 | 簡(jiǎn)單 |
特點(diǎn) | 依賴于common-pool | 阿里開源,功能全面 | 歷史久遠(yuǎn),代碼邏輯復(fù)雜,且不易維護(hù) | 優(yōu)化力度大,功能簡(jiǎn)單,起源于boneCP | |
連接池管理 | LinkedBlockingDeque | 數(shù)組 | FairBlockingQueue | threadlocal+CopyOnWriteArrayList |
- 由于boneCP被hikariCP替代,并且已經(jīng)不再更新,boneCP沒(méi)有進(jìn)行調(diào)研。
- proxool網(wǎng)上有評(píng)測(cè)說(shuō)在并發(fā)較高的情況下會(huì)出錯(cuò),proxool便沒(méi)有進(jìn)行調(diào)研。
- druid的功能比較全面,且擴(kuò)展性較好,比較方便對(duì)jdbc接口進(jìn)行監(jiān)控跟蹤等。
- c3p0歷史悠久,代碼及其復(fù)雜,不利于維護(hù)。并且存在deadlock的潛在風(fēng)險(xiǎn)。
- 基于連接池的性能、使?的便捷性、連接監(jiān)控等多??綜合情況,druid是?前企業(yè)應(yīng)?中使?最 ?泛的
- Hikari在SpringBoot中默認(rèn)集成,性能是諸多競(jìng)品中最好的
4.Druid線程池的使用
- 參考我的另一篇博客: Druid連接池簡(jiǎn)介及其使用
2.5 Commons Dbutils(Apache提供的dao層工具類)
(1)原理(自定義DaoUtils實(shí)現(xiàn)通用)
- 核心思想:使用泛型
- 參考我的另一篇博客: DaoUtils實(shí)現(xiàn)通用(增、刪、改、查)
(2)Apache的DbUtils
- 核心思想:反射
- 注意事項(xiàng):創(chuàng)建QueryRunner對(duì)象時(shí),不能使用無(wú)參構(gòu)造方法,需要傳入一個(gè)連接池對(duì)象(配合線程池使用)
- 參考我的另一篇博客: DaoUtils實(shí)現(xiàn)通用(增、刪、改、查)