MyBatis中通過(guò)package標(biāo)簽加載mapper映射文件的方式分析

本文作者:孔維勝,叩丁狼高級(jí)講師。原創(chuàng)文章,轉(zhuǎn)載請(qǐng)注明出處。

MyBatis中通過(guò)package標(biāo)簽加載mapper映射文件的方式分析

看文章前的要求

在學(xué)習(xí)MyBatis的初級(jí)篇之前,有兩個(gè)前提要求,第一.必須學(xué)會(huì)使用IDEA,因?yàn)樵谖恼轮校褂玫墓ぞ邽镮DEA,文章中的案例也都是基于IDEA的。第二.必須學(xué)會(huì)使用MAVEN,因?yàn)樵诎咐行枰膉ar包,都是通過(guò)MAVEN來(lái)管理的。

文章中的案例的開發(fā)環(huán)境

JDK 1.8

IDEA 2017.3

MySQL 5.1.38

Apache Maven 3.5.0

Tomcat 9.0.6

MyBatis 3.4.6

案例需要的表和數(shù)據(jù)

我們使用MyBatis的目的最終是訪問(wèn)數(shù)據(jù)庫(kù),所以在數(shù)據(jù)庫(kù)方面,我們先創(chuàng)建相應(yīng)的數(shù)據(jù)庫(kù),表,導(dǎo)入相關(guān)的數(shù)據(jù)。如:

1.創(chuàng)建mybatis數(shù)據(jù)庫(kù)。

2.在mybatis數(shù)據(jù)庫(kù)中創(chuàng)建department(部門表)。

DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
  `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '部門ID',
  `name` varchar(20) DEFAULT NULL COMMENT '部門名稱',
  `sn` varchar(20) DEFAULT NULL COMMENT '部門縮寫',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

3.準(zhǔn)備department(部門表相關(guān)的數(shù)據(jù))

INSERT INTO `department` VALUES (1, '人力資源', 'HR_DEPT');
INSERT INTO `department` VALUES (2, '銷售部', 'SALE_DEPT');
INSERT INTO `department` VALUES (3, '開發(fā)部', 'DEVELOP_DEPT');
INSERT INTO `department` VALUES (4, '財(cái)務(wù)部', 'FINANCE_DEPT');

案例需求

需求:使用Mapper動(dòng)態(tài)代理的方式完成所有數(shù)據(jù)的查詢操作。

需求分析

    1. 導(dǎo)入相關(guān)jar依賴

要使用MyBatis框架,首先需要導(dǎo)入mybatis的核心包,MyBatis主要是操作數(shù)據(jù)庫(kù),替換掉傳統(tǒng)的JDBC方式訪問(wèn)數(shù)據(jù)庫(kù),所以需要導(dǎo)入mysql的驅(qū)動(dòng)包。我們要在項(xiàng)目中使用單元測(cè)試進(jìn)行測(cè)試,所以需要導(dǎo)入junit包,我們不想寫javaBean的setter和getter方法,可以導(dǎo)入lombok的包。

  • 2.添加配置文件。

我們使用MyBatis框架,需要兩個(gè)配置文件,一個(gè)是MyBatis的主配置文件,主要用來(lái)配置事務(wù)管理器和數(shù)據(jù)庫(kù)的連接信息,一個(gè)是封裝SQL語(yǔ)句Mapper映射文件。我們?yōu)榱藬?shù)據(jù)庫(kù)的連接信息不寫死在主配置文件中,所以我們采用抽取的方式,把連接數(shù)據(jù)庫(kù)的信息抽取到db.properties文件中,進(jìn)行管理。通過(guò)package掃描的方式在主配置文件中掛載mapper的文件。如:

   <package name="cn.wolfcode.mapper"/> 
  • 3.添加實(shí)體類和接口。

可能查詢數(shù)據(jù)需要查詢條件有很多,查詢數(shù)據(jù)需要封裝到對(duì)象中,所以我們可以定義一個(gè)JavaBean,來(lái)封裝條件和查詢的數(shù)據(jù)。

定義一個(gè)接口,編寫操作數(shù)據(jù)庫(kù)方法。方法的名字保持和sql映射文件中的標(biāo)簽的id一一對(duì)應(yīng)。

  • 4.增加工具類。

通過(guò)加載主配置文件來(lái)獲取SqlSessionFactory工廠對(duì)象,一般工廠對(duì)象都是單例模式的,所以這個(gè)操作只需要做一次即可。比如:我們不能每吃一次飯,都去建一所餐廳。兩者的道理是一樣的。

而在MyBatis的官網(wǎng)給出的建議是SqlSessionFactory 一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運(yùn)行期間一直存在,沒有任何理由對(duì)它進(jìn)行清除或重建。因此 SqlSessionFactory 應(yīng)該使用其單例模式,只創(chuàng)建一次在整個(gè)應(yīng)用中,都可以使用。

工廠對(duì)象的獲取思考:

那么把獲取工廠對(duì)象的操作放在哪里合適呢?如果在本類中進(jìn)行抽取,放在一個(gè)方法中,但是每個(gè)DAO的實(shí)現(xiàn)類都這樣處理,還是會(huì)出現(xiàn)代碼的冗余。所以最合適的方式定義一個(gè)MyBatisUtil工具類,把獲取工廠對(duì)象的操作抽取到工具類中,那么工廠對(duì)象的獲取只需要獲取一次即可,所以在工具類中,定義在哪里,只會(huì)執(zhí)行一次呢?靜態(tài)代碼塊,我們都知道,類中的靜態(tài)代碼塊,只會(huì)隨著類的加載而加載,并且只執(zhí)行一次。

MyBatis工具類設(shè)計(jì)思考:

何為工具類,一般我們?cè)诙x的工具類的時(shí)候,希望使用者只使用而不要修改此類,所以我們會(huì)設(shè)置這個(gè)類使用final進(jìn)行修飾,這樣這個(gè)類就是終結(jié)類,不能被繼承。一般工具類不會(huì)讓使用者去創(chuàng)建對(duì)象,而是采用提供靜態(tài)方法的方式共使用者調(diào)用。

SqlSession對(duì)象獲取思考:

定義一個(gè)方法供外部訪問(wèn),獲取SqlSession對(duì)象。這個(gè)方法設(shè)計(jì)成靜態(tài)的這樣,調(diào)用方法的時(shí)候不用再創(chuàng)建工具類對(duì)象。

  • 5.添加測(cè)試類。

定義一個(gè)測(cè)試類,編寫一個(gè)測(cè)試方法,通過(guò)調(diào)用工具類中的方法獲取SqlSession對(duì)象,通過(guò)SqlSession對(duì)象調(diào)用getMapper方法獲取對(duì)應(yīng)的Mapper的代理對(duì)象,然后調(diào)用接口中的方法獲取所有數(shù)據(jù)。

案例代碼

pom.xml:

 <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.40</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.20</version>
    </dependency>

mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
   <properties  resource="db.properties"/>
   <typeAliases>
        <package name="cn.wolfcode.domain"/>
    </typeAliases>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driverName}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${userName}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>    
    <mappers>
       <package name="cn.wolfcode.mapper"/>
    </mappers>

</configuration>

db.properties:

driverName=com.mysql.jdbc.Driver
url=jdbc:mysql:///mybatis
userName=root
password=root123

DepartmentMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.wolfcode.mapper.DepartmetMapper">

    <!--
     select 表示查詢語(yǔ)句的標(biāo)簽。標(biāo)簽體的內(nèi)容即是查詢的SQL語(yǔ)句
          id:SQL語(yǔ)句的唯一標(biāo)識(shí)
          parameterType:傳入這條SQL語(yǔ)句的參數(shù)的類的完全限定名或別名,
                 因?yàn)?MyBatis 可以通過(guò) TypeHandler 推斷出具體傳入語(yǔ)句的參數(shù),故可以省略
          resultType:返回期望的類型(類的完全限定名或別名),用來(lái)接收查詢的結(jié)果。

      -->
    <select id="selectAll" resultType="cn.wolfcode.domain.Department">
        SELECT id,name,sn FROM department
        WHERE id = #{id}
    </select>

</mapper>

Department:

@Getter
@Setter
@ToString
public class Department {

    // 主鍵id
    private Long id;
    // 部門名稱
    private String name;
    // 部門簡(jiǎn)寫
    private String sn;
}

DepartmentMapper:

public interface DepartmentMapper {

   /**
     *  查詢所有部門信息
     * @return 返回所有部門信息的集合
     */
    List<Department> selectAll();
}

MyBatisUtil:

public final class MyBatisUtil {

    private static SqlSessionFactory factory = null;

    static {
        // 使用static靜態(tài)代碼塊,隨著類的加載而加載,只執(zhí)行一次
        try {
            // 加載MyBatis的主配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            // 通過(guò)構(gòu)建器(SqlSessionFactoryBuilder)構(gòu)建一個(gè)SqlSessionFactory工廠對(duì)象
            factory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 獲取sqlSession對(duì)象
    public static SqlSession openSession() {
        return factory.openSession();
    }
}

DepartmentMapperTest:

public class DepartmentMapperTest {
 @Test
    public void testQueryOne(){
        // 獲取sqlSession對(duì)象
        SqlSession sqlSession = MyBatisUtil.openSession();
        // 獲取Mapper對(duì)象
        DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
        List<Department> departmentList = departmentMapper.selectAll();
        // 關(guān)閉資源
        sqlSession.close();
        // 遍歷結(jié)果
  departmentList.stream().forEach(System.out::println);
    }

DepartmentMapper文件加載流程分析

  • 1 . 加載主配置文件,通過(guò)build方法構(gòu)建工廠對(duì)象。如:
    MyBatisUtil.png
  • 2 . 創(chuàng)建XML配置構(gòu)建器的對(duì)象(XMLConfigBuilder)。底層使用的是XPath解析器。 在這個(gè)方法的finally塊中,對(duì)外部傳入的流,進(jìn)行了關(guān)閉。所以外部不需要進(jìn)行關(guān)閉了。如:


    SqlSessionFactoryBuilder.png
  • 3 . 通過(guò)構(gòu)建器對(duì)象調(diào)用parse方法,把解析的數(shù)據(jù)封裝到Configuration對(duì)象中。我們主要是關(guān)心mapper文件的加載,所以繼續(xù)往下看。如:


    XMLConfigBuilder.png
  • 4 .方法中定義了一個(gè)while死循環(huán),主要是便利mappers節(jié)點(diǎn)下面的所有元素。因?yàn)槲覀儾捎玫氖窃谥髋渲梦募惺褂胮ackage掃描的方式掛載的mapper映射文件。所以跳入if代碼塊。在if塊中通過(guò)獲取name屬性的值,拿到了mapper文件的所屬的包名,通過(guò)configuration對(duì)象調(diào)用addMappers方法把mapper映射文件所在的包傳入。如:
XMLConfigBuilder.png
  • 5 .調(diào)用Mapper注冊(cè)對(duì)象中(MapperRegistry)的addMappers方法,添加映射。如:
Configuration.png
  • 6 .創(chuàng)建ResolverUtil工具類,通過(guò)調(diào)用find方法把包下面的字節(jié)碼對(duì)象找出來(lái),并存入到Set集合中,通過(guò)調(diào)用getClasses方法取出,進(jìn)行遍歷。把每一個(gè)字節(jié)碼對(duì)象傳入addMapper方法。如:
MapperRegistry.png
  • 7 .在MapperRegistry(映射注冊(cè)類)中定義一個(gè)map容器(knowMappers),用來(lái)存入映射。在addMapper方法中,先通過(guò)調(diào)用isInterface方法看看mapper是不是接口,必須是接口,才會(huì)添加。在通過(guò)調(diào)用hasMapper方法來(lái)判斷是否已經(jīng)添加過(guò)了,如果已經(jīng)添加,就拋出一個(gè)綁定異常。通過(guò)標(biāo)記loadCompleted,來(lái)確保添加成功。如果添加出現(xiàn)了異常,在finally塊中刪除map中存入的映射。把字節(jié)碼對(duì)象作為key,創(chuàng)建該字節(jié)碼對(duì)象的代理對(duì)象作為value,存入knowMappers中。并創(chuàng)建MapperAnnotationBuilder對(duì)象如:
MapperRegistry.png
  • 8 .MapperAnnotationBuilder這個(gè)類總會(huì)優(yōu)先解析xml配置文件,并且這個(gè)xml配置文件必須與Class對(duì)象所在的包路徑一致,且文件名要與類名一致。在解析完xml配置文件后,才會(huì)開始解析Class對(duì)象中包含的注解。里面有個(gè)if判斷,如果在主配置對(duì)象(configuration)添加過(guò)接口標(biāo)記,表示解析過(guò),就不再進(jìn)入if語(yǔ)句。首先調(diào)用loadXmlResource方法,解析指定的xml配置文件。如:
MapperAnnotationBuilder.png
  • 9 . 在這個(gè)方法中,先通過(guò)if判斷之前是否解析過(guò),如果沒有解析過(guò),則進(jìn)入if語(yǔ)句,把包名中的"."替換成"/",這樣變成了文件夾,然后在后面追加".xml"后綴。這樣拼接成一個(gè)xml文件的資源路徑。然后加載到內(nèi)存。在通過(guò)調(diào)用parse方法進(jìn)行解析xml文件。
    所以這也是為何如果使用package掃描的方式,必須要保證接口和mapper映射文件必須在同一個(gè)包中,名字也必須相同的原因。如:
MapperAnnotationBuilder.png
  • 10 . 繼續(xù)往下解析。如:
XMLMapperBuilder.png

DepartmentMapper文件加載整體流程圖

Mapper映射文件通過(guò)package方式解析流程圖.png

想獲取更多技術(shù)干貨,請(qǐng)前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1 Mybatis入門 1.1 單獨(dú)使用jdbc編程問(wèn)題總結(jié) 1.1.1 jdbc程序 上邊使...
    哇哈哈E閱讀 3,417評(píng)論 0 38
  • 1. 簡(jiǎn)介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存儲(chǔ)過(guò)程以及高級(jí)映射的優(yōu)秀的...
    笨鳥慢飛閱讀 6,248評(píng)論 0 4
  • “同學(xué)們,所以說(shuō)學(xué)校是‘創(chuàng)文’的一個(gè)重要陣地……”,文老師在課堂上邊用手比劃邊說(shuō):“相信同學(xué)們也聽到了校長(zhǎng)的...
    颶風(fēng)交匯點(diǎn)閱讀 290評(píng)論 0 1
  • 簫黎閱讀 499評(píng)論 5 3

友情鏈接更多精彩內(nèi)容