public interface TestDAO {
Test selectById(Integer id);
}
一、問題:
如上代碼所示,爲什麼調用TestMapper的selectById方法,就能從數據庫中讀取數據?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&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&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接口生成代理類實例的流程,大致如下(不太準確):