mybatis的原理详解

11 篇文章 0 订阅

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>
<!--    配置 mybatis的环境-->
    <environments default="development">
<!--        配置环境-->
        <environment id="development">
<!--            配置事物类型-->
            <transactionManager type="JDBC"></transactionManager>
<!--            配置连接数据库的信息:用的是数据源[连接池]-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--                jdbc:mysql://localhost:3306/db_school?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC-->
<!--                和javaWeb servlet三层架构中的区别这里是只需要设置时区就可以了-->
                <property name="url" value="jdbc:mysql://localhost:3306/db_school?serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="h123456"/>
            </dataSource>
        </environment>
    </environments>
<!--    注册StudentDao接口映射文件位置-->
    <mappers>
        <mapper resource="mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

核心配置文件参数详解:

environment标签中的id属性必须和enviroments标签中的default属性一致。

事务管理器:

第一种采用JDBC事务类型,直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。

第二种采用MANAGEd事务类型,它从提交或者回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为false来阻止默认的关闭行为。例如:

< transactionManager type=“MANAGED”>

​ < property name=“closeConnection” value=“false”/>

</ transactionManager>

数据源(dataSource)

dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。大多数 MyBatis应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是 type =“[UNPOOLED|POOLED|JNDI]”)

UNPOOLED- 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED- 这种数源的实现利用"池"的概念将JDBC连接对象组织起来,避免了创建新建的连接实列时所必须的初始化和认证时间。这种处理方式很流行,能使并发web应用快速响应请求。

JNDI - 这个数据源实现是为了能在入EJB 或者应用服务器这类容器中使用的,容器可以集中或外部配置数据源,然而放置一个JNDI上下文的数据源引用。

mapper 中的resource属性用于指定映射文件的位置。mapper中有多个属性可以描述映射文件,分别为:

resource=“mapper/StudentMapper.xml"文件在多级目录中要用分隔符”/"隔开。

class=“com.etime.dao.StudentDao” 指定StudentDao接口文件位置,但此时StudentDao.xml必须和接口处在同一个包中并且文件名要相同。

映射文件:

StudentMapper.xml文件:

<?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">

<!--namespace是当前mapper对应的dao接口-->
<mapper namespace="com.etime.dao.StudentDao">
<!--    select指定当前进行数据库操作的是查询-->
<!--    id的值对应的是当前dao层接口中的方法名字-->
<!--    resultType指定当前查询得到的数据要封装成的类型-->
    <select id="getAllStudents" resultType="com.etime.pojo.Student">
        select  * from student
    </select>
</mapper>

映射文件参数详解

namespacce必须为接口的完全限定名(即包名+类名的格式)

select标签中的id必须和接口中声明的方法同名

如果接口中方法有返回值,resultType必须跟方法返回值一致并采用返回值的完全限定名来表示。

底层源码分析

在这里插入图片描述

  • 利用Resources的getResourceAsStream方法读取mybatis核心配置文件,该配置文件中注册数据源[dataSource]和映射的文件的位置[mappers标签中的mapper子标签的resource属性]
inputStream in = Resources.getResourceAsStream("config.xml");
  • 展开映射文件StudentMapper.xml中定义了子查询方法、查询时所封装 结果类型和所要执行的sql语句。
< select id = "getAllStudents" resultType="com.etime.pojo.Student">

​		select * from student

</ select>
  • 使用构建者模式SqlSessionFactoryBuilder类的builder方法创建SqlSessionFactory对象,在build方法中从字节输入流中解析数据
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(in);
SqlSessionFactoryBuilder.java的build方法:
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;
}
XMLConfigBuilder的parse方法:
public Configuration parse() {
  if (this.parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  } else {
      this.parsed = true;
      this.parseConfiguration(this.parser.evalNode("/configuration"));
      return this.configuration;
  }
}    

XMLConfigBuilder的parseConfiguration方法:
 private void parseConfiguration(XNode root) {
  try {
      this.propertiesElement(root.evalNode("properties"));
      Properties settings = this.settingsAsProperties(root.evalNode("settings"));
      this.loadCustomVfs(settings);
      this.loadCustomLogImpl(settings);
      this.typeAliasesElement(root.evalNode("typeAliases"));
      this.pluginElement(root.evalNode("plugins"));
      this.objectFactoryElement(root.evalNode("objectFactory"));
      this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
      this.settingsElement(settings);
      this.environmentsElement(root.evalNode("environments"));
      this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      this.typeHandlerElement(root.evalNode("typeHandlers"));
      this.mapperElement(root.evalNode("mappers"));
  } catch (Exception var3) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
  }
} 

XMLConfigBuilder的mapperElement方法:    
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
      Iterator var2 = parent.getChildren().iterator();

      while(true) {
          while(var2.hasNext()) {
              XNode child = (XNode)var2.next();
              String resource;
              if ("package".equals(child.getName())) {
                  resource = child.getStringAttribute("name");
                  this.configuration.addMappers(resource);
              } else {
                  resource = child.getStringAttribute("resource");
                  String url = child.getStringAttribute("url");
                  String mapperClass = child.getStringAttribute("class");
                  XMLMapperBuilder mapperParser;
                  InputStream inputStream;
                  if (resource != null && url == null && mapperClass == null) {
                      ErrorContext.instance().resource(resource);
                      inputStream = Resources.getResourceAsStream(resource);
                      mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                      mapperParser.parse();
                  } else if (resource == null && url != null && mapperClass == null) {
                      ErrorContext.instance().resource(url);
                      inputStream = Resources.getUrlAsStream(url);
                      mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                      mapperParser.parse();
                  } else {
                      if (resource != null || url != null || mapperClass == null) {
                          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                      }

                      Class<?> mapperInterface = Resources.classForName(mapperClass);
                      this.configuration.addMapper(mapperInterface);
                  }
              }
          }

          return;
      }    

上述代码中我们可以看到创建了一个XMLConfigBuilder对象用来解析XML,把配置文件中所有标签及标签中属性值放封装Configuration对象中并返回SqlSessionFactory对象。

  • 调用SqlSessionFactory对象的openSession方法返回一个SqlSession对象,SqlSessionFactory是一个接口,我们找到它的实现类DefaultSqlSessionFactory类,点击它的openSession方法,打开openSessionFromDataSource方法。
SqlSession sqlSession = sqlSessionFactory.openSession();

DefaultSqlSessionFactory.java的openSessionFromDataSource方法:
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);
      //创建DefaultSqlSession对象,即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;
}

  • 调用sqlSession.getMapper(StudentDao.class)方法返回一个StudentDao接口的代理类对象。
DefaultSqlSessionFactory.java的getMapper方法: 
public <T> T getMapper(Class<T> type) {
  return this.configuration.getMapper(type, this);
}

Configuration.java的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return this.mapperRegistry.getMapper(type, sqlSession);
}

MapperRegistry.java的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  MapperProxyFactory<T> 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.java的newInstance方法:            
public T newInstance(SqlSession sqlSession) {
  MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
  return this.newInstance(mapperProxy);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@湖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值