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接口生成代理类实例的流程,大致如下(不太准确):