Mybatis 实现原理

时间:2022-10-02 14:55:48

Mybatis 手撸专栏

MapperProxyFactory

Mapper是什么?

Mapper由两个文件组成
1 Java文件(映射器类/IDAO)用户定义的数据库操作接口:想要对数据库进行的操作

public interface IUserService {
    queryNameById();
    queryAgeById();
}

2 XML文件(XML映射文件)操作的具体实现:SQL语句

<?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="cn.IUserService">
    <select id="queryNameById" parameterType="java.lang.Integer" resultType="java.lang.String">
    	SELECT name FROM user WHERE id = #{id}
    </select>
    <select id="queryAgeById" parameterType="java.lang.Integer" resultType="java.lang.String">
    	SELECT age FROM user WHERE id = #{id}
    </select>
</mapper>

在 XML 文件中,通过 namespace,可以找到对应 IDAO类,后面会用到

MapperProxy

通过IDAO接口 + XML文件,可以完成对数据库的操作
那么,IDAO接口和XML文件,是如何联系在一起的?
通过代理类
将IDAO接口和XML文件封装到代理类中
之后,通过这个代理类,当调用IDAO接口中的方法时,最终会调用XML中对应SQL语句
这个代理类就是MapperProxy,如图
Mybatis 实现原理

public class MapperProxy implements InvocationHandler {
    private Class IDAO;
    private HashMap<String, String> xmlFile;
    public MapperProxy(Class IDAO, HashMap<String, String> xmlFile) {
        this.IDAO = IDAO;
        this.xmlFile = xmlFile;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return xmlFile.get(method.getName());
    }
}

测试如下

HashMap<String, String> xmlFile = new HashMap<>();
xmlFile.put("queryNameById", "SELECT name FROM user WHERE id = 1");
xmlFile.put("queryAgeById", "SELECT age FROM user WHERE id = 1");
MapperProxy mapperProxy =  new MapperProxy(IUserService.class, xmlFile);
IUserService o = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, mapperProxy);
System.out.println(o.queryNameById());		// SELECT name FROM user WHERE id = 1

测试的时候,每次都要调用 Proxy.newProxyInstance,很麻烦;于是,封装到 MapperProxy 中

public Object getProxyObject(){
    return Proxy.newProxyInstance(IDAO.getClassLoader(), new Class[]{IDAO}, this);
}

// 之后就可以
MapperProxy mapperProxy =  new MapperProxy(IUserService.class, xmlFile);
IUserService o = (IUserService) mapperProxy.getProxyObject();
System.out.println(o.queryNameById());		// SELECT name FROM user WHERE id = 1

MapperProxyFactory

相同的IDAO接口,可有不同的实现方式,甚至,所使用的数据库也可能不同

因此,干脆将IDAO封装到一个类中,之后通过 newInstance(xmlFIle) 的方式生成 MapperProxy 代理对象

另外,Proxy.newProxyInstance 的逻辑也封装到 newInstance 中

这个类就是MapperProxyFactory
Mybatis 实现原理

public class MapperProxyFactory<T> {
    private Class<T> IDAO;
    public MapperProxyFactory(Class<T> IDAO) {
        this.IDAO = IDAO;
    }
    public T newInstance(HashMap<String, String> xmlFIle) {
        final MapperProxy mapperProxy = new MapperProxy(IDAO, xmlFIle);
        return (T) Proxy.newProxyInstance(IDAO.getClassLoader(), new Class[]{IDAO}, mapperProxy);
    }
}

使用如下

IUserService o = new MapperProxyFactory<>(IUserService.class).newInstance(xmlFile);
System.out.println(o.queryNameById());

第2章:创建简单的映射器代理工厂

这里的 sqlSession 理解为 XML 文件,mapperInterface 理解为 IDAO,后面就使用作者的了
Mybatis 实现原理

MapperRegistry 及 SqlSession 封装

MapperRegistry

MapperProxyFactory 只是针对一个IDAO接口,并不能添加、删除;那么,就必定需要一个集中式管理的类
这个类就是 MapperRegistry,相当于封装了多个 IDAO 接口,用于集中式管理IDAO接口

SqlSession

之前定义的 sqlsession (xmlFile)为一个 HashMap,现将其封装成一个对象,并由 SqlSessionFactory 构造,该工厂持有 MapperRegistry(封装多个IDAO服务)
通过该工厂,可创建 SqlSession,但此时 SqlSession 中未定义具体实现

为何要创建一个 SqlSessionFactory ?直接 new SqlSession 不行吗?
诶,好像还真可以~
如果要强行解释的话,那么我的强行解释就是,Factory,从语义上可以理解为创建 xxx 的工厂,很规范。。。。。容易理解

第3章:实现映射器的注册和使用

Mybatis 实现原理

XML解析

Configuration 类

根据 mapper.xml 的信息,namespace 对应 IDAO的位置,select 等对应 SQL 语句,通过解析 XML,可获取这些内容
解析出这些内容后,必定要存储,于是用到了 Configuration 类:将 xmlFile(对数据库的操作)的信息,封装到 Configuration 类中
Configuration 类中有

  • mapperRegistry(为多个IDAO服务):根据 mapper.xml 文件下的 namespace 进行添加的
  • mappedStatements(与IDAO对应的SQL语句):类型为 HashMap<String, MappedStatement>,一个 MappedStatement 对应一条 SQL 语句
    Mybatis 实现原理

XML environments 解析

Environment

这里会解析 environment(用于连接数据库的信息:driver、url、username、password),并放入 Configration类中
于是,Configuration 中多出了个 Environment 类实例的成员变量,Environment 类中有

  • id:如 development
  • transactionFactory:如 JdbcTransactionFactory
  • dataSource:如 DruidDataSource

typeAliasRegistry 类型别名注册机

Configration类中有个名为 typeAliasRegistry 的成员变量,它是 TypeAliasRegistry 类的实例,类中有个 HashMap,用于存储别名

 <select id="queryUser" resultType="com.foo.bar.baz.quz.User">
 	SELECT * FROM user WHERE id = 1
 </select>

resultType 很长是不是 ? 起个别名吧~

<configuration>
    <typeAliases>
        <typeAlias alias="user" type="com.foo.bar.baz.quz.User"/>
    </typeAliases>
</configuration>
 <select id="queryUser" resultType="user">
 	SELECT * FROM user WHERE id = 1
 </select>

new Configuration 的时候(还未解析XML文件时),会将 JdbcTransactionFactory、DruidDataSourceFactory 存放到 typeAliasRegistry 内的 HashMap
之后,根据 XML 文件中的信息,可通过 typeAliasRegistry 获取到JdbcTransactionFactory、DruidDataSourceFactory ,用于创建 Environment

Mybatis 实现原理

池化技术

Mybatis 实现原理