【源码解析】Mybatis执行原理

Mybatis执行原理

MyBatis 是一款优秀的持久层框架,MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

Mybatis中的mapper接口并没有具体实现,那么他是如何执行SQL的?

mybatis-config.xml配置

xml 复制代码
<!-- mybatis-config.xml -->
<?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="development">
    <environment id="development">
      <!-- 使用jdbc事务管理 -->
      <transactionManager type="JDBC" />
      <!-- 数据库连接池 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url"
          value="jdbc:mysql://127.0.0.1:3306/demo?characterEncoding=utf8"/>
        <property name="username" value="" />
        <property name="password" value="" />
      </dataSource>
    </environment>
  </environments>

  <!-- 加载mapper.xml -->
  <mappers>
    <!-- <package name=""> -->
    <mapper resource="mapper/DemoMapper.xml"></mapper>
  </mappers>
</configuration>

从一个简单的mybatis示例开始源码分析:

java 复制代码
public static void main(String[] args) throws Exception {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //创建SqlSessionFacory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获取Mapper
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("id","1");
    System.out.println(mapper.selectAll(map));
    sqlSession.close();
}

1.获取SqlSessionFactory

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

new SqlSessionFactoryBuilder().build()方法得到SqlSessionFactory实例,并解析mybatis-config.xml文件configuration节点数据源配置及mapper配置到Configuration对象,Configuration是SqlSessionFactory的一个属性。

java 复制代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        if (inputStream != null) {
          inputStream.close();
        }
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}

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

XMLConfigBuilder解析配置并组装SQL语句:

java 复制代码
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析mybatis-config.xml文件configuration节点数据源配置及mapper配置到Configuration对象
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 读取mapper配置
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

2.创建SqlSession

SqlSession sqlSession = sqlSessionFactory.openSession();

通过SqlSession工厂类创建SqlSession,并增加事务处理、执行器等。这里将SqlSessionFactory中的Environment传递给了SqlSession,Environment中包含DataSource,用于创建数据库连接。

java 复制代码
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
      boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

3.创建Mapper、执行SQL

DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);

java 复制代码
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

在XMLConfigBuilder.parseConfiguration()中configuration.addMapper(mapperInterface)方法对knownMappers初始化,将所有Mapper的代理类放入knownMappers。

复制代码
knownMappers.put(type, new MapperProxyFactory<>(type));

sqlSession.getMapper()从knownMappers获取代理类对象。

mapper.selectAll(map);

代理类通过MapperProxy.invoke()方法执行,自定义代理类会执行cachedInvoker()方法。

java 复制代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      }
      return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
}

最终通过mapperMethod.execute()方法执行对应的SQL语句。

java 复制代码
private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
        this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
        return mapperMethod.execute(sqlSession, args);
    }
}
相关推荐
独自破碎E5 分钟前
Java是怎么实现跨平台的?
java·开发语言
To Be Clean Coder12 分钟前
【Spring源码】从源码倒看Spring用法(二)
java·后端·spring
雨中飘荡的记忆14 分钟前
MyBatis缓存模块详解
mybatis
xdpcxq102931 分钟前
风控场景下超高并发频次计算服务
java·服务器·网络
想用offer打牌33 分钟前
你真的懂Thread.currentThread().interrupt()吗?
java·后端·架构
橘色的狸花猫44 分钟前
简历与岗位要求相似度分析系统
java·nlp
独自破碎E1 小时前
Leetcode1438绝对值不超过限制的最长连续子数组
java·开发语言·算法
用户91743965391 小时前
Elasticsearch Percolate Query使用优化案例-从2000到500ms
java·大数据·elasticsearch
yaoxin5211231 小时前
279. Java Stream API - Stream 拼接的两种方式:concat() vs flatMap()
java·开发语言
坚持学习前端日记2 小时前
2025年的个人和学习年度总结以及未来期望
java·学习·程序人生·职场和发展·创业创新