美文网首页
Mybatis源码解析

Mybatis源码解析

作者: 我是嘻哈大哥 | 来源:发表于2019-06-24 16:19 被阅读0次

    1.编写一个小demo

    导入mybatis源码,demo目录结构如下:


    源码的pom文件中需要加入以下依赖:

          <dependency>
              <groupId>mysql</groupId>
              <artifactId>mysql-connector-java</artifactId>
              <version>5.1.38</version>
          </dependency>
    

    UserMapper.java

    public interface UserMapper {
       User selectById(String id);
    }
    

    User.java

    public class User {
      private String id;
      private String name;
    
      public User(String id, String name) {
        this.id = id;
        this.name = name;
      }
    
      public String getId() {
        return id;
      }
    
      public void setId(String id) {
        this.id = id;
      }
    
      public String getName() {
        return name;
      }
    
      public void setName(String name) {
        this.name = name;
      }
    
      @Override
      public String toString() {
        return "User{" +
          "id='" + id + '\'' +
          ", name='" + name + '\'' +
          '}';
      }
    }
    

    UserMapper.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 namespace="demo.mapper.UserMapper"> //namespace指定映射的接口文件
    
        <!--resultMap标签:映射实体与表,非主键属性标签 type属性:实体全路径名,可在mybatis.xml文件中统一配置 id属性:为实体与表的映射取一个唯一的编号-->
        <resultMap type="user" id="userMap">
            <!--id标签:映射主键属性 result标签:映射非主键属性 property属性:实体属性名 column属性:表的字段名-->
            <id property="id" column="id"/>
            <result property="name" column="name"/>
        </resultMap>
        
        <select id="selectById" resultMap="userMap"  parameterType="demo.pojo.User">
            SELECT * FROM USER WHERE id=#{id};
        </select>
    
    </mapper>
    

    db.properties

    mysql.driver=com.mysql.jdbc.Driver
    mysql.url=jdbc:mysql://localhost:3306/mybatisdb
    mysql.username=root
    mysql.password=12345678
    

    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>
    
        <properties resource="db.properties"></properties> //定义属性文件
    
        <!-- 设置实体类的类型别名 -->
        <typeAliases>
            <typeAlias type="demo.pojo.User" alias="user"/>
        </typeAliases>
    
        <!-- 设置一个默认的连接环境信息,支持多数据源 -->
        <environments default="mysql_env">
            <!-- 连接环境信息,取一个唯一的编号 -->
            <environment id="mysql_env">
                <!-- mybatis使用的jdbc事务管理方式 -->
                <transactionManager type="jdbc">
                </transactionManager>
                <!-- mybatis使用连接池方式来获取链接 -->
                <dataSource type="pooled">
                    <!-- 配置与数据库交互的四个属性 -->
                    <property name="driver" value="${mysql.driver}"/>
                    <property name="url" value="${mysql.url}"/>
                    <property name="username" value="${mysql.username}"/>
                    <property name="password" value="${mysql.password}"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
                <mapper resource="sqlMapper/UserMapper.xml"/>  //定义映射XML文件
        </mappers>
    </configuration>
    

    MybatisDemo.java

    public class MybatisDemo {
    
      SqlSessionFactory sqlSessionFactory;
    
      @Before
      public void init() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        inputStream.close();
      }
    
    
      @Test
      public void  testAutoMapping(){
        SqlSession sqlSession=sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.selectById("1");
        System.out.println(user);
      }
    }
    

    运行结果为:

    User{id='1', name='xiaoming'}
    

    2.源码的分析

    主要分三个阶段:
    (1)初始化阶段
    主要是对mybatis-config.xml配置文件以及UserMapper的解析,代码跟踪如下:

     sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
     --> return build(parser.parse());
         --> parseConfiguration(parser.evalNode("/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"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    跟踪一下 environmentsElement(root.evalNode("environments"));

    environmentsElement(root.evalNode("environments"));
    -->TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); //构建 transaction
    -->DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); //构建dataSource
    -->configuration.setEnvironment(environmentBuilder.build()); //设置环境变量
    
    

    跟踪一下 mapperElement(root.evalNode("mappers"));

    mapperElement(root.evalNode("mappers"));
    --> XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
    --> mapperParser.parse(); //解析mappers标签下的文件
      --> configurationElement(parser.evalNode("/mapper"));
        --> buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
           --> buildStatementFromContext(list, null);
              --> statementParser.parseStatementNode();
                -->builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
                   --> configuration.addMappedStatement(statement);
    

    可以看出,所有的相关的额配置信息都存储到了Configuration这个类的实例里

    (2)代理阶段

    主要是这两句代码

        SqlSession sqlSession=sqlSessionFactory.openSession(); //定义sqlSession
        UserMapper mapper = sqlSession.getMapper(UserMapper.class); //关键语句
    

    SqlSession中主要是定义操作数据库的一些方法,如CRUD,重点看一下getMapper方法:

    UserMapper mapper = sqlSession.getMapper(UserMapper.class); 
    --> return configuration.getMapper(type, this);
       --> return mapperRegistry.getMapper(type, sqlSession);
         -->  return mapperProxyFactory.newInstance(sqlSession); //从Mapper代理工厂创建mapper
            --> final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);//定义了一个Mapper的动态代理,调用Mapper接口时会调用里面的的invoke方法
              --> return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);//返回代理创建的对象
    

    这里很关键,完成了mapper接口与实际操作sql语句的映射关系。

    (3)数据读取阶段
    执行User user = mapper.selectById("1")时,会调用MapperProxy类下的invoke方法,过程如下:

     return mapperMethod.execute(sqlSession, args);
     -->switch (command.getType())//根据command字段类型执行相应的操作
       --> result = sqlSession.selectOne(command.getName(), param);//执行sqlSession的selectOne方法
         -->List<T> list = this.selectList(statement, parameter);
            --> return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
               --> return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
                  --> list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//从数据库查询
                     --> list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
                        -->stmt = prepareStatement(handler, ms.getStatementLog());
                           --> Connection connection = getConnection(statementLog); //这里执行的jdbc的操作
                           --> stmt = handler.prepare(connection, transaction.getTimeout());//这里执行的jdbc的操作
                              -->return handler.queryCursor(stmt); //返回游标
                                 -->String sql = boundSql.getSql();//获取sql
                                 -->statement.execute(sql);//执行sql
                     
    

    3.手写一个简单的mybatis框架

    可参考以下链接
    https://github.com/xiaohongstudent/mybatis_myself

    相关文章

      网友评论

          本文标题:Mybatis源码解析

          本文链接:https://www.haomeiwen.com/subject/ujeoqctx.html