封面 : 洛小汐
作者 : 潘潘
未來半年,有幸與導師們一起學習交流救欧,趁這個機會衰粹,把所學所感記錄下來。
自畢業(yè)以后笆怠,自己先創(chuàng)業(yè)后上班铝耻,浮沉了近8年,內心著實焦躁,雖一直是走科班路線瓢捉,但在技術道路上卻始終沒靜下心來研究频丘、思考、梳理泡态,機會來了搂漠,便抓牢。
希望自己記錄下來的知識內容某弦,對后來的學習之人桐汤,能有些許幫助。
對文章內容有任何建議或意見靶壮,
或對互聯(lián)網開發(fā)有希望交流學習叠萍,
或單純熱愛生活蛮位,
都歡迎隨時微信我:panshenlian
第一個系列的文章主要圍繞「架構師(Java)技術條線」展開聊麻蹋,不定時更新刻炒。
第一篇我以《手寫一套持久層框架》先來打個樣蝌矛,本篇文章我們先不介紹MyBatis碎罚,也不會分析源碼欣硼,我們先聊一個 Java API:JDBC混卵。
JDBC是Java的老朋友映穗,我們再一次認識他吧,挑挑他的毛病幕随,站在Java資老朋友的角度蚁滋,給他提點優(yōu)化意見,并送他一套《自定義持久層框架》赘淮。
溫馨提示:
如果大家在閱讀過程中辕录,對某些解決思路存在疑問,我建議大家先帶著疑問閱讀完梢卸,消化理解走诞,因為導師們確實是通過研究Mybatis等持久層框架源碼之后,反過來剖析的蛤高。
簡單來說 “ 大廠都這么寫蚣旱,我們且這么跟隨吧 ”。
Mybaits系列全解 (持續(xù)更新)
- Mybatis系列全解(一):手寫一套持久層框架
- Mybatis系列全解(二):Mybatis簡介與環(huán)境搭建
- Mybatis系列全解(三):Mybatis簡單CRUD使用介紹
- Mybatis系列全解(四):全網最全戴陡!Mybatis配置文件XML全貌詳解
- Mybatis系列全解(五):全網最全塞绿!詳解Mybatis的Mapper映射文件
- Mybatis系列全解(六):Mybatis最硬核的API你知道幾個?
- Mybatis系列全解(七):Dao層兩種實現(xiàn)方式
- Mybatis系列全解(八):Mybatis的動態(tài)SQL
- Mybatis系列全解(九):Mybatis的復雜映射
- Mybatis系列全解(十):Mybatis注解開發(fā)
- Mybatis系列全解(十一):Mybatis緩存全解
- Mybatis系列全解(十二):Mybatis插件開發(fā)
- Mybatis系列全解(十三):Mybatis代碼生成器
- Mybatis系列全解(十四):Spring集成Mybatis
- Mybatis系列全解(十五):SpringBoot集成Mybatis
- Mybatis系列全解(十六):Mybatis源碼剖析
一恤批、JDBC是誰异吻?
JDBC是誰?干啥的喜庞?到底有多能打诀浪?看看網絡上的朋友們怎么說棋返。
Java數(shù)據(jù)庫連接,(Java Database Connectivity雷猪,簡稱JDBC)是Java語言中用來規(guī)范客戶端程序如何來訪問數(shù)據(jù)庫的應用程序接口懊昨,提供了諸如查詢和更新數(shù)據(jù)庫中數(shù)據(jù)的方法。
-- 來自百度百科
JDBC(Java DataBase Connectivity,java數(shù)據(jù)庫連接)是一種用于執(zhí)行SQL語句的Java API春宣,可以為多種關系數(shù)據(jù)庫提供統(tǒng)一訪問酵颁,它由一組用Java語言編寫的類和接口組成。JDBC提供了一種基準月帝,據(jù)此可以構建更高級的工具和接口躏惋,使數(shù)據(jù)庫開發(fā)人員能夠編寫數(shù)據(jù)庫應用程序。
-- 來自360百科
... 無法訪問此網站
-- 來自維基百科
以上基本就是JDBC的大致介紹嚷辅,官方且嚴謹?shù)恼f辭簿姨,That's It , 我們往下看看簸搞,它曾經的高光時刻扁位。
自從Java語言于1995年5月正式公布以來,Java風靡全球趁俊。出現(xiàn)大量的用java語言編寫的程序域仇,其中也包括數(shù)據(jù)庫應用程序。由于沒有一個Java語言的API寺擂,編程人員不得不在Java程序中加入C語言的ODBC函數(shù)調用暇务。這就使很多Java的優(yōu)秀特性無法充分發(fā)揮,比如平臺無關性怔软、面向對象特性等垦细。隨著越來越多的編程人員對Java語言的日益喜愛,越來越多的公司在Java程序開發(fā)上投入的精力日益增加挡逼,對java語言接口的訪問數(shù)據(jù)庫的API的要求越來越強烈括改。也由于ODBC的有其不足之處,比如它并不容易使用家坎,沒有面向對象的特性等等嘱能,SUN公司決定開發(fā)一Java語言為接口的數(shù)據(jù)庫應用程序開發(fā)接口。在JDK1.x版本中乘盖,JDBC只是一個可選部件焰檩,到了JDK1.1公布時,SQL類包(也就是JDBCAPI)就成為Java語言的標準部件订框。
后面從JDBC1.0到JDBC4.0析苫,一路發(fā)展。
-- 來自網絡
結合介紹說明加深我們對JDBC的了解。
不過衩侥,我想知道他平時是如何工作的国旷?一張圖 《 JDBC 基本架構 》 了解一下:
有了JDBC,向各種關系數(shù)據(jù)庫發(fā)送SQL語句就是一件很容易的事茫死。
換言之跪但,有了JDBC API,就不必為訪問Sybase數(shù)據(jù)庫專門寫一個程序峦萎,為訪問Oracle數(shù)據(jù)庫又專門寫一個程序屡久,或為訪問Mysql數(shù)據(jù)庫又編寫另一個程序等等,程序員只需用JDBC API寫一個程序就夠了爱榔,它可向相應數(shù)據(jù)庫發(fā)送SQL調用被环。
同時,將Java語言和JDBC結合起來使程序員不必為不同的平臺編寫不同的應用程序详幽,只須寫一遍程序就可以讓它在任何平臺上運行筛欢,這也是Java語言"編寫一次,處處運行"的優(yōu)勢唇聘。
我們再來看看他工作的細節(jié)版姑。
畢竟,有人說過:想了解一個人迟郎,就得先仔細了解Ta的工作剥险。
二、JDBC如何工作谎亩?
JDBC API 允許應用程序訪問任何形式的表格數(shù)據(jù)炒嘲,特別是存儲在關系數(shù)據(jù)庫中的數(shù)據(jù)宇姚。
執(zhí)行流程主要分三步:
- 連接數(shù)據(jù)源匈庭。
- 為數(shù)據(jù)庫傳遞查詢和更新指令。
- 處理數(shù)據(jù)庫響應并返回的結果浑劳。
但實際上阱持,每步流程都特別細節(jié):
使用流程 (詳細說明)
1.加載數(shù)據(jù)庫驅動:
程序中使用Class.forName('驅動')加載驅動,JVM會尋找并加載指定驅動類魔熏,同時執(zhí)行驅動類的靜態(tài)代碼段衷咽,在JDK1.6之前JDBC規(guī)范中明確要求各家在實現(xiàn)Driver類時必須在靜態(tài)代碼段中向DriverManager注冊實例,JDK1.6之后各家實現(xiàn)的Driver類則不再需要主動注冊實例蒜绽,因為DriverManager已經在初始化階段對所有jar包中實現(xiàn)了java.sql.Driver的類進行掃描并進行初始化镶骗。
- 創(chuàng)建數(shù)據(jù)庫連接:
DriverManager通過遍歷所有已注冊的驅動來嘗試獲取連接,第一個匹配上就會直接返回躲雅,并使用對應驅動建立起客戶端與數(shù)據(jù)庫服務器的網絡連接(物理連接Socket了解一下)鼎姊。
- 創(chuàng)建編譯對象:
數(shù)據(jù)庫連接connection成功之后,我們會向數(shù)據(jù)庫發(fā)送一次請求(statement),執(zhí)行一條sql語句相寇,一個連接可以執(zhí)行多次statement慰于,除非你關閉連接,其中還有一個概念就是事務transaction唤衫,事務和請求可以是一對一婆赠,也可以是一對多,這取決于你是想把多個請求statement作為同一個事務提交佳励,還是一個請求提交一次事務休里,JDBC默認是事務是自動提交,即auto-commit是打開的赃承,所以默認是一對一份帐。
- 設置入?yún)?zhí)行SQL:
為了防止SQL注入,我們使用預處理在sql中使用?作為輸入?yún)?shù)的占位符楣导,sql在編譯后成為安全的sql語句再進行查詢(有緣我們可以聊聊為何預處理機制能防止SQL注入)废境。
- 封裝返回結果集:
SQL執(zhí)行之后會把結果集封裝到ResultSet類,ResultSet類本身的迭代器初始行數(shù)的位置是1筒繁,所以我們會發(fā)現(xiàn)與java.util.Iterator接口的迭代初始行數(shù)為0有差異噩凹,同時ResultSet類本身沒有提供hasNext方法,所以我們會不斷的while(rs.next())往后定位毡咏,再通過不同的類型的訪問器讀取數(shù)據(jù)(例如getString,getInteger等)驮宴。
- 釋放數(shù)據(jù)庫連接資源:
考慮到數(shù)據(jù)庫連接占用了數(shù)據(jù)庫服務器的內存資源,所以不可能無限制建立連接呕缭,用完就釋放堵泽,養(yǎng)成好習慣,目前很多成熟的數(shù)據(jù)連接池技術恢总,很好的優(yōu)化管理的數(shù)據(jù)連接問題迎罗。
我們通過一段簡單的例子來演示一下使用流程,本例子使用JDBC操作mysql數(shù)據(jù)庫片仿,先看看我們最終的項目結構與JDBC API在JDK中rt.jar的結構:
- 項目結構:
- JDBC API在JDK中rt.jar的結構:
默認已具備java開發(fā)環(huán)境纹安、mysql數(shù)據(jù)庫
- 創(chuàng)建mave工程,并且引入mysql驅動依賴
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
</dependencies>
- 創(chuàng)建java測試類
package com.panshenlian.jdbc;
import com.panshenlian.po.User;
import java.sql.*;
/**
* @Author: panshenlian
* @Description: 演示通過JDBC連接mysql數(shù)據(jù)庫
* @Date: Create in 20:11 2020/11/10
*/
public class Test01 {
public static void main(String[] args) {
User user = new User();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 加載數(shù)據(jù)庫驅動
Class.forName("com.mysql.jdbc.Driver");
// 通過驅動管理類獲取數(shù)據(jù)庫連接
connection =
DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mybatis"+
"?characterEncoding=utf-8",
"root","123456");
// 定義SQL語句 砂豌? 表示占位符
String sql = " select * from user where username = ? ";
// 獲取預處理statement對象
preparedStatement = connection.prepareStatement(sql);
// 設置參數(shù)
// 第一個參數(shù)sql語句中參數(shù)的序號(從1開始)
// 第二個參數(shù)為設置的參數(shù)值
preparedStatement.setString(1,"panshenlian");
// 向數(shù)據(jù)庫發(fā)出sql執(zhí)行查詢厢岂,查詢出結果集
resultSet = preparedStatement.executeQuery();
// 遍歷查詢結果集
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("username");
// 封裝User
user.setId(id);
user.setUserName(name);
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null){
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
- 創(chuàng)建User類
package com.panshenlian.po;
/**
* @Author: panshenlian
* @Description: 用戶實體
* @Date: Create in 20:10 2020/11/10
*/
public class User {
private Integer id;
private String userName;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
'}';
}
}
- 創(chuàng)建sql語句
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`birthday` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'senly', '123', '2020-11-10');
INSERT INTO `user` VALUES ('2', 'panshenlian', '123456', '2020-11-10');
- 執(zhí)行結果,nice , 成功阳距。
User{id=2, userName='panshenlian'}
看完這段演示塔粒,大家是否發(fā)現(xiàn)一個問題?就是整個JDBC操作數(shù)據(jù)庫的使用過程繁瑣而尷尬筐摘,就如這場對話:
額(⊙o⊙)… JDBC你確實挺煩的卒茬。
我懂你需要和數(shù)據(jù)庫建立連接映跟、執(zhí)行SQL語句、處理查詢結果集...
但是扬虚,這整個過程努隙,能不能優(yōu)化一下呢?
三辜昵、JDBC存在哪些待優(yōu)化的地方荸镊?
我們平時瘦身增肌,工作更得提質增效堪置,來躬存,我們剖開代碼,逐個分析:
// 加載數(shù)據(jù)庫驅動
Class.forName("com.mysql.jdbc.Driver");
// 通過驅動管理類獲取數(shù)據(jù)庫鏈接
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
"root","123456");
-
存在問題1:數(shù)據(jù)庫配置信息存在硬編碼問題舀锨。
優(yōu)化思路:使用配置文件岭洲!
-
存在問題2:頻繁創(chuàng)建、釋放數(shù)據(jù)庫連接問題坎匿。
優(yōu)化思路:使用數(shù)據(jù)連接池盾剩!
// 定義SQL語句 ? 表示占位符
String sql = " select * from user where username = ? ";
// 獲取預處理statement對象
preparedStatement = connection.prepareStatement(sql);
// 設置參數(shù)替蔬,第一個參數(shù)sql語句中參數(shù)的序號(從1開始)告私,第二個參數(shù)為設置的參數(shù)值
preparedStatement.setString(1,"tom");
// 向數(shù)據(jù)庫發(fā)出sql執(zhí)行查詢,查詢出結果集
resultSet = preparedStatement.executeQuery();
-
存在問題3:SQL語句承桥、設置參數(shù)驻粟、獲取結果集參數(shù)均存在硬編碼問題 。
優(yōu)化思路:使用配置文件凶异!
// 遍歷查詢結果集
while(resultSet.next()){
int id = resultSet.getInt("id");
String userName = resultSet.getString("username");
// 封裝User
user.setId(id);
user.setUserName(userName);
System.out.println(user);
}
-
存在問題4:手動封裝返回結果集蜀撑,較為繁瑣。
優(yōu)化思路:使用Java反射剩彬、自士崧蟆!
針對JDBC各個環(huán)節(jié)中存在的不足襟衰,現(xiàn)在贴铜,我們整理出對應的優(yōu)化思路,統(tǒng)一匯總:
存在問題 | 優(yōu)化思路 |
---|---|
數(shù)據(jù)庫配置信息存在硬編碼問題 | 使用配置文件 |
頻繁創(chuàng)建瀑晒、釋放數(shù)據(jù)庫連接問題 | 使用數(shù)據(jù)連接池 |
SQL語句、設置參數(shù)徘意、獲取結果集參數(shù)均存在硬編碼問題 | 使用配置文件 |
手動封裝返回結果集苔悦,較為繁瑣 | 使用Java反射、自省 |
假如讓你來優(yōu)化椎咧,你會根據(jù)這些優(yōu)化思路如何設計一套持久層框架呢玖详?
四把介、自定義持久層框架:思路分析
JDBC是個人作戰(zhàn),凡事親力親為蟋座,低效而高險拗踢,自己加載驅動,自己建連接向臀,自己 ...
而持久層框架好比是多工種協(xié)作巢墅,分工明確,執(zhí)行高效券膀,有專門負責解析注冊驅動建立連接的君纫,有專門管理數(shù)據(jù)連接池的,有專門執(zhí)行sql語句的芹彬,有專門做預處理參數(shù)的蓄髓,有專門裝配結果集的 ...
框架的作用,就是為了幫助我們減去繁重開發(fā)細節(jié)與冗余代碼舒帮,使我們能更加專注于業(yè)務應用開發(fā)会喝。
來,我們一起看看使用JDBC和使用持久層框架有什么區(qū)別玩郊?
使用框架對于我們使用者(主要是研發(fā)人員)好乐,是有多舒爽呢?
是不是發(fā)現(xiàn)瓦宜,擁有這么一套持久層框架是如此舒適蔚万,我們僅僅需要干兩件事:
- 配置數(shù)據(jù)源(地址/數(shù)據(jù)名/用戶名/密碼)
- 編寫SQL與參數(shù)準備(SQL語句/參數(shù)類型/返回值類型)
框架,除了思考本身的工程設計临庇,還需要考慮到實際項目端的使用場景反璃,干系方涉及兩端:
使用端(實際項目)
持久層框架本身
以上兩步,我們通過一張架構圖《 手寫持久層框架基本思路 》來梳理清楚:
核心接口/類重點說明:
分工協(xié)作 | 角色定位 | 類名定義 |
---|---|---|
負責讀取配置文件 | 資源輔助類 | Resources |
負責存儲數(shù)據(jù)庫連接信息 | 數(shù)據(jù)庫資源類 | Configuration |
負責存儲SQL映射定義假夺、存儲結果集映射定義 | SQL與結果集資源類 | MappedStatement |
負責解析配置文件淮蜈,創(chuàng)建會話工廠SqlSessionFactory | 會話工廠構建者 | SqlSessionFactoryBuilder |
負責創(chuàng)建會話SqlSession | 會話工廠 | SqlSessionFactory |
指派執(zhí)行器Executor | 會話 | SqlSession |
負責執(zhí)行SQL (配合指定資源Mapped Statement) | 執(zhí)行器 | Executor |
正常來說項目只對應一套數(shù)據(jù)庫環(huán)境,一般對應一個SqlSessionFactory實例對象已卷,我們使用單例模式只創(chuàng)建一個SqlSessionFactory實例梧田。
如果需要配置多套數(shù)據(jù)庫環(huán)境,那需要做一些拓展侧蘸,例如Mybatis中通過environments等配置就可以支持多套測試/生產數(shù)據(jù)庫環(huán)境進行切換裁眯。
梳理完持久層框架的基本思路,明確了框架各角色分工讳癌,我們開始梳理詳細方案:
A穿稳、項目使用端,調用框架API晌坤,除了引入持久層框架的jar包之外逢艘,還需額外提供兩部分配置信息:
?
1. sqlMapConfig.xml : 數(shù)據(jù)庫配置信息(地址/數(shù)據(jù)名/用戶名/密碼)旦袋,以及mapper.xml的全路徑。
2. mapper.xml : SQL配置信息它改,存放SQL語句疤孕、參數(shù)類型、返回值類型相關信息央拖。
B祭阀、框架本身,實質上就是對JDBC代碼進行封裝爬泥,基本6步:
- 加載配置文件:根據(jù)配置文件的路徑柬讨,加載配置文件成字節(jié)輸入流,存儲在內存中袍啡。
創(chuàng)建Resource類踩官,提供加載流方法:InputStream getResourceAsStream(String path)
- 創(chuàng)建兩個javaBean(容器對象):存放配置文件解析出來的內容
Configuration(核心配置類):存放sqlMapConfig.xml解析出來的內容。
MappedStatement(映射配置類):存放mapper.xml解析出來的內容境输。
- 解析配置文件(使用dom4j) 蔗牡,并創(chuàng)建SqlSession會話對象
創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in)
> 使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中
> 創(chuàng)建SqlSessionFactory對象嗅剖,生產sqlSession會話對象(工廠模式)
- 創(chuàng)建SqlSessionFactory接口以及實現(xiàn)類DefaultSqlSessionFactory
創(chuàng)建openSession()接口方法辩越,生產sqlSession
- 創(chuàng)建SqlSession接口以及實現(xiàn)類DefaultSqlSession
定義對數(shù)據(jù)庫的CRUD操作:
> selectList();
> selectOne();
> update();
> delete();
- 創(chuàng)建Executor接口以及實現(xiàn)類SimpleExecutor
創(chuàng)建query(Configuration conf,MappedStatement ms,Object... params)
實際執(zhí)行的就是JDBC代碼。
基本過程我們已經清晰信粮,我們再細化一下類圖黔攒,更好的助于我們實際編碼:
簡約版
詳細版
最終手寫的持久層框架結構參考:
包接口類說明
- config包
接口/類 | 作用 |
---|---|
BoundSql | 保存Sql語句的對象眼刃,替換sql#{}成為?號并且存儲#{}對應的參數(shù)名 |
XMLConfigBuilder | SqlMapConfig.xml配置文件解析工具類 |
XMLMapperBuilder | Mapper.xml配置文件解析工具類 |
- io包
接口/類 | 作用 |
---|---|
Resource | 讀取SqlMapConfig.xml和Mapper.xml的工具類蒜胖,轉換為輸入流inputStream |
- pojo包
接口/類 | 作用 |
---|---|
Configuration | 封裝SqlMapConfig.xml配置參數(shù) |
MappedStatement | 封裝Mapper.xml配置的sql參數(shù) |
- sqlSession包
接口/類 | 作用 |
---|---|
SqlSessionFactoryBuilder | SqlSessionFactory構建者類 |
SqlSessionFactory | 生產SqlSession的工廠接口 |
DefaultSqlSessionFactory | SqlSessionFactory的默認實現(xiàn)類 |
SqlSession | SqlSession接口定義數(shù)據(jù)庫基本的CRUD方法 |
DefaultSqlSession | SqlSession的實現(xiàn)類 |
Executor | Executor接口sql的真正執(zhí)行者,使用JDBC操作數(shù)據(jù)庫 |
SimpleExecutor | Executor的實現(xiàn)類 |
- utils
接口/類 | 作用 |
---|---|
ParameterMapping | 來源于Mybatis框架茴厉,SQL參數(shù)映射類旅掂,存儲#{}赏胚、${}中的參數(shù)名 |
TokenHandler | 來源于Mybatis框架,標記處理器接口 |
ParameterMappingTokenHandler | 來源于Mybatis框架商虐,標記處理器實現(xiàn)類觉阅,解析#{}、${}成為? |
GenericTokenParser | 來源于Mybatis框架秘车,通用標記解析器典勇,標記#{與}開始結束處理 |
五、自定義持久層框架:編碼
結合UML圖和項目結構圖鲫尊,腦海里開始有點東西了痴柔,燒腦且枯燥的編碼過程,我們開始吧疫向。
框架依賴 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.panshenlian</groupId>
<artifactId>MyPersistence</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- 持久層框架所需要的的依賴 -->
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
</project>
config包下BoundSql類
package com.panshenlian.config;
import com.panshenlian.utils.ParameterMapping;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: panshenlian
* @Description: SQL通配類
* @Date: Create in 16:12 2020/11/12
*/
public class BoundSql {
/**
* 解析過的sql語句
*/
private String sqlText;
private List<ParameterMapping> parameterMappingList =
new ArrayList<ParameterMapping>();
public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
this.sqlText = sqlText;
this.parameterMappingList = parameterMappingList;
}
public String getSqlText() {
return sqlText;
}
public void setSqlText(String sqlText) {
this.sqlText = sqlText;
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
this.parameterMappingList = parameterMappingList;
}
}
config包下XMLConfigBuilder類
package com.panshenlian.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.Configuration;
import com.sun.javafx.scene.control.skin.EmbeddedTextContextMenuContent;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;
/**
* @Author: panshenlian
* @Description: 數(shù)據(jù)庫配置信息解析類
* @Date: Create in 13:56 2020/11/12
*/
public class XMLConfigBuilder {
private Configuration configuration;
public XMLConfigBuilder() {
this.configuration = new Configuration();
}
public Configuration parseConfig(InputStream inputStream) throws Exception {
Document document = new SAXReader().read(inputStream);
Element configurationRootElement = document.getRootElement();
// 解析數(shù)據(jù)源配置dataSource下的參數(shù)信息
List<Element> elementList = configurationRootElement.selectNodes("http://property");
Properties properties = new Properties();
for (Element element : elementList){
String name = element.attributeValue("name");
String value = element.attributeValue("value");
properties.put(name,value);
}
// 使用c3p0數(shù)據(jù)源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(properties.getProperty("driverClass"));
dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
dataSource.setUser(properties.getProperty("userName"));
dataSource.setPassword(properties.getProperty("password"));
// 設置數(shù)據(jù)源
configuration.setDataSource(dataSource);
// 解析mapper.xml咳蔚,根據(jù)路徑讀取字節(jié)輸入流,使用dom4j進行解析
List<Element> mapperElementList = configurationRootElement.selectNodes("http://mapper");
for (Element element : mapperElementList) {
String mapperPath = element.attributeValue("resource");
InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
xmlMapperBuilder.parseMapper(resourceAsStream);
}
return configuration;
}
}
config包下XMLMapperBuilder類
package com.panshenlian.config;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.List;
/**
* @Author: panshenlian
* @Description: SQL配置信息解析類
* @Date: Create in 14:28 2020/11/12
*/
public class XMLMapperBuilder {
private Configuration configuration;
public XMLMapperBuilder(Configuration configuration) {
this.configuration = configuration;
}
public void parseMapper(InputStream inputStream) throws DocumentException {
Document mapperDocument = new SAXReader().read(inputStream);
Element rootElement = mapperDocument.getRootElement();
String namespace = rootElement.attributeValue("namespace");
// 解析每一個select節(jié)點
List<Element> selectNodes = mapperDocument.selectNodes("http://select");
for (Element element : selectNodes) {
String id = element.attributeValue("id");
String resultType = element.attributeValue("resultType");
String parameterType = element.attributeValue("parameterType");
String sql = element.getTextTrim();
// 解析封裝進入MapperdStatement對象
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id);
mappedStatement.setResultType(resultType);
mappedStatement.setParameterType(parameterType);
mappedStatement.setSql(sql);
String statementId = namespace + "." + id;
configuration.getMappedStatementMap().put(statementId,mappedStatement);
}
}
}
io包下Resource工具類
package com.panshenlian.io;
import java.io.InputStream;
/**
* @Author: panshenlian
* @Description: 資源類
* @Date: Create in 9:22 2020/11/12
*/
public class Resource {
/**
* 根據(jù)配置文件路徑搔驼,將配置文件加載成字節(jié)輸入流谈火,存儲在內存中
* @param path
* @return
*/
public static InputStream getResourceAsStream(String path){
InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(path);
return inputStream;
}
}
pojo包下Configuration
package com.panshenlian.pojo;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: panshenlian
* @Description: 數(shù)據(jù)庫配置類
* @Date: Create in 13:58 2020/11/12
*/
public class Configuration {
private DataSource dataSource;
/**
* key:statementId
* value:封裝好的mappedStatement對象
*/
private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Map<String, MappedStatement> getMappedStatementMap() {
return mappedStatementMap;
}
public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
this.mappedStatementMap = mappedStatementMap;
}
}
pojo包下MappedStatement
package com.panshenlian.pojo;
/**
* @Author: panshenlian
* @Description: SQL與結果集資源類 (負責存儲SQL映射定義、存儲結果集映射定義)
* @Date: Create in 14:17 2020/11/12
*/
public class MappedStatement {
/**
* id標識
*/
private String id;
/**
* 返回值類型
*/
private String resultType;
/**
* 參數(shù)值類型
*/
private String parameterType;
/**
* sql語句
*/
private String sql;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getResultType() {
return resultType;
}
public void setResultType(String resultType) {
this.resultType = resultType;
}
public String getParameterType() {
return parameterType;
}
public void setParameterType(String parameterType) {
this.parameterType = parameterType;
}
public String getSql() {
return sql;
}
public void setSql(String sql) {
this.sql = sql;
}
}
sqlSession包下DefaultSqlSession
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import java.lang.reflect.*;
import java.util.List;
/**
* @Author: panshenlian
* @Description: sql會話實現(xiàn)類
* @Date: Create in 14:43 2020/11/12
*/
public class DefaultSqlSession implements SqlSession{
private Configuration configuration;
public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
// 1舌涨、構建sql執(zhí)行器
SimpleExecutor simpleExecutor = new SimpleExecutor();
// 2糯耍、獲取最終執(zhí)行sql對象
MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
// 3、執(zhí)行sql囊嘉,返回結果集
List<Object> queryResultList = simpleExecutor.query(configuration, mappedStatement, params);
return (List<E>)queryResultList;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
List<Object> objects = selectList(statementId, params);
if (null != objects && objects.size() == 1){
return (T)objects.get(0);
} else {
throw new RuntimeException("查詢結果為空或者返回結果多于1條");
}
}
@Override
public int update(String statementId, Object... params) {
return 0;
}
@Override
public int delete(String statementId, Object... params) {
return 0;
}
@Override
public <T> T getMapper(Class<?> mapperClass) {
//使用JDK動態(tài)代理來為Dao接口生成代理對象温技,并返回調用結果
Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 底層都還是去執(zhí)行JDBC
// 根據(jù)不同情況,來調用selectList或selectOne
// 1.準備參數(shù)statementId = sql 語句的唯一標識: namespace.id =接口全限定名.方法名
String methodName = method.getName();
String className = method.getDeclaringClass().getName();
String statementId = className + "." + methodName;
// 2.準備參數(shù) params 即args
// 獲取被調用方法的返回值類型
Type genericReturnType = method.getGenericReturnType();
// 判斷是否進行了 泛型類型參數(shù)化
if ( genericReturnType instanceof ParameterizedType){
List<Object> objects = selectList(statementId, args);
return objects;
}
return selectOne(statementId,args);
}
});
return (T)proxyInstance;
}
}
sqlSession包下DefaultSqlSessionFactory
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
/**
* @Author: panshenlian
* @Description: 默認SqlSession工廠實現(xiàn)類
* @Date: Create in 14:41 2020/11/12
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory{
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return new DefaultSqlSession(configuration);
}
}
sqlSession包下Executor
package com.panshenlian.sqlSession;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;
/**
* @Author: panshenlian
* @Description: sql執(zhí)行器接口
* @Date: Create in 15:02 2020/11/12
*/
public interface Executor {
public <E> List<E> query(Configuration configuration,
MappedStatement mappedStatement,
Object... params) throws Exception;
}
sqlSession包下SimpleExecutor
package com.panshenlian.sqlSession;
import com.mysql.jdbc.StringUtils;
import com.panshenlian.config.BoundSql;
import com.panshenlian.pojo.Configuration;
import com.panshenlian.pojo.MappedStatement;
import com.panshenlian.utils.GenericTokenParser;
import com.panshenlian.utils.ParameterMapping;
import com.panshenlian.utils.ParameterMappingTokenHandler;
import java.beans.ExceptionListener;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: panshenlian
* @Description: sql執(zhí)行器接口簡單實現(xiàn)類
* @Date: Create in 15:55 2020/11/12
*/
public class SimpleExecutor implements Executor {
@Override
public <E> List<E> query(Configuration configuration,
MappedStatement mappedStatement,
Object... params) throws Exception {
// 1扭粱、注冊驅動 , 獲取數(shù)據(jù)庫連接
Connection connection = configuration.getDataSource().getConnection();
// 2舵鳞、獲取sql語句: select * from user where id = #{id}
// 轉換sql語句: select * from user where id = ?
// 轉換的過程,還需要對#{}里面的值進行解析存儲
String sql = mappedStatement.getSql();
BoundSql bounSql = getBoundSql(sql);
// 3琢蛤、獲取預處理對象: preparedStatement
PreparedStatement preparedStatement =
connection.prepareStatement(bounSql.getSqlText());
// 4蜓堕、設置參數(shù),通過反射機制獲取到參數(shù)
String parameterType = mappedStatement.getParameterType();
Class<?> parameterTypeClass = getClassType(parameterType);
List<ParameterMapping> parameterMappingList =
bounSql.getParameterMappingList();
for (int i = 0; i < parameterMappingList.size(); i++) {
ParameterMapping parameterMapping = parameterMappingList.get(i);
String filedName = parameterMapping.getContent();
// 反射
Field declaredField = parameterTypeClass.getDeclaredField(filedName);
// 暴力訪問
declaredField.setAccessible(true);
Object declaredFieldValue = declaredField.get(params[0]); // params[0] 是對象
preparedStatement.setObject(i+1,declaredFieldValue);
}
// 5博其、執(zhí)行SQL
ResultSet resultSet = preparedStatement.executeQuery();
String resultType = mappedStatement.getResultType();
Class<?> resultTypeClass = getClassType(resultType);
List<Object> objects = new ArrayList<Object>();
// 6套才、封裝返回結果集
while (resultSet.next()){
Object o = resultTypeClass.newInstance();
// 元數(shù)據(jù)
ResultSetMetaData metaData = resultSet.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
// 字段名
String columnName = metaData.getColumnName(i);
// 字段值
Object columnValue = resultSet.getObject(columnName);
// 使用內省(反射)慕淡,根據(jù)數(shù)據(jù)庫表和實體的對應關系背伴,完成封裝
PropertyDescriptor propertyDescriptor =
new PropertyDescriptor(columnName, resultTypeClass);
Method writeMethod = propertyDescriptor.getWriteMethod();
writeMethod.invoke(o,columnValue);
}
objects.add(o);
}
return (List<E>)objects;
}
/**
* 根據(jù)參數(shù)的全路徑反射獲取類
* @param parameterType
* @return
*/
private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
if (StringUtils.isNullOrEmpty(parameterType)) {
return null;
}
Class<?> clazz = Class.forName(parameterType);
return clazz;
}
/**
* 完成對#{}的解析工作:1、將#{}使用峰髓?進行代替傻寂,2、解析出#{}里面的值并存儲
* @param sql
* @return
*/
private BoundSql getBoundSql(String sql) {
// 標記處理類儿普,配置標記解析器來完成對占位符的解析處理工作
ParameterMappingTokenHandler parameterMappingTokenHandler
= new ParameterMappingTokenHandler();
GenericTokenParser genericTokenParser =
new GenericTokenParser("#{","}",
parameterMappingTokenHandler);
// 解析出來的sql
String parseSql = genericTokenParser.parse(sql);
// 解析出來的參數(shù)名稱
List<ParameterMapping> parameterMappings =
parameterMappingTokenHandler.getParameterMappings();
// 封裝成為通配sql返回結果
BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
return boundSql;
}
}
sqlSession包下SqlSession
package com.panshenlian.sqlSession;
import java.util.List;
/**
* @Author: panshenlian
* @Description: Sql會話接口
* @Date: Create in 14:40 2020/11/12
*/
public interface SqlSession {
/**
* 查詢所有
* @param statementId
* @param params
* @param <E>
* @return
*/
public <E> List<E> selectList(String statementId , Object ... params) throws Exception;
/**
* 根據(jù)條件查詢單個
* @param statementId
* @param params
* @param <T>
* @return
*/
public <T> T selectOne(String statementId , Object ... params) throws Exception;
/**
* 根據(jù)條件更新
* @param statementId
* @param params
* @return
*/
public int update(String statementId , Object ... params);
/**
* 根據(jù)條件刪除
* @param statementId
* @param params
* @return
*/
public int delete(String statementId , Object ... params);
/**
* 為Dao接口生成代理實現(xiàn)類
* @param mapperClass
* @param <T>
* @return
*/
public <T> T getMapper(Class<?> mapperClass);
}
sqlSession包下SqlSessionFactory
package com.panshenlian.sqlSession;
/**
* @Author: panshenlian
* @Description: SqlSession工廠接口
* @Date: Create in 13:51 2020/11/12
*/
public interface SqlSessionFactory {
public SqlSession openSession();
}
sqlSession包下SqlSessionFactoryBuilder
package com.panshenlian.sqlSession;
import com.panshenlian.config.XMLConfigBuilder;
import com.panshenlian.pojo.Configuration;
import java.io.InputStream;
/**
* @Author: panshenlian
* @Description: SqlSession會話工廠構建類
* @Date: Create in 13:48 2020/11/12
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws Exception {
// 第一步:用dom4j解析配置文件崎逃,將解析出來的內容封裝到Configuration中
XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfig(inputStream);
// 第二步:創(chuàng)建SqlSessionFactory對象,生產sqlSession會話對象(工廠模式)
DefaultSqlSessionFactory defaultSqlSessionFactory =
new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
}
}
utils包下GenericTokenParser
/**
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.panshenlian.utils;
/**
* 通用標記解析器眉孩,標記#{與}開始結束處理
* @author Clinton Begin
*/
public class GenericTokenParser {
private final String openToken; //開始標記
private final String closeToken; //結束標記
private final TokenHandler handler; //標記處理器
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
/**
* 解析${}和#{}
* @param text
* @return
* 該方法主要實現(xiàn)了配置文件个绍、腳本等片段中占位符的解析、處理工作浪汪,并返回最終需要的數(shù)據(jù)巴柿。
* 其中,解析工作由該方法完成死遭,處理工作是由處理器handler的handleToken()方法來實現(xiàn)
*/
public String parse(String text) {
// 驗證參數(shù)問題广恢,如果是null,就返回空字符串呀潭。
if (text == null || text.isEmpty()) {
return "";
}
// 下面繼續(xù)驗證是否包含開始標簽钉迷,如果不包含至非,默認不是占位符,直接原樣返回即可糠聪,否則繼續(xù)執(zhí)行荒椭。
int start = text.indexOf(openToken, 0);
if (start == -1) {
return text;
}
// 把text轉成字符數(shù)組src,并且定義默認偏移量offset=0舰蟆、存儲最終需要返回字符串的變量builder趣惠,
// text變量中占位符對應的變量名expression。判斷start是否大于-1(即text中是否存在openToken)身害,如果存在就執(zhí)行下面代碼
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
// 判斷如果開始標記前如果有轉義字符味悄,就不作為openToken進行處理,否則繼續(xù)處理
if (start > 0 && src[start - 1] == '\\') {
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
//重置expression變量塌鸯,避免空指針或者老數(shù)據(jù)干擾侍瑟。
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {////存在結束標記時
if (end > offset && src[end - 1] == '\\') {//如果結束標記前面有轉義字符時
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {//不存在轉義字符,即需要作為參數(shù)進行處理
expression.append(src, offset, end - offset);
offset = end + closeToken.length();
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//首先根據(jù)參數(shù)的key(即expression)進行參數(shù)處理界赔,返回?作為占位符
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
}
utils包下ParameterMapping
package com.panshenlian.utils;
/**
* @Author: panshenlian
* @Description: 參數(shù)映射類(SQL參數(shù)映射類丢习,存儲#{}、${}中的參數(shù)名)
* @Date: Create in 16:14 2020/11/12
*/
public class ParameterMapping {
private String content;
public ParameterMapping(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
utils包下ParameterMappingTokenHandler
package com.panshenlian.utils;
import java.util.ArrayList;
import java.util.List;
/**
* 標記處理器實現(xiàn)類淮悼,解析#{}咐低、${}成為?
*/
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
// context是參數(shù)名稱 #{id} #{username}
public String handleToken(String content) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
ParameterMapping parameterMapping = new ParameterMapping(content);
return parameterMapping;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public void setParameterMappings(List<ParameterMapping> parameterMappings) {
this.parameterMappings = parameterMappings;
}
}
utils包下TokenHandler
/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.panshenlian.utils;
/**
* 標記處理器接口
* @author Clinton Begin
*/
public interface TokenHandler {
String handleToken(String content);
}
框架書寫好了,我們寫一個測試工程驗證一下框架袜腥,我們在現(xiàn)有框架下新加一個測試項目(以module模塊的方式創(chuàng)建)保證測試工程和框架項目在一個工作組下面:
由于我已經寫好了測試工程见擦,我直接引入即可,效果都一樣羹令,創(chuàng)建和引入都以module方式就可以:
測試工程基本流程也說明一下:
1鲤屡、引入依賴pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.panshenlian</groupId>
<artifactId>MyPersistenceTest</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- 引入自定義持久層框架的依賴 -->
<dependencies>
<dependency>
<groupId>com.panshenlian</groupId>
<artifactId>MyPersistence</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
2、配置數(shù)據(jù)源sqlMapConfig.xml
<configuration>
<!-- 數(shù)據(jù)庫配置信息 -->
<dataSource>
<property name="driverClass"
value="com.mysql.jdbc.Driver" ></property>
<property name="jdbcUrl"
value="jdbc:mysql:///mybatis" ></property>
<property name="userName"
value="root" ></property>
<property name="password"
value="123456" ></property>
</dataSource>
<!-- 應用到的mapper.xml全路徑 -->
<mapper resource="userMapper.xml"></mapper>
<mapper resource="orderMapper.xml"></mapper>
</configuration>
3福侈、我們以用戶表為例子酒来,建立用戶sql配置userMapper.xml
<mapper namespace="com.panshenlian.dao.IUserDao">
<!-- sql的唯一標識:namespace.id 來組成:statementId -->
<select id="findAll"
resultType="com.panshenlian.pojo.User">
select * from user
</select>
<!--
User user = new User();
user.setId(1);
user.setUsername("panshenlian");
-->
<select id="findByCondition"
resultType="com.panshenlian.pojo.User"
parameterType="com.panshenlian.pojo.User">
select * from user
where id= #{id} and username = #{username}
and password= #{password} and birthday = #{birthday}
</select>
</mapper>
4、用戶dao接口
package com.panshenlian.dao;
import com.panshenlian.pojo.User;
import java.util.List;
/**
* @Author: panshenlian
* @Description:
* @Date: Create in 21:35 2020/11/12
*/
public interface IUserDao {
/**
* 查詢所有用戶
* @return
* @throws Exception
*/
public List<User> findAll() throws Exception;
/**
* 根據(jù)條件進行用戶查詢
* @return
* @throws Exception
*/
public User findByCondition(User user) throws Exception;
}
5肪凛、用戶dao的實體類
package com.panshenlian.pojo;
/**
* @Author: panshenlian
* @Description: 用戶實體
* @Date: Create in 9:20 2020/11/12
*/
public class User {
private Integer id;
private String username;
private String password;
private String birthday;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
}
注意:用戶sql配置文件userMapper.xml中的namespace需要和用戶dao的全限定名一致堰汉,這是我們框架默認規(guī)則:namespace="com.panshenlian.dao.IUserDao" 同時select標簽的id和用戶dao接口的方法名保持一致,也是框架默認的規(guī)則伟墙,例如id="findAll"
6翘鸭、最終我們創(chuàng)建測試類:MyPersistenceTest
package com.panshenlian.test;
import com.panshenlian.dao.IUserDao;
import com.panshenlian.io.Resource;
import com.panshenlian.pojo.User;
import com.panshenlian.sqlSession.SqlSession;
import com.panshenlian.sqlSession.SqlSessionFactory;
import com.panshenlian.sqlSession.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.InputStream;
import java.util.List;
/**
* @Author: panshenlian
* @Description: 持久層框架測試類
* @Date: Create in 9:24 2020/11/12
*/
public class MyPersistenceTest {
@Test
public void test() throws Exception {
InputStream resourceAsStream =
Resource.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 一、傳統(tǒng)DAO方式調用
User user = new User();
user.setId(3);
user.setUsername("panshenlian");
user.setBirthday("2020-11-12");
user.setPassword("123456");
User dbUser = sqlSession.selectOne("com.panshenlian.dao.IUserDao.findByCondition",user);
System.out.println(dbUser);
List<User> userList = sqlSession.selectList("com.panshenlian.dao.IUserDao.findAll", user);
for (User db : userList) {
System.out.println(db);
}
// 二戳葵、代理模式調用
IUserDao userDao = sqlSession.getMapper(IUserDao.class);
List<User> users = userDao.findAll();
for (User db : users) {
System.out.println("代理調用=" + db);
}
}
}
7就乓、運行測試類,結果符合預期
框架和測試驗證我們基本完成,其實以上主要是對于持久層框架的一個簡單框架介紹生蚁,方面我們以后學習分析Mybatis框架噩翠,基本我們做到了一個模擬雛形,流程大致是這樣守伸。
同時框架和測試工程的源碼都已上傳绎秒,傳送門:點擊看看
編碼實現(xiàn)過程中涉及到幾個有意思的知識點浦妄,我們后續(xù)找時間聊聊尼摹,包括:
- 內省機制
- 反射機制
- JDK動態(tài)代理
- 設計模式
- 泛型
總結
如今大型項目一般都不會直接使用JDBC,要么采用市面上成熟的持久層方案剂娄,要么自研持久層框架蠢涝,說到底,還是單純的JDBC無法保證高效高穩(wěn)定性能的數(shù)據(jù)層訪問與應用阅懦,而越來越多持久層框架方案和二,不僅消除了大量的JDBC冗余代碼,還提供極低的學習曲線耳胎,既能保證協(xié)同傳統(tǒng)的數(shù)據(jù)庫還接受SQL語句惯吕,也為其他框架提供了拓展集成支持,包括連接池怕午、緩存废登、性能等都做了極大的優(yōu)化與提升,所以框架大行其道是必然趨勢郁惜。
JDBC在90年代誕生之初也是高光而偉大堡距,只不過隨著技術水平的躍遷和業(yè)務場景的迭代更新,舊技術滿足不了現(xiàn)有的訴求兆蕉,所有事物都會輪換更新羽戒,我們僅僅是站在偉人的肩膀上,順勢變遷虎韵。
好易稠,本篇完,晚安包蓝。
下一篇驶社,我們或許會聊聊 Mybatis基礎和架構 。
BIU ~ 文章持續(xù)更新养晋,微信搜索「潘潘和他的朋友們」第一時間閱讀衬吆,隨時有驚喜。本文會在 GitHub https://github.com/JavaWorld 收錄绳泉,熱騰騰的技術逊抡、框架、面經、解決方案冒嫡,我們都會以最美的姿勢第一時間送達拇勃,歡迎 Star。