看到这篇文章,你一定和我刚开始接触MyBatis一样很好奇它到底是如何实现了对数据库的CRUD。接下来我们一点一点的分析
废话不多说,直接开始:
先放一个工程以便分析
对应表的实体类
public class User {
private String name;
private Integer age;
private String gender;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", address='" + address + '\'' +
'}';
}
}
Dao层接口
public interface UserDao {
List<User> findAll ();
}
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">
<configuration>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql:///test?serverTimezone=CTT"/>
<property name="username" value="root"/>
<property name="password" value="1229"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserDao.xml"/>
</mappers>
</configuration>
接口的映射
<?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="UserDao">
<select id="findAll" resultType="User">
select * from user
</select>
</mapper>
测试方法
@Test
public void testFindAll2 () throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream is = Resources.getResourceAsStream("mybatis_config.xml");
SqlSessionFactory factory = builder.build(is);
SqlSession sqlSession = factory.openSession();
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> users = userDao.findAll();
for (User user : users) {
System.out.println(user);
}
}
在测试方法中,最重要的就是sqlSession对象,是它创建了UserDao接口的实现类对象。
先看看它是怎么来的
需要sqlSession对象,就先需要SquSessionFactory的对象factory;需要factory,就先需要SqlSessionFactoryBuilder的对象builder。
其中,builder创建factory用到了建造者模式,factory创建sqlSession用到了工厂模式,这里不细说。
builder的build()方法接收一个InputStream流,把MyBatis的配置文件传递进去,创建了factory
Ctrl+左键进入这个方法,看看源码
不管怎么样,最终都会调用接收一个Configuration类型参数的build()方法
其中Configuration类型参数是由XMLConfigBuilder类型的parse()方法得到的,Ctrl+左键看看XMLConfigBuilder的构造方法干了什么
最终调用了
private XMLConfigBuilder(XPathParser parser, String environment, Properties props)
这个构造方法中调用父类的构造方法创建的一个Configuration类对象
目前这个Configuration对象还什么都没有
其中XPathParser对象parser接收了之前的配置文件
那么parser.parse()又做了什么
它最终返回了configuration,这个configuration就是刚刚在构造方法中创建的Configuration对象。
在返回前,调用了
private void parseConfiguration(XNode root)
看到了吗,这里面全都是标签语句,也就是说这个parseConfiguration(XNode root)方法把刚刚那个新创建的configuration填充了,用到的就是我们之前写好的MyBatis配置文件。
再回到最开始的SqlSessionFactoryBuilder类的那个最终调用的build()方法
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
它返回了一个DefaultSqlSessionFactory对象,传进了带有我们写好的配置文件信息的config,这样我们就得到了SqlSessionFactory对象,接下来就需要利用这个SqlSessionFactory对象的openSession()方法来
很明显,这个类是SqlSessionFactory接口的实现类
这个类中有很多很多openSession()方法的重载 ,不过最终都指向了
这个方法又返回了一个DefaultSqlSession类对象,显然这是一个SqlSession接口的实现类。
到目前为止,我们才刚刚得到SqlSession对象。
好,可以跳回去了,到我们的测试类
SqlSession对象调用了getMapper()方法,创建了UserDao接口的实现类对象
看看getMapper()做了什么
一层一层的进入方法,最终在MapperRegistry类中的getMapper()方法返回了一个mapperProxyFactory.newInstance(sqlSession)
这个东西就是动态代理了。
好,动态代理简单的讲就是把原本的方法fun1()进行功能扩展,比如fun1()原本只能打印“hello”,那么就为他添加功能不但能打印“hello”,还能再将这个“hello”存放进文件中。具体实现的步骤就是:
利用Proxy类的newProxyInstance()方法,提供三个参数,第一个是需要进行扩展的方法所在类的类加载器,第二个是这个类实现的接口数组,第三个是一个InvocationHandler接口的实现类。
实现InvocationHandler接口需要实现invoke()方法,它包含三个参数,Object proxy,Method method,Object[] args,其中proxy是代理对象,method是原类的方法,args是原方法的参数数组。
利用反射机制,可以方便的使用method对象使用原方法。
比如:method.invoke (this, args),就可以实现fun1()中的打印“haha”功能,然后就可以在invoke()方法中写保存“haha”的逻辑了。
所以,这个mapperProxy就变得重要了,这是一个MapperProxy对象,并且它实现了InvocationHandler接口,所以它就是用于扩展我们定义的Dao层接口功能的类。
MyBatis把扩展功能都放进了mapperMethod.execute()中
终于!要到终点了,这里就是代理模式扩展的功能代码。
总结一下
我们先写好MyBatis的配置文件,其中包含了数据库连接的相关信息,以及Dao层接口的映射文件地址,此处记为mybatis_config.xml。
我们把mybatis_config.xml传递给SqlSessionFactoryBuilder类的build()方法,它解析了这个xml配置文件,提取出了关键信息,创建了SqlSessionFactory对象,然后SqlSessionFactory对象又通过openSqlSession()方法创建了SqlSession对象,打开源码,你会发现这个对象类似于传统JDBC中的PrepareStatement对象,有一些增删改查方法。
我们利用SqlSession对象来为Dao层接口创建一个代理对象,扩充了接口中方法的功能,实现了CRUD。
我个人认为,这些东西我们不必完全了解,因为这毕竟是一个开源框架,如果真的要深入了解,我觉得框架所蕴含的思想更为重要,那就是一劳永逸,把复用做到极致,尽可能的让同功能的代码只出现一次。