[Spring]MyBatis的执行原理

MyBatis的执行原理详细介绍

为了使大家能够更加清晰的理解MyBatis程序,在正式讲解MyBatis入门案例之前,先来了解一下MyBatis程序的工作原理,如图1所示。

MyBatis的工作原理#

image

从图1可以看出,MyBatis框架在操作数据库时,大体经过了8个步骤。下面就对图1中的每一步流程进行详细讲解,具体如下。

  • (1)读取MyBatis配置文件mybatis-config.xml。mybatis-config.xml作为MyBatis的全局配置文件,配置了MyBatis的运行环境等信息,其中主要内容是获取数据库连接

  • (2)加载映射文件Mapper.xml。Mapper.xml文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在mybatis-config.xml中加载才能执行。mybatis-config.xml可以加载多个配置文件,每个配置文件对应数据库中的一张表。


  • (3)构建会话工厂。通过MyBatis的环境等配置信息构建会话工厂SqlSessionFactory。

  • (4)创建SqlSession对象。由会话工厂创建SqlSession对象,该对象中包含了执行SQL的所有方法。


  • (5)MyBatis底层定义了一个Executor接口来操作数据库,它会根据SqlSession传递的参数动态的生成需要执行的SQL语句,同时负责查询缓存的维护。

  • (6)在Executor接口的执行方法中,包含一个MappedStatement类型的参数,该参数是对映射信息的封装,用来存储要映射的SQL语句的id、参数等。Mapper.xml文件中一个SQL对应一个MappedStatement对象,SQL的id即是MappedStatement的id。


  • (7)输入参数映射。在执行方法时,MappedStatement对象会对用户执行SQL语句的输入参数进行定义(可以定义为Map、List类型、基本类型和POJO类型),Executor执行器会通过MappedStatement对象在执行SQL前,将输入的Java对象映射到SQL语句中。这里对输入参数的映射过程就类似于JDBC编程中对preparedStatement对象设置参数的过程。

  • (8)输出结果映射。在数据库中执行完SQL语句后,MappedStatement对象会对SQL执行输出的结果进行定义(可以定义为Map和List类型、基本类型、POJO类型),Executor执行器会通过MappedStatement对象在执行SQL语句后,将输出结果映射至Java对象中。这种将输出结果映射到Java对象的过程就类似于JDBC编程中对结果的解析处理过程。

深度分析:mybatis的底层实现原理,看完你学会了吗?

最近在和粉丝聊天的时候被粉丝问到jdbc和mybatis底层实现这一块的问题,而且还不止一个小伙伴问到,于是我似乎认识到了问题的严重性,我花了两天时间整理了一下自己的认识和网上查阅的资料写了这篇文章,话不多说,满满的干货都在下面了。

在说mybatis底层实现之前,先看下基本的知识点jdbc
jdbc是连接数据库的最基本实现,任何对数据库的操作都是基于jdbc

  1. 注册驱动
    Class.forName("com.mysql.jdbc.Driver");

  2. 获取数据库连接
    Connection conn =DriverManager.getConnection(url,user,p);

  3. 创建向数据发送sql 的statement对象
    Statement stmt = conn.CreateStatement();

  4. 向数据库发送sql
    ResultSet rs = stmt.executeQuery(sql)//select语句
    int updateaSum = stmt.executeUpdate(sql)//insert,update delete语句

  5. 处理结果集
    while(rs.next()){
    rs.getString(列名)
    rs.getInt(列名)
    }

  6. 关闭资源
    rs.close();
    stmt.close();
    conn.close();

Mybatis之Sqlsession、Connection和Transaction解析关系与原理#

Connection JDBC 是我们用来与数据库交互最基础的API。 Connection 作为一个特定数据库的会话,在一个连接的上下文中,sql语句被执行,然后结果被返回。 我们先看下使用sqlsession进行数据库操作的基本流程 SqlSession 可以看作是对Connection 更加高级的抽象,从其方法上更加可以看出他具有更加明显的操作特征。 Transaction 事务(Transaction) ,正是对N(N>=1)个操作执行时,同时成功或同时失败的 关系 的具象。

我们先看下sqlsession是如何进行数据库操作的:

        String resource = "mybatis-config.xml";
        //获取数据配置流
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //通过SqlSessionFactoryBuilder获取SqlSessionFactory 
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //通过sqlSessionFactory获取sqlSession 
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //对数据库执行操作
        try {
            TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
            TbUser user = new TbUser("liybk", "liybk","186..","123");
            userMapper.insertUser(user);
            sqlSession.commit();// 这里一定要提交,不然数据进不去数据库中
        } finally {
            sqlSession.close();
        }

我们先看SqlSessionFactoryBuilder().build(inputStream)方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

最终执行通过一系列的xml文件解析,返回了DefaultSqlSessionFactory,进入DefaultSqlSessionFactory构造函数

public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

构造函数只是初始化了其configuration 属性,这个configuration 里面包含了一个Environment属性,而Environment属性里又有数据源,connect,事务等等一系列关于数据库操作的基本属性

public final class Environment {
    private final String id;
    private final TransactionFactory transactionFactory;
    private final DataSource dataSource;
    ....
    }

好的,我们回到主步骤SqlSession sqlSession = sqlSessionFactory.openSession()方法 其执行类是DefaultSqlSessionFactory,目的是获取sqlSession

public SqlSession openSession() {
        return 
//调用该类的openSessionFromDataSource方法       
this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

//openSessionFromDataSource方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
        //获取Environment
            Environment environment = this.configuration.getEnvironment();
            //从Environment中取得TransactionFactory;
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //在取得的数据库连接上创建事务对象Transaction
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            // 创建Executor对象
            Executor executor = this.configuration.newExecutor(tx, execType);
            //创建sqlsession对象。
            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;
    }

可以看到mybatis创建sqlsession经过了以下几个主要步骤:

  1. 从核心配置文件mybatis-config.xml中获取Environment(这里面是数据源);
  2. 从Environment中取得DataSource;
  3. 从Environment中取得TransactionFactory;
  4. 从DataSource里获取数据库连接对象Connection;
  5. 在取得的数据库连接上创建事务对象Transaction;
  6. 创建Executor对象(该对象非常重要,事实上sqlsession的所有操作都是通过它完成的);
  7. 创建sqlsession对象。

我们对Executor对象着重讲下,因为该对象是执行sql的实现类: 进入 Executor executor = this.configuration.newExecutor(tx, execType)方法

        public Executor newExecutor (Transaction transaction, ExecutorType executorType){
            executorType = executorType == null ? defaultExecutorType : executorType;
            executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
            Executor executor;
            if (ExecutorType.BATCH == executorType) {
                executor = new BatchExecutor(this, transaction);
            } else if (ExecutorType.REUSE == executorType) {
                executor = new ReuseExecutor(this, transaction);
            } else {
                executor = new SimpleExecutor(this, transaction);
            }
            if (cacheEnabled) {
                executor = new CachingExecutor(executor);
            }
            executor = (Executor) interceptorChain.pluginAll(executor);
            return executor;
        }

可以看出,如果开启cache的话,会创建CachingExecutor,否则创建普通Executor,普通Executor有3个基础类型,BatchExecutor专门用于执行批量sql操作,ReuseExecutor会重用statement执行sql操作,SimpleExecutor只是简单执行sql没有什么特别的。而CachingExecutor在查询数据库前先查找缓存,若没找到的话调用delegate(就是构造时传入的Executor对象)从数据库查询,并将查询结果存入缓存中。 Executor对象是可以被插件拦截的,如果定义了针对Executor类型的插件,最终生成的Executor对象是被各个插件插入后的代理对象。 我们简单的看下其3个基本基础类型中最简单的SimpleExecutor 是怎么执行sql的

public class SimpleExecutor extends BaseExecutor {
    public SimpleExecutor(Configuration configuration, Transaction transaction) {
        super(configuration, transaction);
    }

    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;

        int var6;
        try {
        	//拿到Configuration 属性
            Configuration configuration = ms.getConfiguration();
            //拿到StatementHandler 
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, (ResultHandler)null, (BoundSql)null);
            //拿到prepareStatement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            //prepareStatement执行sql
            var6 = handler.update(stmt);
        } finally {
            this.closeStatement(stmt);
        }

        return var6;
    }

StatementHandler
可以看出,Executor本质上也是个甩手掌柜,具体的事情原来是StatementHandler来完成的。 当Executor将指挥棒交给StatementHandler后,接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。

publicStatementHandler newStatementHandler(Executor executor, MappedStatementmappedStatement,  
        ObjectparameterObject, RowBounds rowBounds, ResultHandler resultHandler) {  
   StatementHandler statementHandler = newRoutingStatementHandler(executor, mappedStatement,parameterObject,rowBounds, resultHandler);  
   statementHandler= (StatementHandler) interceptorChain.pluginAll(statementHandler);  
   returnstatementHandler;  
} 

可以看到每次创建的StatementHandler都是RoutingStatementHandler,它只是一个分发者,他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定,默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的,和Executor一样,被拦截器拦截后的对像是一个代理对象。例如像实现数据库的物理分页,众多物理分页的实现都是在这个地方使用拦截器实现的

看完了Executor具体执行过程,还没结束,我们还不知道在执行前一步,就是代码块前俩部,到底做了什么关联,再一次贴出来:

		.....
		//通过sqlSessionFactory获取sqlSession 
        SqlSession sqlSession = sqlSessionFactory.openSession();
 TbUserMapper userMapper = sqlSession.getMapper(TbUserMapper.class);
          TbUser user = new TbUser("liybk", "liybk","186..","123");
           userMapper.insertUser(user);

那么这个mapper作用到底是什么呢,它是如何创建的呢,它又是怎么与sqlsession等关联起来的呢? 我们进入方法:

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

最终调用的是configuration的mapperRegistry方法

public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
//mapperRegistry
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
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
可以看到,mapper是一个代理对象,它实现的接口就是传入的type,这就是为什么mapper对象可以通过接口直接访问。同时还可以看到,创建mapper代理对象时传入了sqlsession对象,这样就把sqlsession也关联起来了。 我们进入Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy)方法,注意这个方法传入的参数mapperProxy

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class[] interfaces, InvocationHandler h) throws IllegalArgumentException { Objects.requireNonNull(h); //拿到mapper接口类 final Class[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//进行权限检查
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}

    /*
     * Look up or generate the designated proxy class.
     */
     //查找/生成代理类
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
			//获取构造器
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //将mapperProxy参数转为InvocationHandler 传入
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

mapperProxy是InvocationHandler的子类,再进入cons.newInstance(new Object[]{h})方法

public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}

结果是将mapperProxy作为构造参数返回了一个代理实现类,我们再看下mapperProxy这个类的主要方法 invoke 我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来,MapperProxy的invoke如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

        if (this.isDefaultMethod(method)) {
            return this.invokeDefaultMethod(proxy, method, args);
        }
    } catch (Throwable var5) {
        throw ExceptionUtil.unwrapThrowable(var5);
    }

    MapperMethod mapperMethod = this.cachedMapperMethod(method);
    return mapperMethod.execute(this.sqlSession, args);
}

可以看到invoke把执行权转交给了MapperMethod,我们来看看MapperMethod里又是怎么运作的:

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

可以看到,MapperMethod就像是一个分发者,他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。

作者:Esofar

出处:https://www.cnblogs.com/DCFV/p/18402665

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Duancf  阅读(27)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示