四拂蝎,手寫(xiě)自己的MyBatis框架

1.MyBatis應(yīng)用分析與實(shí)踐
2.MyBatis體系結(jié)構(gòu)與工作原理
3.MyBatis插件原理及Spring集成
4.手寫(xiě)自己的MyBatis框架
本節(jié)目標(biāo):

1德玫、 實(shí)現(xiàn) 1.0 版本沮明,掌握 MyBatis 的本質(zhì)矾麻、核心功能、核心對(duì)象波岛、執(zhí)行流程

2茅坛、 通過(guò)分析 2.0 版本,體驗(yàn)框架的演進(jìn)過(guò)程,理解 MyBatis

一贡蓖,需求分析

假如你在一家軟件公司的研發(fā)部工作曹鸠,有一天技術(shù)總監(jiān)老王想讓你負(fù)責(zé)開(kāi)發(fā)一個(gè)項(xiàng) 目,你要做的第一件事情是什么斥铺?

確定需求彻桃。

那我們要開(kāi)發(fā)這個(gè)項(xiàng)目,需求從哪里來(lái)晾蜘? 我們要跟老王溝通下邻眷。

1、項(xiàng)目目標(biāo):為什么要做這個(gè)項(xiàng)目剔交?做成什么樣肆饶?

老王說(shuō):我發(fā)現(xiàn)在業(yè)務(wù)復(fù)雜的項(xiàng)目中,開(kāi)發(fā)的兄弟們用 JDBC 操作數(shù)據(jù)庫(kù)太麻煩了岖常, 想要把一些基礎(chǔ)的操作做一個(gè)封裝和提取驯镊,讓開(kāi)發(fā)的兄弟們更加專注于業(yè)務(wù)的開(kāi)發(fā),這樣就可以提升開(kāi)發(fā)效率竭鞍,遠(yuǎn)離 996板惑。

原來(lái)是一個(gè)操作數(shù)據(jù)庫(kù)的框架。

那么我要問(wèn)一下老王:這個(gè)項(xiàng)目要做什么偎快,才簡(jiǎn)化我們對(duì)數(shù)據(jù)庫(kù)的操作呢冯乘?或者說(shuō), 在業(yè)務(wù)復(fù)雜的項(xiàng)目中使用 JDBC

2滨砍、核心功能:這個(gè)框架需要解決什么問(wèn)題往湿?

老王給我看了一段 JDBC 的代碼:

  1. 它需要實(shí)現(xiàn)對(duì)連接資源的自動(dòng)管理妖异,也就是把創(chuàng)建 Connection惋戏、Statement、 關(guān)閉 Connection他膳、Statement响逢、ResultSet 這些操作封裝到底層的對(duì)象中,不需要在應(yīng)用層手動(dòng)調(diào)用棕孙。

    rs.close();
    stmt.close();
    conn.close();
    
  2. 它需要把 SQL 語(yǔ)句抽離出來(lái)實(shí)現(xiàn)集中管理舔亭,開(kāi)發(fā)人員不用在業(yè)務(wù)代碼里面寫(xiě) SQL 語(yǔ)句。

    String sql = "SELECT bid, name, author_id FROM blog where bid = 1";
    ResultSet rs = stmt.executeQuery(sql);
    
  3. 它需要實(shí)現(xiàn)對(duì)結(jié)果集的轉(zhuǎn)換蟀俊,也就是我們指定了映射規(guī)則之后钦铺,這個(gè)框架會(huì)自動(dòng) 幫我們把 ResultSet 映射成實(shí)體類對(duì)象。

    Integer bid = rs.getInt("bid");
    String name = rs.getString("name");
    Integer authorId = rs.getInt("author_id");
    blog.setAuthorId(authorId);
    blog.setBid(bid);
    blog.setName(name);
    
  4. 做了這些事以后肢预,這個(gè)框架需要提供一個(gè) API 來(lái)給我們操作數(shù)據(jù)庫(kù)矛洞,這里面封裝 了對(duì)數(shù)據(jù)庫(kù)的操作的常用的方法。

3烫映、功能分解:這個(gè)框架要怎么解決這些問(wèn)題沼本?

老王的需求我已經(jīng)了解了噩峦,這個(gè)框架應(yīng)該怎么解決這些問(wèn)題呢? 我們先來(lái)分析一下需要哪些核心對(duì)象:

  1. 核心對(duì)象

    1. 存放參數(shù)和結(jié)果映射關(guān)系抽兆、存放 SQL 語(yǔ)句识补,我們需要定義一個(gè)配置類;
    2. 執(zhí)行對(duì)數(shù)據(jù)庫(kù)的操作辫红,處理參數(shù)和結(jié)果集的映射凭涂,創(chuàng)建和釋放資源,我們需要定 義一個(gè)執(zhí)行器贴妻;
    3. 有了這個(gè)執(zhí)行器以后导盅,我們不能直接調(diào)用它,而是定義一個(gè)給應(yīng)用層使用的 API揍瑟, 它可以根據(jù) SQL 的 id 找到 SQL 語(yǔ)句白翻,交給執(zhí)行器執(zhí)行;
    4. 直接使用 id 查找 SQL 語(yǔ)句太麻煩了绢片,我們干脆把存放 SQL 的命名空間定義成一 個(gè)接口滤馍,把 SQL 的 id 定義成方法,這樣只要調(diào)用接口方法就可以找到要執(zhí)行的 SQL底循。這 個(gè)時(shí)候我們需要引入一個(gè)代理類巢株。

    核心對(duì)象有了,接下來(lái)我們分析一下這個(gè)框架操作數(shù)據(jù)庫(kù)的主要流程熙涤,先從單條查詢?nèi)胧帧?/p>

  2. 操作流程(繪圖)

MeBatis.png
  1. 定義接口 Mapper 和方法阁苞,用來(lái)調(diào)用數(shù)據(jù)庫(kù)操作。 Mapper 接口操作數(shù)據(jù)庫(kù)需要通過(guò)代理類祠挫。
  2. 定義配置類對(duì)象 Configuration那槽。
  3. 定義應(yīng)用層的 API SqlSession。它有一個(gè) getMapper()方法等舔,我們會(huì)從配置類 Configuration 里面使用 Proxy.newProxyInatance()拿到一個(gè)代理對(duì)象 MapperProxy骚灸。
  4. 有了代理對(duì)象 MapperProxy 之后,我們調(diào)用接口的任意方法慌植,就是調(diào)用代理對(duì) 象的 invoke()方法甚牲。
  5. 代理對(duì)象 MapperProxy 的 invoke()方法調(diào)用了 SqlSession 的 selectOne()。
  6. SqlSession 只是一個(gè) API蝶柿,還不是真正的 SQL 執(zhí)行者丈钙,所以接下來(lái)會(huì)調(diào)用執(zhí)行器 Executor 的 query()方法。
  7. 執(zhí)行器 Executor 的 query()方法里面就是對(duì) JDBC 底層的 Statement 的封裝交汤, 最終實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的操作雏赦,和結(jié)果的返回。

基于我們總結(jié)的這個(gè)框架的主要工作流程,接下來(lái)我們就要?jiǎng)邮秩?xiě)這個(gè)框架了喉誊。 我們先給它起個(gè)名字叫 MyBatis-Custom

二邀摆,V1.0 的實(shí)現(xiàn)

創(chuàng)建一個(gè)全新的 maven 工程,命名為 mebatis伍茄,引入 mysql:

<?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.javacoo</groupId>
    <artifactId>MyBatis-Custom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.test.skip>true</maven.test.skip>
        <maven.test.failure.ignore>true</maven.test.failure.ignore>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <defaultGoal>package</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-javadoc-plugin</artifactId>
                <version>2.9.1</version>
            </plugin>
        </plugins>
    </build>
</project>

1栋盹、SqlSession

我們已經(jīng)分析了 MyBatis-Custom的主要對(duì)象和操作流程,應(yīng)該從哪里入手敷矫?

當(dāng)我們?cè)?psvm 操作的時(shí)候例获,第一個(gè)需要的對(duì)象是 SqlSession。所以我們從應(yīng)用層的接口 SqlSession 入手曹仗。

那么我們先來(lái)創(chuàng)建一個(gè) package榨汤,它是我們手寫(xiě)的 MyBatis-Custom,我們建一個(gè)包怎茫。 首先我們創(chuàng)建一個(gè)自己的 SqlSession收壕,叫 JCSqlSession。

根據(jù)我們剛才總結(jié)的流程圖轨蛤,SqlSession 需要有一個(gè)獲取代理對(duì)象的方法蜜宪,那么這 個(gè)代理對(duì)象是從哪里獲取到的呢?是從我們的配置類里面獲取到的祥山,因?yàn)榕渲妙惱锩嬗薪涌诤退a(chǎn)生的代理類的對(duì)應(yīng)關(guān)系圃验。

所以,我們要先持有一個(gè) Configuration 對(duì)象缝呕,叫 JCConfiguration澳窑,我們也創(chuàng)建這個(gè)類。除了獲取代理對(duì)象之外供常,Configuration 里面還存儲(chǔ)了我們的接口方法(也就是 statementId)和 SQL 語(yǔ)句的綁定關(guān)系摊聋。

第二個(gè),我們?cè)?SqlSession 中定義的操作數(shù)據(jù)庫(kù)的方法话侧,最后都會(huì)調(diào)用 Executor 去操作數(shù)據(jù)庫(kù)栗精,所以我們還要持有一個(gè) Executor 對(duì)象,叫 JCExecutor瞻鹏,我們也創(chuàng)建它。

public class JCSqlSession {
    private JCConfiguration configuration;
    private JCExecutor executor;
    ...
}

除了這兩個(gè)屬性之外鹿寨,我們還要定義 SqlSession 的行為新博,也就是它的主要的方法。

第一個(gè)方法是查詢方法脚草,selectOne()赫悄,由于它可以返回任意類型,我們把返回值定 義成 T 泛型。selectOne()有兩個(gè)參數(shù)埂淮,一個(gè)是 String 類型的 statementId姑隅,我們會(huì) 根據(jù)它找到 SQL 語(yǔ)句。一個(gè)是 Object 類型的 parameter 參數(shù)(可以是 Integer 也可以 是 String 等等倔撞,任意類型)讲仰,用來(lái)填充 SQL 里面的占位符。

它會(huì)調(diào)用 Executor 的 query()方法痪蝇,所以我們創(chuàng)建 Executor 類鄙陡,傳入這兩個(gè)參數(shù), 一樣返回一個(gè)泛型躏啰。Executor 里面要傳入 SQL趁矾,但是我們還沒(méi)拿到,先用 statementId 代替给僵。

public class JCSqlSession {
    ...
    public <T> T selectOne(String statementId, Object paramater){
            // 根據(jù)statementId拿到SQL
            String sql = JCConfiguration.sqlMappings.getString(statementId);
            if(null != sql && !"".equals(sql)){
                return executor.query(sql, paramater );
            }
            return null;
    }
}

JCExecutor.java

public class JCExecutor {
    public <T> T query(String sql, Object paramater) {
        return null;
    }
}

第二個(gè)方法是獲取代理對(duì)象的方法毫捣,我們通過(guò)這種方式去避免了 statementId 的硬 編碼。

我們?cè)?SqlSession 中創(chuàng)建一個(gè) getMapper()的方法帝际,由于可以返回任意類型的代理類泌神,所以我們把返回值也定義成泛型 T晕鹊。我們是根據(jù)接口類型獲取到代理對(duì)象的,所以傳入?yún)?shù)要用類型 Class。

public class JCSqlSession {
    ...
    public <T> T getMapper(Class clazz){
        return null;
    }
}

2龄恋、Configuration

代理對(duì)象我們不是在 SqlSession 里面獲取到的,要進(jìn)一步調(diào)用 Configuration 的 getMapper()方法委煤。返回值需要強(qiáng)轉(zhuǎn)成(T)康铭。

public class JCSqlSession {
    ...
    public <T> T getMapper(Class clazz){
        return (T)configuration.getMapper(clazz);
    }
}

我們先在 Configuration 創(chuàng)建這個(gè)方法,返回類型一樣是泛型 T披粟,先返回空咒锻。

public class JCConfiguration {
    ...
    public <T> T getMapper(Class clazz) {
        return null;
    }  
 }

3、MapperProxy

我們要在 Configuration 中通過(guò) getMapper()方法拿到這個(gè)代理對(duì)象守屉,必須要有一 個(gè)實(shí)現(xiàn)了 InvocationHandler 的代理類惑艇。我們來(lái)創(chuàng)建它:JCMapperProxy。 提供一個(gè) invoke()方法拇泛。

public class JCMapperProxy implements InvocationHandler {
    ...
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return null;
    }
}

invoke()的實(shí)現(xiàn)我們先留著滨巴,先返回 null。MapperProxy 已經(jīng)有了俺叭,我們回到 Configuration.getMapper()完成獲取代理對(duì)象的邏輯恭取。

返回代理對(duì)象,直接使用 JDK 的動(dòng)態(tài)代理:第一個(gè)參數(shù)是類加載器熄守,第二個(gè)參數(shù)是 被代理類蜈垮,第三個(gè)參數(shù)是代理類耗跛。 把返回結(jié)果強(qiáng)轉(zhuǎn)為(T):

public class JCConfiguration {
    ...
    public <T> T getMapper(Class clazz, JCSqlSession sqlSession) {
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{clazz},
                new JCMapperProxy(sqlSession));
    }
}

獲取代理類的邏輯已經(jīng)實(shí)現(xiàn)完了,我們可以在 SqlSession 中通過(guò) getMapper()拿到代理對(duì)象了攒发,也就是可以調(diào)用 invoke()方法了调塌。

接下來(lái)去完成 MapperProxy 的 invoke() 方法。 在 MapperProxy 的 invoke()方法里面又調(diào)用了 SqlSession 的 selectOne()方法惠猿。 一個(gè)問(wèn)題出現(xiàn)了:在 MapperProxy 里面根本沒(méi)有 SqlSession 對(duì)象羔砾?

這兩個(gè)對(duì)象的關(guān)系怎么建立起來(lái)?MapperProxy 怎么拿到一個(gè) SqlSession 對(duì)象紊扬? 很簡(jiǎn)單蜒茄,我們可通過(guò)構(gòu)造函數(shù)傳入它。

先定義一個(gè)屬性餐屎,然后在 MapperProxy 的構(gòu)造函數(shù)里面賦值:

public class JCMapperProxy implements InvocationHandler {
    private JCSqlSession sqlSession;

    public JCMapperProxy(JCSqlSession sqlSession){
        this.sqlSession = sqlSession;
    }
    ...
}

因?yàn)樾薷牧舜眍惖臉?gòu)造函數(shù)檀葛,這個(gè)時(shí)候 Configuration 創(chuàng)建代理類的方法 getMapper()也要修改。

問(wèn)題:Configuration 的 getMapper()方法參數(shù)中也沒(méi)有 SqlSession腹缩,沒(méi)辦法傳給 MapperProxy 的構(gòu)造函數(shù)屿聋。怎么拿到 SqlSession 呢?是直接 new 一個(gè)嗎藏鹊?

不需要润讥,可以在 SqlSession 調(diào)用它的時(shí)候直接把自己傳進(jìn)來(lái):

public class JCConfiguration {
   ...
    public <T> T getMapper(Class clazz, JCSqlSession sqlSession) {
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{clazz},
                new JCMapperProxy(sqlSession));
    }
}

那么 SqlSession 的 getMapper()方法也要修改:

public class JCSqlSession {
    ...
    public <T> T getMapper(Class clazz){
        return configuration.getMapper(clazz, this);
    }
}

現(xiàn)在在 MapperProxy 里面已經(jīng)就可以拿到 SqlSession 對(duì)象了,在 invoke()方法里面我們會(huì)調(diào)用 SqlSession 的 selectOne()方法盘寡。

我們繼續(xù)來(lái)完成 invoke()方法楚殿。 selectOne()方法有兩個(gè)參數(shù), statementId 和 paramater竿痰,這兩個(gè)我們?cè)趺茨玫?呢脆粥?

statementId 其實(shí)就是接口的全路徑+方法名,中間加一個(gè)英文的點(diǎn)影涉。

paramater 可以從方法參數(shù)中拿到变隔,這里我們只傳了一個(gè)參數(shù),用 args[0]蟹倾。

它要把 statementId 和參數(shù)傳給 SqlSession:

public class JCMapperProxy implements InvocationHandler {
    private JCSqlSession sqlSession;

    public JCMapperProxy(JCSqlSession sqlSession){
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String mapperInterface = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String statementId = mapperInterface + "." + methodName;
        return sqlSession.selectOne(statementId, args[0]);
    }
}

4匣缘、Executor

到了 sqlSession 的 selectOne()方法,這里我們要去調(diào)用 Executor 的 query()方法鲜棠, 這個(gè)時(shí)候我們必須傳入 SQL 語(yǔ)句和參數(shù)(根據(jù) statementId 獲燃〕)。

問(wèn)題來(lái)了:我們?cè)趺锤鶕?jù) StatementId 找到我們要執(zhí)行的 SQL 語(yǔ)句呢岔留?他們之間的 綁定關(guān)系我們配置在哪里夏哭?

為了簡(jiǎn)便,免去讀取文件流和解析 XML 標(biāo)簽的麻煩献联,我們把我們的 SQL 語(yǔ)句放在 Properties 文件里面竖配。

我們?cè)?resources 目錄下創(chuàng)建一個(gè) v1sql.properties 文件。key 就是接口全路徑+ 方法名稱里逆,SQL 是我們的查詢 SQL进胯。

參數(shù)這里,因?yàn)槲覀円獋魅胍粋€(gè)整數(shù)原押,所以先用一個(gè)%d 的占位符代

com.javacoo.mybatis.v1.mapper.BlogMapper.selectBlogById=select * from blog where bid = %d

這個(gè)綁定關(guān)系是放在配置類 Configuration 里面的胁镐。

為了避免重復(fù)解析,我們?cè)?Configuration 創(chuàng)建一個(gè)靜態(tài)屬性和靜態(tài)方法诸衔,直接解 析 v1sql.properties 文件里面的所有 KV 鍵值對(duì):

public class JCConfiguration {
    public static final ResourceBundle sqlMappings;

    static{
        sqlMappings = ResourceBundle.getBundle("v1sql");
    }

    public <T> T getMapper(Class clazz, JCSqlSession sqlSession) {
        return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{clazz},
                new JCMapperProxy(sqlSession));
    }
}

這樣就可以通過(guò) Configuration 拿到 SQL 了盯漂。

如果 SQL 語(yǔ)句拿不到,說(shuō)明不存在映射關(guān)系(或者不是接口中定義的操作數(shù)據(jù)的方 法笨农,比如 toString())就缆,我們返回空。

public class JCSqlSession {
    ...
    public <T> T selectOne(String statementId, Object paramater){
        // 根據(jù)statementId拿到SQL
        String sql = JCConfiguration.sqlMappings.getString(statementId);
        if(null != sql && !"".equals(sql)){
            return executor.query(sql, paramater );
        }
        return null;
    }
}

SQL 語(yǔ)句已經(jīng)拿到了谒亦,接下來(lái)就是 Executor 類的 query()方法竭宰,Executor 是數(shù)據(jù)庫(kù) 操作的真正執(zhí)行者。它里面應(yīng)該做什么事情份招?

我們干脆直接把 JDBC 的代碼全部復(fù)制過(guò)來(lái)切揭,職責(zé)先不用細(xì)分。

參數(shù)用傳入的參數(shù)替換%d 占位符锁摔,需要 format 一下廓旬。

ResultSet rs = stmt.executeQuery(String.format(sql, paramater));

最后我們把結(jié)果強(qiáng)轉(zhuǎn)一下。

return (T)blog

寫(xiě)一個(gè)測(cè)試類:

public class MyBatisTest {
    public static void main(String[] args) {
        JCSqlSession sqlSession = new JCSqlSession(new JCConfiguration(), new JCExecutor());
        BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
        blogMapper.selectBlogById(1);
    }
}

測(cè)試通過(guò)谐腰,1.0 的版本完成了:

Blog{bid=1, name='MyBatis 源碼分析', authorId='1001}

三孕豹,1.0 的不足

1.0 的功能完成了,在拿給老王看之前怔蚌,我抽了根煙思考了一下:

V1.0 的不足

  • 在 Executor 中巩步,對(duì)參數(shù)、語(yǔ)句和結(jié)果集的處理是耦合的桦踊,沒(méi)有實(shí)現(xiàn)職責(zé)分離椅野;
  • 參數(shù):沒(méi)有實(shí)現(xiàn)對(duì)語(yǔ)句的預(yù)編譯,只有簡(jiǎn)單的格式化(format)籍胯,效率不高竟闪, 還存在 SQL 注入的風(fēng)險(xiǎn);
  • 語(yǔ)句執(zhí)行:數(shù)據(jù)庫(kù)連接硬編碼杖狼;
  • 結(jié)果集:還只能處理 Blog 類型炼蛤,沒(méi)有實(shí)現(xiàn)根據(jù)實(shí)體類自動(dòng)映射。 確實(shí)有點(diǎn)搓蝶涩,拿不出手理朋。

V1.0 的優(yōu)化目標(biāo)

  • 支持參數(shù)預(yù)編譯絮识;
  • 支持結(jié)果集的自動(dòng)處理(通過(guò)反射);
  • 對(duì) Executor 的職責(zé)進(jìn)行細(xì)化嗽上。

V1.0 的功能增強(qiáng)目標(biāo)

  • 在方法上使用注解配置 SQL次舌;
  • 查詢帶緩存功能;
  • 支持自定義插件兽愤。

四彼念、V2.0 的實(shí)現(xiàn)

1、配置文件

創(chuàng)建了全局配置文件 mybatis.properties浅萧,存放 SQL 連接信息逐沙、緩存開(kāi)關(guān)、插件地 址洼畅、Mapper 接口地址吩案。 全局配置文件在 Configuration 配置類的構(gòu)造器中解析。

2土思、參數(shù)處理

創(chuàng)建 ParameterHandler务热,調(diào)用 psmt 的 set 方法。propertie 文件中 SQL 語(yǔ)句的%d 占位符改成己儒?崎岂。

3、結(jié)果集處理

創(chuàng)建 ResultSetHandler闪湾,在其中創(chuàng)建 pojo 對(duì)象冲甘,獲取 ResultSet 值,通過(guò)反射給 pojo 對(duì)象賦值途样。

實(shí) 體 類 的 轉(zhuǎn) 換 關(guān) 系 通 過(guò) @Entity 注 解 ( 保 存 在 MapperRegistry 中 ) 江醇, 從 MapperProxyFactory(構(gòu)造函數(shù))——MapperProxy 一路傳遞到 ResultSetHandler 中。

4何暇、語(yǔ)句執(zhí)行處理

創(chuàng)建 StatementHandler陶夜,在 Executor 中調(diào)用。封裝獲取連接的方法裆站。

執(zhí)行查詢前調(diào)用 ParameterHandler条辟,執(zhí)行查詢后調(diào)用 ResultSetHandler

5、支持注解配置 SQL

定義了一個(gè)@Select 注解宏胯,加在方法上羽嫡。

在 Configuration 構(gòu) 造 函 數(shù) 中 的 parsingClass() 中解析, 保存在mappedStatements 中(一個(gè) HashMap)肩袍。

注意:在 properties 中和注解上同時(shí)配置 SQL 語(yǔ)句杭棵,注解會(huì)覆蓋 properties。 properties 中對(duì)表達(dá)三個(gè)對(duì)象的映射關(guān)系并不適合氛赐,所以暫時(shí)用--分隔魂爪。注意類型 前面不能有空格先舷。

6、支持查詢緩存

定 義 了 一 個(gè) CachingExecutor 甫窟, 當(dāng) 全 局 配 置 中 的 cacheEnabled=true 時(shí) 密浑, Configuration 的 newExecutor()方法會(huì)對(duì) SimpleExecutor 進(jìn)行裝飾蛙婴,返回被裝飾過(guò)的 Executor粗井。CachingExecutor 中用 HashMap 維護(hù)緩存。 在 DefaultSqlSession 調(diào)用 Executor 時(shí)街图,會(huì)先走到裝飾器 CachingExecutor浇衬。 定義了一個(gè) CacheKey 用于計(jì)算緩存 Key,主要根據(jù) SQL 語(yǔ)句和參數(shù)計(jì)算餐济。

7耘擂、支持插件

定義了一個(gè)@Intercepts 注解,目前還只能攔截 Executor 的方法絮姆,所以屬性只要配置方法名稱醉冤。

定義 Interceptor 接口,是所有自定義插件必須實(shí)現(xiàn)的接口篙悯。

定義 InterceptorChain 容器蚁阳,用來(lái)存放解析過(guò)的攔截器。在 Configuration 中創(chuàng)建 Executor 的時(shí)候鸽照,會(huì)調(diào)用它的 pluginAll()方法螺捐,對(duì) Executor 循環(huán)代理。

定義 Invocation 包裝類矮燎,用于在執(zhí)行完自定義插件邏輯后調(diào)用 Executor 的原方法定血。

定義 Plugin 代理類,提供了一個(gè) wrap()方法用于產(chǎn)生代理對(duì)象诞外。當(dāng) Executor 被代 理后澜沟,所有的方法都會(huì)走到 invoke()方法中,進(jìn)一步調(diào)用自定義插件的 intercept()方法峡谊。

完成了這些功能茫虽,我覺(jué)得應(yīng)該可以拿給老王看了。

五靖苇、V2.0 可優(yōu)化之處

老王看了2.0 的代碼以后席噩,點(diǎn)了一根煙,提了一些建議:

1 贤壁、在 ResultSetHandler 中 悼枢, 類 型 處 理 都 是 寫(xiě) 死 的 , 能 不 能 創(chuàng) 建 一 個(gè) TypeHandler脾拆,把這些關(guān)系維護(hù)起來(lái)馒索,處理所有類型的轉(zhuǎn)換關(guān)系和自定義類型莹妒; 2、只實(shí)現(xiàn)了@Select 的注解绰上,插入旨怠、刪除、修改的注解呢蜈块?參數(shù)能不能用@Param 傳入類型鉴腻?

3、插件只能攔截 Executor百揭,能不能實(shí)現(xiàn)對(duì)其他核心對(duì)象的方法的攔截爽哎?插件可 以支持配置參數(shù)么?

4器一、緩存只有一級(jí)课锌,不能在單個(gè)方法上關(guān)閉(properties 不夠用了),能不能實(shí) 現(xiàn)多級(jí)的緩存祈秕?

5渺贤、異常處理有點(diǎn)粗暴,都是直接 catch请毛,沒(méi)有細(xì)化志鞍;

…… 小哥,接下來(lái)拯救世界的任務(wù)就交給你了……

工程源碼:https://gitee.com/javacoo/my-batis-custom

一些信息
路漫漫其修遠(yuǎn)兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末获印,一起剝皮案震驚了整個(gè)濱河市述雾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兼丰,老刑警劉巖玻孟,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鳍征,居然都是意外死亡黍翎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)艳丛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)匣掸,“玉大人,你說(shuō)我怎么就攤上這事氮双∨鲈停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵戴差,是天一觀的道長(zhǎng)送爸。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么袭厂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任墨吓,我火速辦了婚禮,結(jié)果婚禮上纹磺,老公的妹妹穿的比我還像新娘帖烘。我一直安慰自己,他們只是感情好橄杨,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布秘症。 她就那樣靜靜地躺著,像睡著了一般讥珍。 火紅的嫁衣襯著肌膚如雪历极。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,919評(píng)論 1 290
  • 那天衷佃,我揣著相機(jī)與錄音,去河邊找鬼蹄葱。 笑死氏义,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的图云。 我是一名探鬼主播惯悠,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼竣况!你這毒婦竟也來(lái)了克婶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丹泉,失蹤者是張志新(化名)和其女友劉穎情萤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體摹恨,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筋岛,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晒哄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睁宰。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖寝凌,靈堂內(nèi)的尸體忽然破棺而出柒傻,到底是詐尸還是另有隱情,我是刑警寧澤较木,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布红符,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏违孝。R本人自食惡果不足惜刹前,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雌桑。 院中可真熱鬧喇喉,春花似錦、人聲如沸校坑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耍目。三九已至膏斤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邪驮,已是汗流浹背莫辨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留毅访,地道東北人沮榜。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像喻粹,于是被迫代替她去往敵國(guó)和親蟆融。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容