本文是作者原創,版權歸作者所有.若要轉載,請注明出處.本文以簡單的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