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&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介面生成代理類例項的流程,大致如下(不太準確):