• 搜索
    搜新闻
  • 您的位置: 首页 >  资讯

    聊聊Mybatis的实现原理-每日看点

    来源博客园时间:2023-05-21 14:26:43
    使用示例

    平时我们使用的一般是集成了Spring或是Spring Boot的Mybatis,封装了一层,看源码不直接;如下,看看原生的Mybatis使用示例

    示例解析

    通过代码可以清晰地看出,MyBatis的操作主要分为两大阶段:


    (资料图)

    第一阶段:MyBatis初始化阶段。该阶段用来完成MyBatis运行环境的准备工作,读取配置并初始化关键的对象,提供给后续使用,只在 MyBatis启动时运行一次。第二阶段:数据读写阶段。该阶段由数据读写操作触发,将根据要求完成具体的增、删、改、查等数据库操作。

    在第一阶段,最关键的就是SqlSessionFactory对象。在Spring集成Mybatis的源码中,SqlSessionFactoryBean也是做这个事情,读取配置并初始化构建SqlSessionFactory

    public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {    // Spring Bean的生命周期会调用此方法    public void afterPropertiesSet() throws Exception {        this.sqlSessionFactory = this.buildSqlSessionFactory();    }    protected SqlSessionFactory buildSqlSessionFactory(){        // 构建Configuration....        Configuration configuration;        if (this.configuration != null) {            configuration = this.configuration;            if (configuration.getVariables() == null) {                configuration.setVariables(this.configurationProperties);            } else if (this.configurationProperties != null) {                configuration.getVariables().putAll(this.configurationProperties);            }        } else if (this.configLocation != null) {            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);            configuration = xmlConfigBuilder.getConfiguration();        } else {            if (LOGGER.isDebugEnabled()) {                LOGGER.debug("Property `configuration` or "configLocation" not specified, using default MyBatis Configuration");            }            configuration = new Configuration();            configuration.setVariables(this.configurationProperties);        }        /// 其它code...        return this.sqlSessionFactoryBuilder.build(configuration);    }}

    配置文件的解析,最终会生成一个Configuration对象。

    private void parseConfiguration(XNode root) {    try {        Properties settings = this.settingsAsPropertiess(root.evalNode("settings"));        this.propertiesElement(root.evalNode("properties"));        this.loadCustomVfs(settings);        this.typeAliasesElement(root.evalNode("typeAliases"));        this.pluginElement(root.evalNode("plugins"));        this.objectFactoryElement(root.evalNode("objectFactory"));        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));        this.reflectionFactoryElement(root.evalNode("reflectionFactory"));        this.settingsElement(settings);        this.environmentsElement(root.evalNode("environments"));        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));        this.typeHandlerElement(root.evalNode("typeHandlers"));        // 解析mappers节点        this.mapperElement(root.evalNode("mappers"));    } catch (Exception var3) {        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);    }}

    前期的准备已就绪,关键的配置已解析且构建并初始化了SqlSessionFactory了。接下来就是创建数据库连接并执行业务的CRUD。在第二阶段的OpenSession方法负责创建并打开数据库链接。

    public SqlSession openSession(Connection connection) {    return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {    Transaction tx = null;    DefaultSqlSession var8;    try {        Environment environment = this.configuration.getEnvironment();        TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);        Executor executor = this.configuration.newExecutor(tx, execType);        var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);    } catch (Exception var12) {        this.closeTransaction(tx);        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);    } finally {        ErrorContext.instance().reset();    }    return var8;}

    最后就是调用Mapper接口的业务方法,返回业务数据。

    //SqlSession.getMapper()public  T getMapper(Class type) {    return this.configuration.getMapper(type, this);}// configuration.getMapper()public  T getMapper(Class type, SqlSession sqlSession) {    return this.mapperRegistry.getMapper(type, sqlSession);}// mapperRegistry.getMapper()public  T getMapper(Class type, SqlSession sqlSession) {    MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);    if (mapperProxyFactory == null) {        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");    } else {        try {            return mapperProxyFactory.newInstance(sqlSession);        } catch (Exception var5) {            throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);        }    }}// mapperProxyFactory.newInstance()protected T newInstance(MapperProxy mapperProxy) {    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);}public T newInstance(SqlSession sqlSession) {    MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);    return this.newInstance(mapperProxy);}

    最后,打完收工,示例代码所涉及到的关键代码就这些。

    反思

    上面的示例是比较简单的,那么其实现思路到底是什么样的?首先就有几个问题:

    Mapper接口中的方法没有实现,那客户端调用接口的方法时,返回的数据是从哪来的?Mapper文件与Mapper接口是怎么关联绑定上的?

    第一个问题,绝对离不开动态代理,因为只有接口的时候,那么一定会有动态代理生成代理类同时有拦截处理器(InvocationHandler)来增强其执行逻辑。

    第二个问题,Mapper文件中有一个节点,其namespace就是接口的全限定名称,而其下节点