(2)一起來看下使用mybatis框架的insert語句的源碼執行流程吧


本文是作者原創,版權歸作者所有.若要轉載,請注明出處.本文以簡單的insert語句為例,只貼我覺得比較重要的源碼,其他不重要非關鍵的就不貼了

 

1.mybatis的底層是jdbc操作,我們先來回顧一下insert語句的執行流程,如下

 

//連接數據庫
    Class.forName("com.mysql.jdbc.Driver");
    String url="jdbc:mysql://localhost/kaikeba?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8";
    String user="root";
    String password="root";
    //建立連接
    Connection conn = DriverManager.getConnection(url, user, password);
    //sql語句
    String sql="INSERT INTO dept(DNAME)VALUES (?)";
    //獲得預處理對象
    PreparedStatement preparedStatement = conn.prepareStatement(sql);
    //將SQL語句占位符位置設置實際參數
    preparedStatement.setString(1, "T"); //為問號賦值
    //增刪改都使用executeUpdate()提交,返回值是int,表示有多少條數據受到了影響
    int i = preparedStatement.executeUpdate();
    System.out.println(i);
    //釋放資源
    preparedStatement.close();
    conn.close();

 

執行完后,我們看下數據庫結果

OK,語句生效了.

 

2.貼一下我用mybatis框架寫的demo,貼一下主要代碼,依次是寫sql的xml,以及該xml所對應的接口,還有個是測試的代碼

 

 

 還有mybatis的配置信息

<configuration>

<!-- 指定properties配置文件, 我這里面配置的是數據庫相關 -->
<properties resource="db.properties"></properties>

<settings>
<!-- 該配置影響的所有映射器中配置的緩存的全局開關。默認值true -->
<setting name="cacheEnabled" value="true"/>
<!--配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句; BATCH 執行器將重用語句並執行批量更新。默認SIMPLE -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>

<!--別名-->
<typeAliases>
<typeAlias type="com.lusaisai.po.DeptInfo" alias="deptInfo"></typeAlias>
</typeAliases>

<!-- 和spring整合后mybatis的 environments配置將廢除 -->
<environments default="development">
<environment id="development">
<!-- 使用jdbc事務管理 -->
<transactionManager type="JDBC" />
<!-- 數據庫連接池 -->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>

<!-- 加載mapper.xml -->
<mappers>
<!-- 第一種方式:通過resource指定 -->
<mapper resource="com/lusaisai/mapper/DemoMapper.xml" ></mapper>
<!--第二種方式,直接指定包,自動掃描-->
<!--<package name="com.lusaisai.mapper.*"/>-->
</mappers>

</configuration>

 

 

 執行完后,我們看下數據庫結果

 

 

 

 

 

 OK,語句生效了.

 

3.現在是開始閱讀源碼環節,我在源碼上加了自己的理解,這就是我上篇文章編譯mybatis源碼的好處,否則就只能干看了

 

從讀取配置文件下面一行代碼開始

 

//就是這個方法,注意后面兩個參數均為空
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //XMLConfigBuilder看這個名字就是對mybatis的配置文件進行解析的類,現在他還是一個初始化對象,沒有開始解析.用的是java的dom解析
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //這個就是讀取方法,parser.parse()返回一個Configuration對象,該對象將存放讀取配置文件的信息
      //build(parser.parse())傳入一個Configuration對象,並使用多態返回SqlSessionFactory接口的實體類DefaultSqlSessionFactory
      //DefaultSqlSessionFactory只有一個屬性,就是Configuration對象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        //這里關閉了讀取流inputStream,我們不用再手動關閉了
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

 

 

一層一層看下去:先內層,返回Configuration對象,這個對象是用來存儲解析xml文件的信息的

 

 

//調用此方法對mybatis配置文件進行解析
  public Configuration parse() {
    //注意:parsed默認為false,配置文件讀取非常消耗資源,因此這里只讀取一次,如果讀取過則將parsed的值改為true,再次讀取的時候就拋一個異常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    //在這里將parsed的值改為true
    parsed = true;
    //從配置文件configuration標簽開始解析,具體解析過程在下面
    parseConfiguration(parser.evalNode("/configuration"));
    //返回Configuration對象用於存儲mybatis配置文件信息
    return configuration;
  }

 

//此方法就是解析configuration節點下的子節點
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first,這里先讀取properties標簽,也就是數據庫配置
      //我們在configuration下面能配置的節點為以下10個節點
      //properties標簽這里一般配置數據源
      propertiesElement(root.evalNode("properties"));
      //settings標簽可以配置緩存,執行器等信息
      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"));//解析jdbc信息
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));//解析mapper
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
//解析settings標簽,返回Properties對象
  private Properties settingsAsProperties(XNode context) {
    //如果沒有settings標簽,給一個默認值
    if (context == null) {
      return new Properties();
    }
    //如果有settings標簽,解析
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

 

 我們可以看到返回的Properties對象就是我們mybatis的配置信息里的值,當然還有很多默認屬性這里就不一一列舉了

 

下面我們看下怎么解析別名的

//解析別名的方法
  private void typeAliasesElement(XNode parent) {
    //解析別名標簽
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //批量解析
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {//一個一個解析
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            //根據type表示的全限定名拿到它的class對象
            Class<?> clazz = Resources.classForName(type);
            //注冊別名,分兩種情況,一種別名alias為空,一種不為空
            if (alias == null) {//別名alias為空
              typeAliasRegistry.registerAlias(clazz);
            } else {//別名alias不為空
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
//Alias為空的注冊別名方法
  public void registerAlias(Class<?> type) {
    String alias = type.getSimpleName();
    //看下注解上面有沒有
    Alias aliasAnnotation = type.getAnnotation(Alias.class);
    //如果有注解,解析注解
    if (aliasAnnotation != null) {
      alias = aliasAnnotation.value();
    }
    //否則,解析xml
    registerAlias(alias, type);
  }

  //這就是注冊別名的本質方法, 其實就是向保存別名的hashMap新增值而已
  public void registerAlias(String alias, Class<?> value) {
    if (alias == null) {
      throw new TypeException("The parameter alias cannot be null");
    }
    // issue #748
    //別名統一處理,都轉化為小寫,所以別名不區分大小寫
    String key = alias.toLowerCase(Locale.ENGLISH);
    if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
      throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
    }
    //將別名存進map
    typeAliases.put(key, value);
  }
//原來別名就通過一個HashMap來實現, key為別名, value就是別名對應的類型(class對象)
  private final Map<String, Class<?>> typeAliases = new HashMap<>();

  //別名在這這個構造方法做默認的處理,以下就是mybatis默認為我們注冊的預制別名
  public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

我們再看下如何解析mappers標簽

 

//解析com/lusaisai/mapper/DemoMapper.xml 方法
  private void configurationElement(XNode context) {
    try {
      //解析命名空間
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));//解析緩存標簽
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));//解析resultMap標簽
      sqlElement(context.evalNodes("/mapper/sql"));//解析sql標簽
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//這里四種標簽一起解析,所以效果一樣
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

 我們再看下mybatis是怎么解析sql的,貼一下重要代碼

 

 可以看到originalSql 就是我們自己寫的代碼,在這個方法里將#{和}里的參數替換為占位符並返回

public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
    //INSERT INTO dept(DNAME) VALUES (#{dName})  將#{和}里的參數替換為占位符?
    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  //這里點進去 String sql
= parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }

 

這個就是解析sql的方法,主要方式是截取我們寫的sql語句,將#{}的地方循環截取掉,將里面的參數改成?
 
         
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
int offset = 0;
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
//這里就是將#{}里的參數 替換為?的方法
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
 
 
        
點進去看下,找到了,就是這里將#{}里的參數替換為占位符?
@Override
    public String handleToken(String content) {
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }

 

 

 

最后封裝到Configuration對象的mappedStatements屬性的sqlsource里面

 

 到此為止,我們已經把配置文件中的所有配置封裝到Configuration對象中.

 

我們看下解析完的Configuration對象都有哪些屬性,首先是jdbc配置參數

 

 

然后是這個,混個眼熟,name的值是SIMPLE

然后是這個,剛剛我們看到sql相關

,

 

點開看一下

 

 

 我們可以看到有sql的xml信息和接口信息.

好,回到這里,繼續下一層

 

 

 點進去

好了,這里我們知道其實返回了DefaultSqlSessionFactory對象.繼續看下去

 

 

 

 這里幫我們關閉了讀取流inputStream,我們不用再手動關閉了,繼續看下去

 

 

返回的DefaultSqlSessionFactory就是SqlSessionFactory接口的實現類,
這個類只有一個屬性,就是Configuration對象,Configuration對象用來存放讀取xml配置的信息
繼續下一行,點進去

 

 
        

 剛剛我們看到過這個屬性,不知道干啥的,很眼熟,name的值是SIMPLE,繼續往下看

 

 

 看下tx這個對象,其他的不仔細看了

 

 

 (猜測:有個屬性autoCommit默認是false,不自動提交,這里就對應了上面,我們手動提交事務了,繼續往下看DefaultSqlSession)

 

 

 我們發現和上文一樣,也是SqlSession接口的實現類

 

現在,可以走出去了

 

 

 


返回的DefaultSqlSession是SqlSession接口的實現類,它應該是與數據庫打交道的對象,
對象中有上面傳來的Configuration對象,還有executor處理器對象,executor處理器有一個dirty屬性,默認為false,如下圖

 

 
        

 現在進入sqlSession的insert方法看下

 

 

 繼續點進去看下

 

 

 可以看到,我們拿出了上面提到的sql相關的信息,我們看下ms有哪些屬性

 

 

ms對象有兩個重要屬性:sqlSource是用來存sql語句的,id是DemoMapper接口的.insertDept方法名
繼續往下看,下面那個方法應該就是執行的方法了,一層一層點進去

 

 
        

 這里判斷參數是什么類型,我們是string類型,直接原樣返回,其他集合類型,數組類型有是專門的處理,繼續點下去

 

 我們點進那個執行方法

 

 繼續點進那個執行方法

 

我們看下handler對象,它是Statement對象的處理器,看看它有哪些屬性,如下

 

 

 我們可以看到,里面有一條完整的可執行語句,我猜下面就是jdbc操作了,點進去看下

 

 

 真是失望,看源碼就是封裝的太多了,看着看着就暈了,繼續點進去看

 

 

 

 終於看到PreparedStatement預編譯對象了,

我們可以看下返回rows那行代碼,點進去發現如下

 

執行了ClientPreparedStatement對象的方法,這個對象好像沒見過

別着急,我們在idea內點擊右鍵,選擇 Diagrams,查看類的繼承關系圖

 

 

 好了,我們知道這個對象本質也是PreparedStatement對象,在那個方法里返回了受影響的行數,其他的目前不必深究

 

 

 到這里我們就要結束了,最后看下這個commit方法吧,點進去

 注意,這里有個dirty屬性為true,我猜是臟數據,不會寫入數據庫,我們讓他繼續執行

 

 

 

 變成false了,哈哈,現在不是臟數據了,肯定可以寫入數據庫了.最后看下數據庫

 

 

 ,這些數據是我debug好幾次寫入的數據,不管它們,我們把commit方法注釋掉,再執行一次

 

我們把注釋放開,繼續執行一次

 

所以insert語句要commit,手動執行提交事件,將數據提交到數據庫中,才真正成功插入到數據庫中,否則會回滾,數據庫看不到數據
好了,這篇文章到這里就結束了.謝謝大家的觀看,大家有空的話就幫我點個贊吧
文章中若有錯誤和疏漏之處,還請各位大佬不吝指出
若要下載我的源代碼和注釋,這里是我的GitHub地址:https://github.com/lusaisai1019/mybatis-master



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM