1. 程式人生 > >DAO層介面,為什麼能操作資料庫

DAO層介面,為什麼能操作資料庫

public interface TestDAO {
    Test selectById(Integer id);
}

一、問題:

  如上程式碼所示,為什麼呼叫TestMapper的selectByPrimaryKey方法,就能從資料庫中讀取資料?TestMapper不是個介面嗎?介面怎麼能直接呼叫方法呢?

猜測:

  介面當然是不能直接呼叫方法的,那麼介面的實現類呢?應該是mybatis框架,自動實現了DAO層的介面。

那麼讓我們debug一下,來看看DAO層的實現類,是怎麼生成的。

二、準備工作,程式碼準備

1、需要一個實體類

public class Test {
    private Integer id;

    private String name;

   //...
}

2、需要一個DAO層介面

public interface TestDAO {
    Test selectById(Integer id);
}

3、需要一個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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_test?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/TestMapper.xml"/>
    </mappers>
</configuration>

4、需要一個TestMapper.xml檔案,與TestDAO介面進行繫結

<?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="com.me.mybatis.dao.TestDAO">
    <resultMap id="testMap" type="com.me.mybatis.domain.Test">
        <result property="id" column="id" />
        <result property="name" column="name" />
    </resultMap>

    <sql id="allColumn">
        id, name
    </sql>

    <select id="selectById" resultMap="testMap">
        SELECT <include refid="allColumn"/>
        FROM test
        WHERE id = #{id}
    </select>

</mapper>

5、POM檔案,引入依賴

<?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.me</groupId>
    <artifactId>mybatis-test</artifactId>
    <version>1.0-SNAPSHOT</version>


    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

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

</project>

6、專案結構如下:

7、Main類,進行測試

public class Main {

    public static SqlSession getSqlSession() throws IOException {
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        return sqlSessionFactory.openSession();
    }

    public static void main(String[] args) throws IOException {
        TestDAO testDAO = getSqlSession().getMapper(TestDAO.class);
        Test test = testDAO.selectById(1);
    }
}

三、開始debug

1、起始位置

   從main方法中的TestDAO testDAO = getSqlSession().getMapper(TestDAO.class);這一行開始debug,探究TestDAO介面的實現類,是怎麼來的?

2、UML圖

  這裡先給出一個UML圖,照著這個UML進行講解

3、sqlsession將getMapper方法,委託給configuration實現

3.1 那configuration又是哪裡冒出來的呢?

   還記得mybatis-config.xml檔案嗎?xml中的元素,與JAVA類是一一對應的。所以這個檔案裡的<configuration></configuration>,也就被解析成了這裡的DefaultSqlSession類中的configuration。

4、configuration又將getMapper方法委託給mapperRegistery去實現

4.1 mapperRegistery又是哪裡來的?

可以發現MapperRegistery作為Configuration的例項變數,是自己new出來的。那它對應xml中的哪一塊呢?我們繼續往下看。

5、mapperRegistery又將getMapper方法,委託給了MapperProxyFactory去實現

5.1 我們可以看到mapperProxyFactory是由knownMappers這個Map,get出來的。那knownMappers這個Map,是什麼時候有值的?

5.2  我們看到在addMapper這個方法中,knownMappers 加入了值。於是我們沿著addMapper回溯,看看是誰呼叫了它。

回溯路線: MapperRegistery.addMapper() -->  Configuration.addMapper() --> XMLConfigBuilder.mapperElement() --> XMLConfigBuilder.parseConfiguration()

  5.3  在parseConfiguration方法中,我們可以看到mapperElement(root.evalNode("mappers")),這是什麼意思?這是在解析xml檔案中的<mappers></mappers>標籤啊。

  也就是說MapperProxyFactory中的knownMappers,存的就是一個個的mapper。

6、繼續回到MapperRegistery中的getMapper方法

可以看到mapperProxyFactory這個代理工廠,終於生成代理類的例項了。

7、繼續跟蹤

   終於到了我們所熟悉的動態代理方法了,就是在這裡生成了DAO層的例項。這也就是為什麼DAO層的介面,能夠直接呼叫方法的原因了——其實不是介面呼叫方法,而是它的代理類呼叫方法。

  根據我們所熟知的動態代理,可以知道mapperProxy必定是實現了InvocationHandler介面,我們點進去,驗證一下。發現確實如此。

以後我們呼叫TestDAO這個介面中的方法時,實際上會到MapperProxy的invoke方法中來。

四、完善UML

經過debug,我們可以知道,TestDAO介面生成代理類例項的流程,大致如下(不太準確):