MyBatis是怎麼玩的
MyBatis
MyBatis 是支持定製化 SQL、存儲過程以及高級映射的優秀的持久層框架。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以對配置和原生Map使用簡單的 XML 或注解,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數據庫中的記錄。
在深入的了解MyBatis之前,我們先來看一下,沒有MyBatis的時候,我們是怎麼去處理與數據庫的交互的。
一、從JDBC開始
JDBC(Java DataBase Connectivity,java數據庫連接)是一種用於執行SQL語句的Java API,可以為多種關係數據庫提供統一訪問。
看一下下麵這個例子,邏輯非常簡單,就是從數據庫中查詢出所有的的用戶:
import java.sql.*;
public class FirstExample {
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/EMP";
static final String USER = "username";
static final String PASS = "password";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(DB_URL,USER,PASS);
stmt = conn.createStatement();
String sql = "SELECT id, first, last, age FROM user";
ResultSet rs = stmt.executeQuery(sql);
List<User> userList = new ArrayList<>();
while(rs.next()){
//Retrieve by column name
int id = rs.getInt("id");
int age = rs.getInt("age");
String first = rs.getString("first");
String last = rs.getString("last");
User newUser = new User();
newUser.setId(id);
newUser.setAge(age);
newUser.setFirst(first);
newUser.setLast(last);
userList.add(newUser);
}
rs.close();
stmt.close();
conn.close();
}catch(SQLException se){
//Handle errors for JDBC
se.printStackTrace();
}catch(Exception e){
//Handle errors for Class.forName
e.printStackTrace();
}finally{
//finally block used to close resources
try{
if(stmt!=null)
stmt.close();
}catch(SQLException se2){
}// nothing we can do
try{
if(conn!=null)
conn.close();
}catch(SQLException se){
se.printStackTrace();
}
}
}
}
這段代碼看起來沒什麼複雜的,這樣進行數據庫操作也是完全的可以,可以借助代碼生成工具快速的生成對一個表的CRUD操作,但是對於比較複雜的查詢,生成工具就力不從心了。
那麼,我們就來對比一下MyBatis是怎麼與數據庫進行交互的。
二、Get started with MyBatis
和很多的Java框架一樣,如果你需要使用MyBatis,那麼就需要MyBatis的jar包出現在classpath中,可以通過下載、Maven、Gradle等等方式來處理這個問題。
根據國際慣例,框架都需要配置。
2.1 配置
下麵是一個非常基礎的配置mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment >
<transactionManager type="JDBC"/>
<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>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
BlogMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
<select resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
2.2 加載配置
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2.3 執行數據庫操作
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}
使用MyBatis對數據庫進行操作的風格與使用JDBC是完全不一樣的,接下來,我們就來詳細的看一下。
三、MyBatis
Mybatis大致可以分為兩個phase,一,配置階段,二,執行階段
- 在啟動階段主要完成的:
- 配置文件的解析
- 創建核心的Configuration對象,並通過配置創建相關聯的對象,並用這些對象填充Configuration對象
- 創建SqlSession實例
- 在執行階段,根據之前的配置,通過SqlSession執行,參數處理,SQL語句解析處理,語句執行,處理返回。
3.1 配置階段
3.1.1 配置都包含了什麼內容
- 對於配置文件的讀取,Mybatis抽象了一個名為
Resources
的類,用於從classpath中找到配置文件,並將其轉為InputStream
或者是Reader
。 - 將上一步中獲得的
InputStream
或者Reader
對象傳遞給SqlSessionFactoryBuilder
,builder會new XMLConfigBuilder
,調用其parse方法,得到Configuration
的實例,將Configuration
的實例傳遞給DefaultSqlSessionFactory
。由此可見,Mybatis的配置的解析是通過XMLConfigBuilder
完成的。
3.1.1.1 包羅萬象的Configuration
在詳細了解XMLConfigBuilder
之前,我們需要看一下Mybatis的Configuration
都包含了哪些內容。從官方給出的文檔上來看,Configuration
包含以下的配置信息:
-
properties
These are externalizable, substitutable properties that can be configured in a typical Java Properties file instance, or passed in through sub-elements of the properties element. For example:<properties resource="org/mybatis/example/config.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties>
-
settings
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
-
typeAliases
<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>
<typeAliases> <package name="domain.blog"/> </typeAliases>
@Alias("author") public class Author { ... }
typeHandlers
Whenever MyBatis sets a parameter on a PreparedStatement or retrieves a value from a ResultSet, a TypeHandler is used to retrieve the value in a means appropriate to the Java type. The following table describes the default TypeHandlersobjectFactory
Each time MyBatis creates a new instance of a result object, it uses an ObjectFactory instance to do so. The default ObjectFactory does little more than instantiate the target class with a default constructor, or a parameterized constructor if parameter mappings exist. If you want to override the default behaviour of the ObjectFactory-
plugins
MyBatis allows you to intercept calls to at certain points within the execution of a mapped statement. By default, MyBatis allows plug-ins to intercept method calls of:-
Executor
(update
,query
,flushStatements
,commit
,rollback
,getTransaction
,close
,isClosed
) -
ParameterHandler
(getParameterObject
,setParameters
) -
ResultSetHandler
(handleResultSets
,handleOutputParameters
) -
StatementHandler
(prepare
,parameterize
,batch
,update
,query
)
-
-
environments
- environment
MyBatis can be configured with multiple environments. This helps you to apply your SQL Maps to multiple databases for any number of reasons.
One important thing to remember though: While you can configure multiple environments, you can only choose ONE per SqlSessionFactory instance.
- transactionManager
There are two TransactionManager types (i.e. type="[JDBC|MANAGED]") that are included with MyBatis
dataSource
-
databaseIdProvider
MyBatis is able to execute different statements depending on your database vendor.
-
mappers
Now that the behavior of MyBatis is configured with the above configuration elements, we’re ready to define our mapped SQL statements. But first, we need to tell MyBatis where to find them. Java doesn’t really provide any good means of auto-discovery in this regard, so the be.
在Configuration
類中,包含了
- environment
- databaseId
- setting的配置項
- jdbcTypeForNull
- lazyLoadTriggerMethods
- defaultStatementTimeout
- defaultFetchSize
- defaultExecutorType
- autoMappingBehavior
- NONE Disables auto-mapping.
- PARTIAL(default) Will only auto-map results with no nested result mappings defined inside
- FULL Will auto-map result mappings of any complexity (containing nested or otherwise)
- autoMappingUnknownColumnBehavior
- NONE(default) Do nothing
- WARNING Output warning log
- FAILING Fail mapping
- variables(properties)
- reflectorFactory
Reflector
represents a cached set of class definition information that allows for easy mapping between property names and getter/setter methods - objectFactory MyBatis uses an ObjectFactory to create all needed new Objects.
- objectWrapperFactory
Wrapper of the object, 主要在
MetaObject
中使用 - proxyFactory
proxyFactory,default is
JavassistProxyFactory
- configurationFactory Configuration factory class. Used to create Configuration for loading deserialized unread properties.
- mapperRegistry
通過字段
Map<Class<?>, MapperProxyFactory<?>> knownMappers
緩存mapper
,有兩個重要的方法:
-
public <T> void addMapper(Class<T> type)
首先會添加一個type
與MapperProxyFactory
實例的對應關係到knownmapper
中,隨後創建一個MapperAnnotationBuilder
的實例,並調用其parse方法 -
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
嚐試從knownmapper
中,通過type
找到對應的MapperProxyFactory
,如果有MapperProxyFactory
,則調用其newInstance()
創建一個mapper
的代理實例。
-
interceptorChain
使用一個ArrayList
保存了所有的Interceptor
,其最重要的方法為:public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }
-
typeHandlerRegistry
//JDBC Type private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class); //Java Type private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>(); //Unknown Type private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); //All Type private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>(); //NULL Type private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = new HashMap<JdbcType, TypeHandler<?>>();
typeAliasRegistry
languageRegistry
mappedStatements(Map<String, MappedStatement>)
caches (Map<String, Cache>)
resultMaps(Map<String, ResulMap>)
parameterMaps(Map<String, ParameterMap>)
keyGenerators(Map<String, KeyGenerator>)
loadedResources(Set<String>)
sqlFragments(<String, XNode>)
cacheRefMap(Map<String, String>)
incompleteStatements(Collection<XMLStatementBuilder>)
incompleteCacheRefs(Collection<CacheRefResolver>)
incompleteResultMaps(Collection<ResultMapResolver>)
incompleteMethods(Collection<MethodResolver>)
3.1.1.2 new Configuration時都做了些什麼
在new Configuration的時候,初始化了一些默認的type alias,
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
同時設置了默認的language driver為XMLLanguageDriver
,並注冊是RawLanguageDriver
。
3.1.2 配置文件的處理過程
3.1.2.1 XML
在Mybatis中Configuration
實例的各項配置是通過XMLConfigBuilder
完成的,具體的來看,主要是通過:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(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);
}
}
進行的。
這裏需要關注一下的是mapperElement
,因為在Mybatis的xml配置中,mapper的配置方式有兩類四種:
- 第一類是通過XML,
- 另外一類是通過java的interface,
- 兩種類型的配置可以混合。
XML的配置是通過:XMLMapperBuilder
進行解析處理的。
Interface的配置是通過:MapperAnnotationBuilder
進行解析處理的。
<!-- 1. 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>
<!-- 2. 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>
<!-- 3. Using mapper interface classes -->
<mappers>
<mapper />
<mapper />
<mapper />
</mappers>
<!-- 4. Register all interfaces in a package as mappers -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
在XMLMapperBuilder
解析xml配置的時候,首先會處理namespace
,然後依次處理:
cache-ref
讀取出
cache-ref
中的namespace
,然後通過CacheRefResolver
的resolveCacheRef()
方法處理-
resolveCacheRef()
將處理邏輯代理到MapperBuilderAssistant
private final MapperBuilderAssistant assistant; public Cache resolveCacheRef() { return assistant.useCacheRef(cacheRefNamespace); }
-
而
MapperBuilderAssistant
則是從Configuration
中getCache(namespace)
public Cache useCacheRef(String namespace) { if (namespace == null) { throw new BuilderException("cache-ref element requires a namespace attribute."); } try { unresolvedCacheRef = true; Cache cache = configuration.getCache(namespace); if (cache == null) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found."); } currentCache = cache; unresolvedCacheRef = false; return cache; } catch (IllegalArgumentException e) { throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e); } }
與其他的配置類似,最終的信息都會匯總到Configuration對象中去。
- cache
讀取xml中的配置
-
通過
builderAssistant
完成Cache
對象的創建,並將新創建出來的Cache
對象添加到Configuration
中。builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
parameterMap
DeprecatedresultMap
抓出xml文件中,所有的
resultMap
,逐個進行解析在處理每一個
resultMap
的時候,XMLMapperBuilder
從xml中讀取resultMap的各項屬性配置,然後同樣是將處理代理到builderAssistant
完成resultMap
的創建在獲得
resultMap
對象之後,因為resultMap
是可以extends
其他的resultMap
的,且被繼承的resultMap
有可能已經解析,也有可能還沒有解析,此時,ResultMapResolver
負責處理這個問題,而ResultMapResolver
又將問題拋給builderAssistant
來完成最終的處理。在
builderAssistant
中
* 如果`parentResultMap`還沒有加載完成,Mybatis會拋出異常
`IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");`
在`XMLMapperBuilder`會捕獲`IncompleteElementException`,並將`ResultMapResolver`添加到`Configuration`中:`configuration.addIncompleteResultMap`
* 如果`parentResultMap`已經加載完成,
1. 則使用`subResultMap`的`constructor`替換`parentResultMap`的`constructor`
2. 合並`subResultMap`和`parentResultMap`
3. 通過`ResultMap.Builder`完成`ResultMap`實例的創建,並將其添加到`Configuration`中
-
sql
This element can be used to define a reusable fragment of SQL code that can be included in other statements. It can be statically (during load phase) parametrized. Different property values can vary in include instances. For example:
<sql > ${alias}.id,${alias}.username,${alias}.password </sql>
The SQL fragment can then be included in another statement, for example:
<select resultType="map"> select <include ref><property name="alias" value="t1"/></include>, <include ref><property name="alias" value="t2"/></include> from some_table t1 cross join some_table t2 </select>
從上述可以看出,sql配置的解析應該是比較簡單:將sql作為應該XNode,以
(Id,XNode)
的形式,存儲到XMLMapperBuilder.sqlFragments
中。 -
select|Insert|update|delete
select|Insert|update|delete
是最為複雜的配置項,Mybatis將其處理交由XMLStatementBuilder
進行處理。在
XMLStatementBuilder
的parseStatementNode()
中,包括以下的處理:- 簡單配置項的讀取
- 通過
XMLIncludeTransformer.applyIncludes
完成對include節點的處理 - 處理
selectKey
- 處理完
include
和selectKey
,Mybatis會將初始的include
和selectKey
標簽進行替換或者移除,然後通過LanguageDriver
完成對SQL語句的處理,返回一個SqlSource
的實例。
默認的
LanguageDriver
為XMLLanguageDriver
,其將SQL語句的解析處理代理給XMLScriptBuilder.parseScriptNode()
:public SqlSource parseScriptNode() { List<SqlNode> contents = parseDynamicTags(context); MixedSqlNode rootSqlNode = new MixedSqlNode(contents); SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }
在
parseDynamicTags
中返回的List<SqlNode>
,包含以下幾種SqlNode
tag SqlNode handler ${} TextSqlNode N/A trim TrimSqlNode TrimHandler where WhereSqlNode WhereHandler set SetSqlNode SetHandler foreach ForEachSqlNode ForEachHandler if IfSqlNode IfHandler choose ChooseSqlNode ChooseHandler when IfSqlNode IfHandler otherwise MixedSqlNode OtherwiseHandler bind VarDeclSqlNode BindHandler 普通 StaticTextSqlNode N/A - 在獲得
SqlSource
對象之後,Mybatis更加配置初始化KeyGenerator
- 最後通過
MappedStatement.Builder
生成MappedStatement
,並將其添加到Configuration
中。
到此為止,一個mapper的xml配置已經處理完成,接下來就是要把namespace所對應的java class通過configuration.addMapper
添加到Configuration上去。
最後處理,分別調用pending的resolver:
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
3.1.2.2 Annotation
那麼,現在還有一個問題沒有解決,那就是Mapper的定義,可以通過XML的方式,也可以通過Annotation
的方式,或者是兩種混合使用,我們已經清楚了XML方式的配置是怎麼處理的,那麼Annotation
是在哪裏處理的,是怎麼處理的呢?
當調用configuration.addMapper
的時候,Mybatis會代理到MapperRegistry
,進而通過MapperAnnotationBuilder
來完成Annotation
的處理,Mybatis支持的Annotation
如下表:
Annotation | Target | XML equivalent |
---|---|---|
@CacheNamespace |
Class |
<cache> |
@Property |
N/A | <property> |
@CacheNamespaceRef |
Class |
<cacheRef> |
@ConstructorArgs |
Method |
<constructor> |
@Arg |
N/A |
<arg> <idArg>
|
@TypeDiscriminator |
Method |
<discriminator> |
@Case |
N/A | <case> |
@Results |
Method |
<resultMap> |
@Result |
N/A |
<result> <id>
|
@One |
N/A | <association> |
@Many |
N/A | <collection> |
@MapKey |
Method |
|
@Options |
Method |
Attributes of mapped statements. |
@Insert @Update @Delete @Select
|
Method |
<insert> <update> <delete> <select>
|
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider
|
Method |
<insert> <update> <delete> <select>
|
@Param |
Parameter |
N/A |
@SelectKey |
Method |
<selectKey> |
@ResultMap |
Method |
N/A |
@ResultType |
Method |
N/A |
@Flush |
Method |
N/A |
當MapperAnnotationBuilder開始處理Annotation之前,MapperAnnotationBuilder會通過configuration.isResourceLoaded(resource)
檢查以Mapper為namespace的相關資源有沒有被加載,如果沒有被加載過,則先加載並解析XML的配置,然後處理Cache及CacheRef,因為這兩個Annotation是在Type層麵的,所以優先處理,處理的方式和XML沒有太多的區別。
接下來遍曆Type
上所有的methods
:
-
處理
ParameterType
如果是單個
parameter
,則ParameterType
為該參數的Type
,否則,則為ParamMap.class
。 獲取
LanguageDriver
-
通過
LanguageDriver
獲取SqlSource
- 獲取
@Insert/@Update/@Delete/@Select
- 獲取
@InsertProvider/@UpdateProvider/@DeleteProvider/@SelectProvider
- 兩種注解不能同時出現,隻能二選一,
- 如果是第1種,則:
- 從
sqlAnnotation
中獲取sql語句,結果為一個String類型的數組 - 用空格連接String數組
- 通過
LanguageDriver
創建SqlSource
,- 如果在sql語句中有
<script>
,則通過XMLScriptBuilder
來處理SQL。 - 如果沒有
<script>
,則通過PropertyParser.parse
,然後通過創建SqlSource
。
- 如果在sql語句中有
- 從
- 如果是第2種,則返回一個
ProviderSqlSource
的實例。
- 獲取
如果sql語句類型是
INSERT
或者是UPDATE
,則根據SelectKey
和Options
這兩個Annotation
確定KeyGenerator
處理
@ResultMap
,將ResultMap
的value
,以,
拚接到一起,形成resultMapId
。-
如果是
SELECT
,則通過調用resultMapId = parseResultMap(method)
;- 獲取
returnType
,ConstructorArgs
,Results
,TypeDiscriminator
- 如果
Results
有指定id
的話,則resultMapId=type.getName() + "." + results.id()
如果沒有指定的話,則resultMapId=type.getName() + "." + method.getName() + suffix
- 將ConstructorArg轉為ResultMapping對象。
- 處理Results,將其轉為ResultMapping對象。
- 處理Discriminator
- 獲取
最終通過MapperBuilderAssistant得到MappedStatement對象。
3.2 數據庫操作的執行過程
到目前為止,無論是XML的配置,還是Annotation的配置,都已經加載完成了,接下來就可以對mapper的方法進行調用了。
-
通過SqlSession獲取Mapper的代理實例:
//1. DefaultSqlSession.java public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } //2. Configuration.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } //3. MapperRegistry.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
-
從上麵的代碼可以看出,我們得到的Mapper的對象,是一個MapperProxy的實例,MapperProxy的聲明如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {...}
當我們執行一個方法時,其執行過程是這樣的:
- MapperProxy嚐試從methodCache中獲取MapperMethod實例,如果沒有,則創建一個實例,並添加到methodCache中。
- 執行
mapperMethod.execute(sqlSession, args);
- MapperMethod包含兩個實例:
* SqlCommand
通過MappedStatement獲取類型和MappedStatement的ID
* MethodSignature
```java
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
```
在執行execute方法時,
1. 通過SqlCommand判斷是INSERT/UPDATE/DELETE/SELECT/FLUSH,下麵以INSERT為例。
2. 通過MethodSignature的ParamNameResolver獲取到參數對象,會有三種情況:
* 沒有任何參數時,返回null
* 如果隻有一個參數,則是參數本身
* 如果有多個參數,則返回一個Map
3. 執行`rowCountResult(sqlSession.insert(command.getName(), param))`
DefaultSqlSession通過MappedStatement的ID,從Configuration中獲取到MappedStatement,然後交由Executor執行。
-
在配置Mybatis的時候,我們可以通過defaultExecutorType配置Executor為SIMPLE/REUSE/ BATCH,默認為SIMPLE。
- 清理本地緩存
-
通過MappedStatement獲取到Configuration對象,通過:
configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
獲取到StatementHandler對象,此時得到的對象為RoutingStatementHandler,該對象會根據MappedStatement的類型,將實際的操作代理給
SimpleStatementHandler/PreparedStatementHandler(默認)/CallableStatementHandler
。在創建PreparedStatementHandler的時候,會通過MappedStatement.getBoundSql->sqlSource.getBoundSql(parameterObject)得到BoundSql。
最後,將所有的interceptor實例plugin到這個StatementHandler上去。
plugin的動作,其實就是對這個StatementHandler進行逐層的動態代理。
-
Mybatis封裝了一個Transaction對象,從實現類JdbcTransaction中獲取Connection,同時設定TransactionIsolation:
protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommmit); }
-
調用StatementHandler的prepare方法,獲取到Statement對象:
//1. 創建Statement實例 stmt = handler.prepare(connection, transaction.getTimeout()); //2. 添加參數 handler.parameterize(stmt); /* 1. 通過boundSql獲取到List<ParameterMapping> 2. 逐個遍曆,通過相對應的TypeHandler往Statement上添加參數。 parameterMapping.getTypeHandler(); */
//PreparedStatementHandler.java,創建Statement實例, //最終還是通過JDBC的connection對象創建處理PreparedStatement對象。 protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } }
-
調用Handler的update/query方法執行:
public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); int rows = ps.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; } public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
這兩個方法的區別在於,update需要對key進行處理,而query則需要對resultSet進行處理。
* Jdbc3KeyGenerator 1. 通過PreparedStatement.getGeneratedKeys 2. 獲取配置中的keyProperties 3. 獲取相對應類型的TypeHandler,將TypeHandler處理過的值設置到對象中去。 * SelectKeyGenerator 1. 通過SimpleExecutor執行KeyStatement獲取到Key的值 2. 將值設置到Parameter對象中去 * ResultHandler 執行SELECT的時候,MapperMethod需要根據返回值的類型,選擇不同的執行策略: ```java if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); //使用指定的resultHandler或者null result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); //executor.query時傳入的resultHandler為null } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); //executor.query時傳入的resultHandler為null //返回前,通過DefaultMapResultHandler對結果進行處理 } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); //executor.query時傳入的resultHandler為null } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); //executor.query時傳入的resultHandler為null } ``` 在Executor執行是,如果傳入的ResultHandler為null,則嚐試從localCache獲取結果,如果結果為null才會從Database中查詢。 SimpleExecutor在doQuery時,會通過`configuration.newStatementHandler`時,會通過 `configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);` 創建ResultSetHandler,將resultHandler作為傳入參數。 ResultSetHandler在其方法: ```java public List<Object> handleResultSets(Statement stmt) throws SQLException {...} ``` 中,如果發現resultHandler為null,則會new DefaultResultHandler的實例,對result進行處理。
四、Mybatis中有哪些優秀的設計及值得借鑒的地方?
最後更新:2017-07-04 20:02:56