对 Mybatis 执行过程的分析
- Mybatis 在使用代理dao的方式实现增删改查时做什么事情呢?
- 只有两件事情
- 第一:创建代理对象
- 第二:在代理对象中调用selectList
- 只有两件事情
1. 执行查询所有分析 selectList
首先是测试类中,调用 Mybatis 框架进行查询的代码:
public class MybatisTest {
/**
* 入门案例
* @param args
*/
public static void main(String[] args) throws IOException {
// 1. 读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
// 2. 创建 SqlSessionFactory 工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
// 3. 使用工厂生产一个 SQLSession 对象
SqlSession session = factory.openSession();
// 4. 使用 SQLSession 创建接口的代理对象
IUserDao userDao = session.getMapper(IUserDao.class);
// 5. 使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
// 6. 释放资源
session.close();
is.close();
}
}
我们通过创建代理对象,来进行方法的执行,但是这个代理方法调用的方法,实际上是 SqlSession 对象中的 selectList 方法。
在 Mybatis 的配置文件中(名字是自定义的):
<?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">
<!--mybatis 的主配置文件-->
<configuration>
<!-- 配置环境 -->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务的类型-->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,也叫连接池-->
<dataSource type="POOLED">
<!-- 配置连接数据的 4 个基本信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/review/mybatis/dao/IUserDao.xml"></mapper>
</mappers>
</configuration>
有以下一些代码:
<dataSource type="POOLED">
<!-- 配置连接数据的 4 个基本信息-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
连接数据库的信息,有了它们就能够创建 Connection 对象。
<mappers>
<mapper resource="com/review/mybatis/dao/IUserDao.xml"></mapper>
</mappers>
这是映射的配置信息
在IUserDao.xml 中,有以下代码:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.review.mybatis.dao.IUserDao">
<select id="findAll" resultType="com.review.mybatis.domain.User">
select * from user
</select>
</mapper>
其中:
<mapper namespace="com.review.mybatis.dao.IUserDao">
<select id="findAll" resultType="com.review.mybatis.domain.User">
select * from user
</select>
</mapper>
这里面是需要执行的 sql 语句,有了它,就可以获取 PreparedStatement 对象。这个配置中还有封装的实体类全限定的类名。这个就决定我们用什么去查询,又封装到哪里去。
这些所有的东西,都是我们上来需要做的事情,也就是需要读成一个流,解析配置文件。
读取配置文件有很多种,用到的技术也就是解析 XML 的技术。此处使用的是 dom4j 解析xml 的技术。
在解析完 xml 之后,第二片代码创建了 Connection
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
,之后的那一片代码
<mappers>
<mapper resource="com/review/mybatis/dao/IUserDao.xml"></mapper>
</mappers>
会读到最下面的配置文件,
<mapper namespace="com.review.mybatis.dao.IUserDao">
<select id="findAll" resultType="com.review.mybatis.domain.User">
select * from user
</select>
</mapper>
而最下面的配置文件,里面就包含了我们想要的一些信息。
在解析完 xml 文件,并读取之后,Mybatis 框架调用 selectList 方法,而selectList 方法的执行过程如下:
-
根据配置信息创建 Connection 对象。
- 注册驱动,获取连接
-
获取预处理对象 PreparedStatement 此时需要 SQL 语句
- conn.preparedStatement(Sql);
-
执行查询
- ResultSet set = PreparedStatement.executeQuery();
-
遍历结果集,用于封装
List<E> list = new ArrayList<E>(); while(set.next()) { E element = xxx; //进行封装,把每个set中的内容都添加到 element 中 //把 element 加入到 list 中 list.add(element); }
继续深入,我们会发现,这个 E ,其实就是我们之前
<mapper namespace="com.review.mybatis.dao.IUserDao"> <select id="findAll" resultType="com.review.mybatis.domain.User"> select * from user </select> </mapper>
这一段配置文件中的全限定类名,根据这个全限定类名,使用反射获得这个对象。
List<E> list = new ArrayList<E>(); while(set.next()) { E element = (E) Class.forName(配置的全限定类名).newInstance() //进行封装,把每个set中的内容都添加到 element 中 //把 element 加入到 list 中 list.add(element); }
在获得实体类之后,我们就可以封装了:
List<E> list = new ArrayList<E>(); while(set.next()) { E element = (E) Class.forName(配置的全限定类名).newInstance() // 我们的实体类属性和表中的列名是一致的。 // 于是我们可以把表的列名看成是实体类的属性名 // 就可以使用反射的方式来根据名称获取每个属性,并把值赋进去 list.add(element); }
-
返回 list ;
return list;
从以上的执行过程可以看出,要想让 selectList 方法执行,我们需要给方法提供两个信息:
-
第一个:连接信息
-
第二个:映射信息
-
它包含了两个部分:
-
第一:执行的 SQL 语句
-
第二:封装结果的实体类全限定类名
-
以上两个信息我们需要合在一起封装,我们可以将这两个信息组合起来定义成一个对象。为什么定义成某个对象呢?因为如果我们定义某个参数,那么在以后的开发中,如果我们想要查询表中其他的东西,我们修改了 SQL 语句,那么反映到映射信息中,就不知道哪条和哪条对应了。
这一个对象可以取个名(随便),这里我称为 Mapper 。但是我们开发项目中如果有多个这样的Mapper,Mapper都不固定,我们就要将其存起来。这样我们需要一个 Map 将其存起来。 Key 是 String 类型,这个key是 Dao 接口和其方法:
com.review.mybatis.dao.IUserDao.findAll()
value就是 那个 Mapper 对象,其中包含了一个执行的 SQL 语句和实体类的全限定类名。
-
-
2. 创建代理对象的分析
在之前的例子中,我们通过 SqlSession 的对象中的 getMapper 方法进行了代理对象的创建。
IUserDao userDao = session.getMapper(IUserDao.class);
在这里面:
// 根据 dao 接口的字节码创建 dao 的代理对象
public T getMapper(Class<T> daoInterfaceClass) {
/**
* 类加载器: 使用和被代理对象是相同的类加载器
* 代理对象要实现的接口: 和被代理对象实现相同的接口
* 如何代理:它就是增强的方法,我们要自己来提供。
* 此处是一个 InvocationHandler 的接口,我们要写一个
* 该接口的实现类。在实现类中调用 selectList 方法。
*/
Proxy newProxyInstance(类加载, 代理对象要实现的接口字节码数组, 如何代理);
}