1.MyBatis一般使用步驟
1.1獲取Configuration實例或編寫配置文件
//獲取Configuration實例的樣例 TransactionFactory transactionFactory = new JdbcTransactionFactory();//定義事務工廠 Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class);
配置文件的編寫請看2
1.2生成SqlSessionFactory實例(一個數據庫對應一個SqlSessionFactory)
//通過Configuration實例生成SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); //通過xml配置文件方式生成SqlSessionFactory String resource = "org/mybatis/example/Configuration.xml"; Reader reader = Resources.getResourceAsReader(resource); sqlSessionFactory= new SqlSessionFactoryBuilder().build(reader);
1.3生成SqlSession實例
//樣例 SqlSession session = sqlSessionFactory.openSession();
1.4執行sql各種操作
//樣例 try { Blog blog = (Blog) session.selectOne( "org.mybatis.example.BlogMapper.selectBlog", 101); } finally { session.close(); }
2.MyBatis的配置文件解析
2.1配置文件的基本結構
- configuration —— 根元素
- properties —— 定義配置外在化
- settings —— 一些全局性的配置
- typeAliases —— 為一些類定義別名
- typeHandlers —— 定義類型處理,也就是定義java類型與數據庫中的數據類型之間的轉換關系
- objectFactory
- plugins —— Mybatis的插件,插件可以修改Mybatis內部的運行規則
- environments —— 配置Mybatis的環境
- environment
- transactionManager —— 事務管理器
- dataSource —— 數據源
- environment
- databaseIdProvider
- mappers —— 指定映射文件或映射類
2.2 properties元素——定義配置外在化
<!--樣例--> <properties resource="org/mybatis/example/config.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties>
配置外在化的屬性還可以通過SqlSessionFactoryBuilder.build()方法提供,如:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
配置外在化的優先級是 build方法->resource屬性指定的文件->property元素
2.3 settings元素——Mybatis的一些全局配置屬性
設置參數 | 描述 | 有效值 | 默認值 |
---|---|---|---|
cacheEnabled | 這個配置使全局的映射器啟用或禁用 緩存。 | true | false | true |
lazyLoadingEnabled | 全局啟用或禁用延遲加載。當禁用時, 所有關聯對象都會即時加載。 | true | false | true |
aggressiveLazyLoading | 當啟用時, 有延遲加載屬性的對象在被 調用時將會完全加載任意屬性。否則, 每種屬性將會按需要加載。 | true | false | true |
multipleResultSetsEnabled | 允許或不允許多種結果集從一個單獨 的語句中返回(需要適合的驅動) | true | false | true |
useColumnLabel | 使用列標簽代替列名。 不同的驅動在這 方便表現不同。 參考驅動文檔或充分測 試兩種方法來決定所使用的驅動。 | true | false | true |
useGeneratedKeys | 允許 JDBC 支持生成的鍵。 需要適合的 驅動。 如果設置為 true 則這個設置強制 生成的鍵被使用, 盡管一些驅動拒絕兼 容但仍然有效(比如 Derby) | true | false | False |
autoMappingBehavior | 指定 MyBatis 如何自動映射列到字段/ 屬性。PARTIAL 只會自動映射簡單, 沒有嵌套的結果。FULL 會自動映射任 意復雜的結果(嵌套的或其他情況) 。 | NONE, PARTIAL, FULL | PARTIAL |
defaultExecutorType | 配置默認的執行器。SIMPLE 執行器沒 有什么特別之處。REUSE 執行器重用 預處理語句。BATCH 執行器重用語句 和批量更新 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 設置超時時間, 它決定驅動等待一個數 據庫響應的時間。 | Any positive integer | Not Set (null) |
safeRowBoundsEnabled | Allows using RowBounds on nested statements. | true | false | False |
mapUnderscoreToCamelCase | Enables automatic mapping from classic database column names A_COLUMN to camel case classic Java property names aColumn. | true | false | False |
localCacheScope | MyBatis uses local cache to prevent circular references and speed up repeated nested queries. By default (SESSION) all queries executed during a session are cached. If localCacheScope=STATEMENT local session will be used just for statement execution, no data will be shared between two different calls to the same SqlSession. | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | Specifies the JDBC type for null values when no specific JDBC type was provided for the parameter. Some drivers require specifying the column JDBC type but others work with generic values like NULL, VARCHAR or OTHER. | JdbcType enumeration. Most common are: NULL, VARCHAR and OTHER | OTHER |
lazyLoadTriggerMethods | Specifies which Object's methods trigger a lazy load | A method name list separated by commas | equals,clone,hashCode,toString |
2.4 typeAliases元素——定義類別名,簡化xml文件的配置,如:
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> </typeAliases>
Mybatis還內置了一些類型別名:
別名 | 映射的類型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
2.5 typeHandlers元素
每當MyBatis 設置參數到PreparedStatement 或者從ResultSet 結果集中取得值時,就會使用TypeHandler 來處理數據庫類型與java 類型之間轉換。
2.5.1 自定義typeHandlers
- 實現TypeHandler接口
View Code
- 在配置文件中聲明自定義的TypeHandler
View Code
2.5.2 Mybatis內置的TypeHandler
類型處理器 | Java 類型 | JDBC 類型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean, boolean | 任何兼容的布爾值 |
ByteTypeHandler | java.lang.Byte, byte | 任何兼容的數字或字節類型 |
ShortTypeHandler | java.lang.Short, short | 任何兼容的數字或短整型 |
IntegerTypeHandler | java.lang.Integer, int | 任何兼容的數字和整型 |
LongTypeHandler | java.lang.Long, long | 任何兼容的數字或長整型 |
FloatTypeHandler | java.lang.Float, float | 任何兼容的數字或單精度浮點型 |
DoubleTypeHandler | java.lang.Double, double | 任何兼容的數字或雙精度浮點型 |
BigDecimalTypeHandler | java.math.BigDecimal | 任何兼容的數字或十進制小數類型 |
StringTypeHandler | java.lang.String | CHAR 和 VARCHAR 類型 |
ClobTypeHandler | java.lang.String | CLOB 和 LONGVARCHAR 類型 |
NStringTypeHandler | java.lang.String | NVARCHAR 和 NCHAR 類型 |
NClobTypeHandler | java.lang.String | NCLOB 類型 |
ByteArrayTypeHandler | byte[] | 任何兼容的字節流類型 |
BlobTypeHandler | byte[] | BLOB 和 LONGVARBINARY 類型 |
DateTypeHandler | java.util.Date | TIMESTAMP 類型 |
DateOnlyTypeHandler | java.util.Date | DATE 類型 |
TimeOnlyTypeHandler | java.util.Date | TIME 類型 |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP 類型 |
SqlDateTypeHandler | java.sql.Date | DATE 類型 |
SqlTimeTypeHandler | java.sql.Time | TIME 類型 |
ObjectTypeHandler | Any | 其他或未指定類型 |
EnumTypeHandler | Enumeration Type | VARCHAR-任何兼容的字符串類型, 作為代碼存儲(而不是索引) |
EnumOrdinalTypeHandler | Enumeration Type | Any compatible NUMERIC or DOUBLE, as the position is stored (not the code itself). |
2.6 objectFactory元素——用於指定結果集對象的實例是如何創建的。
下面演示了如何自定義ObjectFactory
1.繼承DefaultObjectFactory


// ExampleObjectFactory.java public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(
2.在配置文件中配置自定義的ObjectFactory


// MapperConfig.xml <objectFactory type="org.mybatis.example.ExampleObjectFactory"> <property name="someProperty" value="100"/> </objectFactory>
2.7 plugins元素
MyBatis 允許您在映射語句執行的某些點攔截方法調用。默認情況下,MyBatis 允許插件(plugins)攔截下面的方法:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
下面是自定義plugin示例:
- 實現Interceptor接口,並用注解聲明要攔截的方法
// ExamplePlugin.java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { return invocation.proceed(); } public Object plugin(Object target) { return Plugin.wrap(target, this); } public void setProperties(Properties properties) { } }
- 在配置文件中聲明插件
View Code
2.8 Environments元素
可以配置多個運行環境,但是每個SqlSessionFactory 實例只能選擇一個運行環境。
2.8.1 Environments配置示例:


<environments default="development"> <environment id="development"> <transactionManager type="JDBC"> <property name="..." value="..."/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments>
2.8.2 transactionManager事務管理器
MyBatis 有兩種事務管理類型(即type=”[JDBC|MANAGED]”):
- JDBC – 這個配置直接簡單使用了 JDBC 的提交和回滾設置。 它依賴於從數據源得 到的連接來管理事務范圍。
- MANAGED – 這個配置幾乎沒做什么。它從來不提交或回滾一個連接。而它會讓 容器來管理事務的整個生命周期(比如 Spring 或 JEE 應用服務器的上下文) 默認 情況下它會關閉連接。 然而一些容器並不希望這樣, 因此如果你需要從連接中停止 它,將 closeConnection 屬性設置為 false。例如:
<transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager>
自定義事務管理器:
- 實現TranscactionFactory,它的接口定義如下:
View Code
- 實現TransactionFactory,它的接口定義如下:
public interface Transaction { Connection getConnection(); void commit() throws SQLException; void rollback() throws SQLException; void close() throws SQLException; }
2.8.3 dataSource數據源
dataSource 元素使用標准的JDBC 數據源接口來配置JDBC 連接對象源。
MyBatis 內置了三種數據源類型:
UNPOOLED – 這個數據源的實現是每次被請求時簡單打開和關閉連接。它有一點慢, 這是對簡單應用程序的一個很好的選擇, 因為它不需要及時的可用連接。 不同的數據庫對這 個的表現也是不一樣的, 所以對某些數據庫來說配置數據源並不重要, 這個配置也是閑置的。 UNPOOLED 類型的數據源僅僅用來配置以下 5 種屬性:
- driver – 這是 JDBC 驅動的 Java 類的完全限定名(如果你的驅動包含,它也不是 數據源類)。
- url – 這是數據庫的 JDBC URL 地址。
- username – 登錄數據庫的用戶名。
- password – 登錄數據庫的密碼。
- defaultTransactionIsolationLevel – 默認的連接事務隔離級別。
作為可選項,你可以傳遞數據庫驅動的屬性。要這樣做,屬性的前綴是以“driver.”開 頭的,例如:
- driver.encoding=UTF8
這 樣 就 會 傳 遞 以 值 “ UTF8 ” 來 傳 遞 屬 性 “ encoding ”, 它 是 通 過 DriverManager.getConnection(url,driverProperties)方法傳遞給數據庫驅動。
POOLED – 這是 JDBC 連接對象的數據源連接池的實現,用來避免創建新的連接實例 時必要的初始連接和認證時間。這是一種當前 Web 應用程序用來快速響應請求很流行的方 法。
除了上述(UNPOOLED)的屬性之外,還有很多屬性可以用來配置 POOLED 數據源:
- poolMaximumActiveConnections – 在任意時間存在的活動(也就是正在使用)連 接的數量。默認值:10
- poolMaximumIdleConnections – 任意時間存在的空閑連接數。
- poolMaximumCheckoutTime – 在被強制返回之前,池中連接被檢查的時間。默認 值:20000 毫秒(也就是 20 秒)
- poolTimeToWait – 這是給連接池一個打印日志狀態機會的低層次設置,還有重新 嘗試獲得連接, 這些情況下往往需要很長時間 為了避免連接池沒有配置時靜默失 敗)。默認值:20000 毫秒(也就是 20 秒)
- poolPingQuery – 發送到數據的偵測查詢,用來驗證連接是否正常工作,並且准備 接受請求。默認是“NO PING QUERY SET” ,這會引起許多數據庫驅動連接由一 個錯誤信息而導致失敗。
- poolPingEnabled – 這是開啟或禁用偵測查詢。如果開啟,你必須用一個合法的 SQL 語句(最好是很快速的)設置 poolPingQuery 屬性。默認值:false。
- poolPingConnectionsNotUsedFor – 這是用來配置 poolPingQuery 多次時間被用一次。 這可以被設置匹配標准的數據庫連接超時時間, 來避免不必要的偵測。 默認值: 0(也就是所有連接每一時刻都被偵測-但僅僅當 poolPingEnabled 為 true 時適用)。
JNDI – 這個數據源的實現是為了使用如 Spring 或應用服務器這類的容器, 容器可以集 中或在外部配置數據源,然后放置一個 JNDI 上下文的引用。這個數據源配置只需要兩個屬 性:
- initial_context – 這 個 屬 性 用 來 從 初 始 上 下 文 中 尋 找 環 境 ( 也 就 是 initialContext.lookup(initial——context) 。這是個可選屬性,如果被忽略,那么 data_source 屬性將會直接以 initialContext 為背景再次尋找。
- data_source – 這是引用數據源實例位置的上下文的路徑。它會以由 initial_context 查詢返回的環境為背景來查找,如果 initial_context 沒有返回結果時,直接以初始 上下文為環境來查找。
和其他數據源配置相似, 它也可以通過名為 “env.” 的前綴直接向初始上下文發送屬性。 比如:
- env.encoding=UTF8
在初始化之后,這就會以值“UTF8”向初始上下文的構造方法傳遞名為“encoding” 的屬性。
2.9 mappers元素
既然 MyBatis 的行為已經由上述元素配置完了,我們現在就要定義 SQL 映射語句了。 但是, 首先我們需要告訴 MyBatis 到哪里去找到這些語句。 Java 在這方面沒有提供一個很好 的方法, 所以最佳的方式是告訴 MyBatis 到哪里去找映射文件。 你可以使用相對於類路徑的 資源引用,或者字符表示,或 url 引用的完全限定名(包括 file:///URLs) 。例如:
<!-- Using classpath relative resources --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
<!-- Using url fully qualified paths --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers>
<!-- Using mapper interface classes --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
<!-- Register all interfaces in a package as mappers --> <mappers> <package name="org.mybatis.builder"/> </mappers>
http://www.cnblogs.com/sin90lzc/archive/2012/06/30/2570847.html
廢話不多說,直接進入文章。
我們在使用mybatis的時候,會在xml中編寫sql語句。
比如這段動態sql代碼:
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and name != ''"> name = #{name} </if> <if test="age != null and age != ''"> , age = #{age} </if> <if test="birthday != null and birthday != ''"> , birthday = #{birthday} </if> </trim> where id = ${id} </update>
mybatis底層是如何構造這段sql的?
這方面的知識網上資料不多,於是就寫了這么一篇文章。
下面帶着這個疑問,我們一步一步分析。
介紹MyBatis中一些關於動態SQL的接口和類
SqlNode接口,簡單理解就是xml中的每個標簽,比如上述sql的update,trim,if標簽:
public interface SqlNode { boolean apply(DynamicContext context); }
SqlSource Sql源接口,代表從xml文件或注解映射的sql內容,主要就是用於創建BoundSql,有實現類DynamicSqlSource(動態Sql源),StaticSqlSource(靜態Sql源)等:
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
BoundSql類,封裝mybatis最終產生sql的類,包括sql語句,參數,參數源數據等參數:
XNode,一個Dom API中的Node接口的擴展類。
BaseBuilder接口及其實現類(屬性,方法省略了,大家有興趣的自己看),這些Builder的作用就是用於構造sql:
下面我們簡單分析下其中4個Builder:
1 XMLConfigBuilder
解析mybatis中configLocation屬性中的全局xml文件,內部會使用XMLMapperBuilder解析各個xml文件。
2 XMLMapperBuilder
遍歷mybatis中mapperLocations屬性中的xml文件中每個節點的Builder,比如user.xml,內部會使用XMLStatementBuilder處理xml中的每個節點。
3 XMLStatementBuilder
解析xml文件中各個節點,比如select,insert,update,delete節點,內部會使用XMLScriptBuilder處理節點的sql部分,遍歷產生的數據會丟到Configuration的mappedStatements中。
4 XMLScriptBuilder
解析xml中各個節點sql部分的Builder。
LanguageDriver接口及其實現類(屬性,方法省略了,大家有興趣的自己看),該接口主要的作用就是構造sql:
簡單分析下XMLLanguageDriver(處理xml中的sql,RawLanguageDriver處理靜態sql):
XMLLanguageDriver內部會使用XMLScriptBuilder解析xml中的sql部分。
ok, 大部分比較重要的類我們都已經介紹了,下面源碼分析走起。
源碼分析走起
Spring與Mybatis整合的時候需要配置SqlSessionFactoryBean,該配置會加入數據源和mybatis xml配置文件路徑等信息:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatisConfig.xml"/> <property name="mapperLocations" value="classpath*:org/format/dao/*.xml"/> </bean>
我們就分析這一段配置背后的細節:
SqlSessionFactoryBean實現了Spring的InitializingBean接口,InitializingBean接口的afterPropertiesSet方法中會調用buildSqlSessionFactory方法
buildSqlSessionFactory方法內部會使用XMLConfigBuilder解析屬性configLocation中配置的路徑,還會使用XMLMapperBuilder屬性解析mapperLocations屬性中的各個xml文件。
部分源碼如下:
由於XMLConfigBuilder內部也是使用XMLMapperBuilder,我們就看看XMLMapperBuilder的解析細節。
我們關注一下,增刪改查節點的解析。
XMLStatementBuilder的解析:
默認會使用XMLLanguageDriver創建SqlSource(Configuration構造函數中設置)。
XMLLanguageDriver創建SqlSource:
XMLScriptBuilder解析sql:
得到SqlSource之后,會放到Configuration中,有了SqlSource,就能拿BoundSql了,BoundSql可以得到最終的sql。
實例分析
我以以下xml的解析大概說下parseDynamicTags的解析過程:
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and name != ''"> name = #{name} </if> <if test="age != null and age != ''"> , age = #{age} </if> <if test="birthday != null and birthday != ''"> , birthday = #{birthday} </if> </trim> where id = ${id} </update>
在看這段解析之前,請先了解dom相關的知識,xml dom知識, dom博文
parseDynamicTags方法的返回值是一個List,也就是一個Sql節點集合。SqlNode本文一開始已經介紹,分析完解析過程之后會說一下各個SqlNode類型的作用。
1 首先根據update節點(Node)得到所有的子節點,分別是3個子節點
(1)文本節點 \n UPDATE users
(2)trim子節點 ...
(3)文本節點 \n where id = #{id}
2 遍歷各個子節點
(1) 如果節點類型是文本或者CDATA,構造一個TextSqlNode或StaticTextSqlNode
(2) 如果節點類型是元素,說明該update節點是個動態sql,然后會使用NodeHandler處理各個類型的子節點。這里的NodeHandler是XMLScriptBuilder的一個內部接口,其實現類包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看類名也就明白了這個Handler的作用,比如我們分析的trim節點,對應的是TrimHandler;if節點,對應的是IfHandler...
這里子節點trim被TrimHandler處理,TrimHandler內部也使用parseDynamicTags方法解析節點
3 遇到子節點是元素的話,重復以上步驟
trim子節點內部有7個子節點,分別是文本節點、if節點、是文本節點、if節點、是文本節點、if節點、文本節點。文本節點跟之前一樣處理,if節點使用IfHandler處理
遍歷步驟如上所示,下面我們看下幾個Handler的實現細節。
IfHandler處理方法也是使用parseDynamicTags方法,然后加上if標簽必要的屬性。
private class IfHandler implements NodeHandler { public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { List<SqlNode> contents = parseDynamicTags(nodeToHandle); MixedSqlNode mixedSqlNode = new MixedSqlNode(contents); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }
TrimHandler處理方法也是使用parseDynamicTags方法,然后加上trim標簽必要的屬性。
private class TrimHandler implements NodeHandler { public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { List<SqlNode> contents = parseDynamicTags(nodeToHandle); MixedSqlNode mixedSqlNode = new MixedSqlNode(contents); String prefix = nodeToHandle.getStringAttribute("prefix"); String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides"); String suffix = nodeToHandle.getStringAttribute("suffix"); String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides"); TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides); targetContents.add(trim); } }
以上update方法最終通過parseDynamicTags方法得到的SqlNode集合如下:
trim節點:
由於這個update方法是個動態節點,因此構造出了DynamicSqlSource。
DynamicSqlSource內部就可以構造sql了:
DynamicSqlSource內部的SqlNode屬性是一個MixedSqlNode。
然后我們看看各個SqlNode實現類的apply方法
下面分析一下兩個SqlNode實現類的apply方法實現:
MixedSqlNode:
public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; }
MixedSqlNode會遍歷調用內部各個sqlNode的apply方法。
StaticTextSqlNode:
public boolean apply(DynamicContext context) { context.appendSql(text); return true; }
直接append sql文本。
IfSqlNode:
public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; }
這里的evaluator是一個ExpressionEvaluator類型的實例,內部使用了OGNL處理表達式邏輯。
TrimSqlNode:
public boolean apply(DynamicContext context) { FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context); boolean result = contents.apply(filteredDynamicContext); filteredDynamicContext.applyAll(); return result; } public void applyAll() { sqlBuffer = new StringBuilder(sqlBuffer.toString().trim()); String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH); if (trimmedUppercaseSql.length() > 0) { applyPrefix(sqlBuffer, trimmedUppercaseSql); applySuffix(sqlBuffer, trimmedUppercaseSql); } delegate.appendSql(sqlBuffer.toString()); } private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) { if (!prefixApplied) { prefixApplied = true; if (prefixesToOverride != null) { for (String toRemove : prefixesToOverride) { if (trimmedUppercaseSql.startsWith(toRemove)) { sql.delete(0, toRemove.trim().length()); break; } } } if (prefix != null) { sql.insert(0, " "); sql.insert(0, prefix); } } }
TrimSqlNode的apply方法也是調用屬性contents(一般都是MixedSqlNode)的apply方法,按照實例也就是7個SqlNode,都是StaticTextSqlNode和IfSqlNode。 最后會使用FilteredDynamicContext過濾掉prefix和suffix。
總結
大致講解了一下mybatis對動態sql語句的解析過程,其實回過頭來看看不算復雜,還算蠻簡單的。 之前接觸mybaits的時候遇到剛才分析的那一段動態sql的時候總是很費解。
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and name != ''"> name = #{name} </if> <if test="age != null and age != ''"> , age = #{age} </if> <if test="birthday != null and birthday != ''"> , birthday = #{birthday} </if> </trim> where id = ${id} </update>
想搞明白這個trim節點的prefixOverrides到底是什么意思(從字面上理解就是前綴覆蓋),而且官方文檔上也沒這方面知識的說明。我將這段xml改成如下:
<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> UPDATE users <trim prefix="SET" prefixOverrides=","> <if test="name != null and name != ''"> , name = #{name} </if> <if test="age != null and age != ''"> , age = #{age} </if> <if test="birthday != null and birthday != ''"> , birthday = #{birthday} </if> </trim> where id = ${id} </update>
(第二段第一個if節點多了個逗號) 結果我發現這2段xml解析的結果是一樣的,非常迫切地想知道這到底是為什么,然后這也促使了我去看源碼的決心。最終還是看下來了。
文章有點長,而且講的也不是非常直觀,希望對有些人有幫助。
http://www.cnblogs.com/fangjian0423/p/mybaits-dynamic-sql-analysis.html