Mybatis系列全解(一):手寫一套持久層框架

封面 : 洛小汐
作者 : 潘潘

未來半年,有幸與導師們一起學習交流救欧,趁這個機會衰粹,把所學所感記錄下來。

封面人物-Java之父:詹姆斯·高斯林

自畢業(yè)以后笆怠,自己先創(chuàng)業(yè)后上班铝耻,浮沉了近8年,內心著實焦躁,雖一直是走科班路線瓢捉,但在技術道路上卻始終沒靜下心來研究频丘、思考、梳理泡态,機會來了搂漠,便抓牢。

希望自己記錄下來的知識內容某弦,對后來的學習之人桐汤,能有些許幫助。

對文章內容有任何建議或意見靶壮,
或對互聯(lián)網開發(fā)有希望交流學習叠萍,
或單純熱愛生活蛮位,
都歡迎隨時微信我:panshenlian

趁年輕薪伏,終生學習吧

第一個系列的文章主要圍繞「架構師(Java)技術條線」展開聊麻蹋,不定時更新刻炒。

第一篇我以《手寫一套持久層框架》先來打個樣蝌矛,本篇文章我們先不介紹MyBatis碎罚,也不會分析源碼欣硼,我們先聊一個 Java API:JDBC混卵。

JDBC是Java的老朋友映穗,我們再一次認識他吧,挑挑他的毛病幕随,站在Java資老朋友的角度蚁滋,給他提點優(yōu)化意見,并送他一套《自定義持久層框架》赘淮。

溫馨提示:

如果大家在閱讀過程中辕录,對某些解決思路存在疑問,我建議大家先帶著疑問閱讀完梢卸,消化理解走诞,因為導師們確實是通過研究Mybatis等持久層框架源碼之后,反過來剖析的蛤高。

簡單來說 “ 大廠都這么寫蚣旱,我們且這么跟隨吧 ”。

Mybaits系列全解 (持續(xù)更新)


一恤批、JDBC是誰异吻?


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架構圖

有了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é):
JDBC使用流程
使用流程 (詳細說明)

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的類進行掃描并進行初始化镶骗。

  1. 創(chuàng)建數(shù)據(jù)庫連接:

DriverManager通過遍歷所有已注冊的驅動來嘗試獲取連接,第一個匹配上就會直接返回躲雅,并使用對應驅動建立起客戶端與數(shù)據(jù)庫服務器的網絡連接(物理連接Socket了解一下)鼎姊。

  1. 創(chuàng)建編譯對象:

數(shù)據(jù)庫連接connection成功之后,我們會向數(shù)據(jù)庫發(fā)送一次請求(statement),執(zhí)行一條sql語句相寇,一個連接可以執(zhí)行多次statement慰于,除非你關閉連接,其中還有一個概念就是事務transaction唤衫,事務和請求可以是一對一婆赠,也可以是一對多,這取決于你是想把多個請求statement作為同一個事務提交佳励,還是一個請求提交一次事務休里,JDBC默認是事務是自動提交,即auto-commit是打開的赃承,所以默認是一對一份帐。

  1. 設置入?yún)?zhí)行SQL:

為了防止SQL注入,我們使用預處理在sql中使用?作為輸入?yún)?shù)的占位符楣导,sql在編譯后成為安全的sql語句再進行查詢(有緣我們可以聊聊為何預處理機制能防止SQL注入)废境。

  1. 封裝返回結果集:

SQL執(zhí)行之后會把結果集封裝到ResultSet類,ResultSet類本身的迭代器初始行數(shù)的位置是1筒繁,所以我們會發(fā)現(xiàn)與java.util.Iterator接口的迭代初始行數(shù)為0有差異噩凹,同時ResultSet類本身沒有提供hasNext方法,所以我們會不斷的while(rs.next())往后定位毡咏,再通過不同的類型的訪問器讀取數(shù)據(jù)(例如getString,getInteger等)驮宴。

  1. 釋放數(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的結構:
JDBC API 在JDK的結構

默認已具備java開發(fā)環(huán)境纹安、mysql數(shù)據(jù)庫

  1. 創(chuàng)建mave工程,并且引入mysql驅動依賴

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.25</version>
    </dependency>
</dependencies>

  1. 創(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();
              }
           }
        }
    }
}

  1. 創(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 + '\'' +
                '}';
    }
}



  1. 創(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');

  1. 執(zhí)行結果,nice , 成功阳距。

User{id=2, userName='panshenlian'}

看完這段演示塔粒,大家是否發(fā)現(xiàn)一個問題?就是整個JDBC操作數(shù)據(jù)庫的使用過程繁瑣而尷尬筐摘,就如這場對話:

用戶使用原始JDBC日常

額(⊙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ā)人員)好乐,是有多舒爽呢?
JDBC VS 持久層框架

是不是發(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步:

  1. 加載配置文件:根據(jù)配置文件的路徑柬讨,加載配置文件成字節(jié)輸入流,存儲在內存中袍啡。

創(chuàng)建Resource類踩官,提供加載流方法:InputStream getResourceAsStream(String path)

  1. 創(chuàng)建兩個javaBean(容器對象):存放配置文件解析出來的內容
 
 Configuration(核心配置類):存放sqlMapConfig.xml解析出來的內容。
 MappedStatement(映射配置類):存放mapper.xml解析出來的內容境输。
 
  1. 解析配置文件(使用dom4j) 蔗牡,并創(chuàng)建SqlSession會話對象
 
 創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in)
 > 使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中
 > 創(chuàng)建SqlSessionFactory對象嗅剖,生產sqlSession會話對象(工廠模式)
 
  1. 創(chuàng)建SqlSessionFactory接口以及實現(xiàn)類DefaultSqlSessionFactory

  創(chuàng)建openSession()接口方法辩越,生產sqlSession
 
  1. 創(chuàng)建SqlSession接口以及實現(xiàn)類DefaultSqlSession

  定義對數(shù)據(jù)庫的CRUD操作:
  > selectList();
  > selectOne();
  > update();
  > delete();
 
  1. 創(chuàng)建Executor接口以及實現(xiàn)類SimpleExecutor

  創(chuàng)建query(Configuration conf,MappedStatement ms,Object... params)
  實際執(zhí)行的就是JDBC代碼。
 

基本過程我們已經清晰信粮,我們再細化一下類圖黔攒,更好的助于我們實際編碼:

簡約版

持久層框架UML簡約版

詳細版

持久層框架UML詳細版
最終手寫的持久層框架結構參考:
自定義持久層框架項目結構圖
包接口類說明
  • 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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末孝凌,一起剝皮案震驚了整個濱河市方咆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蟀架,老刑警劉巖瓣赂,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異片拍,居然都是意外死亡煌集,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門捌省,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苫纤,“玉大人,你說我怎么就攤上這事纲缓【砭校” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵祝高,是天一觀的道長栗弟。 經常有香客問我,道長褂策,這世上最難降的妖魔是什么横腿? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮斤寂,結果婚禮上耿焊,老公的妹妹穿的比我還像新娘。我一直安慰自己遍搞,他們只是感情好罗侯,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溪猿,像睡著了一般钩杰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诊县,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天讲弄,我揣著相機與錄音,去河邊找鬼依痊。 笑死避除,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播瓶摆,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼凉逛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了群井?” 一聲冷哼從身側響起状飞,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎书斜,沒想到半個月后诬辈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡菩佑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年自晰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片稍坯。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖搓劫,靈堂內的尸體忽然破棺而出瞧哟,到底是詐尸還是另有隱情,我是刑警寧澤枪向,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布勤揩,位于F島的核電站,受9級特大地震影響秘蛔,放射性物質發(fā)生泄漏陨亡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一深员、第九天 我趴在偏房一處隱蔽的房頂上張望负蠕。 院中可真熱鬧,春花似錦倦畅、人聲如沸遮糖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽欲账。三九已至,卻和暖如春芭概,著一層夾襖步出監(jiān)牢的瞬間赛不,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工罢洲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留踢故,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像畴椰,于是被迫代替她去往敵國和親臊诊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內容