mybatis框架設計圖
mybatis的主要構件
- SqlSession
作爲MyBatis工作的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪改查功能 - Executor
MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護 - StatementHandler
封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數、將Statement結果集轉換成List集合。 - ParameterHandler
負責對用戶傳遞的參數轉換成JDBC Statement 所需要的參數, - ResultSetHandler
負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合; - TypeHandler
負責java數據類型和jdbc數據類型之間的映射和轉換 - MappedStatement
MappedStatement維護了一條<select|update|delete|insert>節點的封裝, - SqlSource
負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回 - BoundSql
表示動態生成的SQL語句以及相應的參數信息 - Configuration
MyBatis所有的配置信息都維持在Configuration對象之中。
mybatis mapper xml的解析
mybatis 加載入口
org.mybatis.spring.SqlSessionFactoryBean#afterPropertiesSet
@Override
public void afterPropertiesSet() throws Exception {
this.sqlSessionFactory = buildSqlSessionFactory();
}
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
//在默認的構造器中,會註冊很多別名處理器
configuration = new Configuration();
// mybatis 插件註冊
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
// mybatis 全局配置文件解析
if (xmlConfigBuilder != null) {
try {
// 全局配置文件解析
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// mapper的xml解析 重點
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
// 查看該方法
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}
mapper xml 文件下的 resultMap 標籤的解析
ResultMap:將查詢結果集中的列一一映射到java對象的各個屬性上
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
// parser 就是XPathParser對象,使用該對象查找 mapper標籤節點
configurationElement(parser.evalNode("/mapper"));
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
// context 就是mapper xml文件中的內容
// 解析 resultMap 標籤下的所有標籤
resultMapElements(context.evalNodes("/mapper/resultMap"));
返回到
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
mapper xml 文件下的 sql 標籤的解析
sql片段:Sql中可將重複的sql提取出來,使用時用include引用即可,最終達到sql重用的目的
sqlElement(context.evalNodes("/mapper/sql"));
mapper xml 文件下的 crud 的標籤解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
查看
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
// 將 context 內容即sql內容解析後,分爲 動態的SqlNode和靜態的SqlNode
List<SqlNode> contents = parseDynamicTags(context);
SqlNode
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseDynamicTags
List<SqlNode> parseDynamicTags(XNode node) {
List<SqlNode> contents = new ArrayList<SqlNode>();
//sql的所有節點,即 ParentNode#getChildNodes,使用 ParentNode的實現類是 DeferredElementImpl
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE ||
child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
//解析到 sql 內容有靜態sql
contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
//是動態標籤時,會走到這裏,例如: <where>
String nodeName = child.getNode().getNodeName();
//根據標籤名獲取對應的處理器,這裏的 nodeName 就是動態sql的標籤
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//解析動態標籤,例如 <if>
handler.handleNode(child, contents);
isDynamic = true;
}
}
// 看下面動態sql生成的結果 <if>
return contents;
}
// 註冊了標籤對就的處理類
NodeHandler nodeHandlers(String nodeName) {
Map<String, NodeHandler> map = new HashMap<String, NodeHandler>();
map.put("trim", new TrimHandler());
map.put("where", new WhereHandler());
map.put("set", new SetHandler());
map.put("foreach", new ForEachHandler());
map.put("if", new IfHandler());
map.put("choose", new ChooseHandler());
map.put("when", new IfHandler());
map.put("otherwise", new OtherwiseHandler());
map.put("bind", new BindHandler());
return map.get(nodeName);
}
==NodeHandler ==
if 標籤解析
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.IfHandler#handleNode
@Override
public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
//這裏迭代調用 parseDynamicTags,解析動態標籤裏的 sql
List<SqlNode> contents = parseDynamicTags(nodeToHandle);
MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
//獲取 test 元素上的值,即表達式
String test = nodeToHandle.getStringAttribute("test");
IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
targetContents.add(ifSqlNode);
}
<!--sql片段-->
<sql id="query_user_where_sql">
<if test="id!=null and id!=''">
id=#{id}
</if>
</sql>
<select id="selectUserById" resultType="com.dn.spring.mybatis.bean.UserDo">
SELECT * from t_user
<where>
<include refid="query_user_where_sql"/>
</where>
</select>
上面的動態sql解析後,如下圖所示
回到
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
List<SqlNode> contents = parseDynamicTags(context);
// MixedSqlNode 裏有很多元素,這些元素是分層次結構的,每個元素下面可能還有很多元素,如上圖所示的層次
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
回到
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
//將前面獲取到的 xml屬性,封裝成 MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
MappedStatement 類
- id
mapper的命名空間 + sql語句的id - SqlSource
sql已拼裝好了, 但是還沒有參數值 - resultMaps
sql 結果的映射關係也封裝在這個在類中 - sqlCommandType
當前標籤是sql的哪個類型
sqlSource內容
將 sql中的 #{ }
替換爲 ?
是在 org.apache.ibatis.builder.SqlSourceBuilder#parse
中的 String sql = parser.parse(originalSql);
中做的
最終 會封裝到 org.apache.ibatis.session.Configuration#mappedStatements 屬性中,key就是mapper的命名空間 + sql語句的id ,value 就是 MappedStatement 對象.每個定義的sql都會有一個MappedStatement 對象
mybatis mapper 註解的解析
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
if (!configuration.isResourceLoaded(resource)) {
//mapper xml的解析
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
// mapper 接口方法上 annotation的支持
bindMapperForNamespace();
}
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// mapper 接口類
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
//查看該方法,根據key(mapper的接口類) 在org.apache.ibatis.binding.MapperRegistry#knownMappers容器中獲取值,
// 值是 MapperProxyFactory
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
//初始化 org.apache.ibatis.binding.MapperRegistry#knownMappers 容器
configuration.addMapper(boundType);
}
}
}
}
org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
org.apache.ibatis.binding.MapperRegistry#addMapper
//容器存入信息
knownMappers.put(type, new MapperProxyFactory<T>(type));
//mapper接口類中的方法上的註解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
查看構造器和parse方法
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
sqlAnnotationTypes.add(Update.class);
sqlAnnotationTypes.add(Delete.class);
sqlProviderAnnotationTypes.add(SelectProvider.class);
sqlProviderAnnotationTypes.add(InsertProvider.class);
sqlProviderAnnotationTypes.add(UpdateProvider.class);
sqlProviderAnnotationTypes.add(DeleteProvider.class);
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//加載 mapper xml的配置文件
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 獲取接口的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析方法
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parseStatement
// 解析方法上的註解後,將信息封裝成 SqlSource
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#getSqlSourceFromAnnotations
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
final String[] strings =
(String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
// 查看 buildSqlSourceFromStrings 方法
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
//例如 @SelectProvider 註解的支持日解析
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
}
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#buildSqlSourceFromStrings
return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, java.lang.String, java.lang.Class<?>)
if (script.startsWith("<script>")) {
//方法上的註解,例如 @Select 的值是 <script>開頭的
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
// 使用XMLLanguageDriver#createSqlSource解析 ognl表達式
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
}else {
//走到這裏,則註解裏的值是純文本的
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
// 封裝成 DynamicSqlSource
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
註解上也是支持 ognl表達式的,並且使用的是 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource的方式解析的