mybatis-启动源码分析
【1】測試用例
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><!-- 1)properteis1.1、mybatis 可以使用properties 標簽來引入外部properties配置文件的內容;1.2、resource 引入類路徑下 的資源;1.3、url 是引入網絡路徑或磁盤路徑下的資源;--><properties resource="dbconfig.properties"></properties><!-- 2)settings 包含很多重要的設置項2.1、setting 用來設置每一個設置項:name: 設置項名稱, value:設置項取值;--><!-- 開啟駝峰命名規則,數據庫表字段 a_b 可以解析為 類成員變量 aB --><!-- 顯示指定每個需要更改的配置項,即使有默認值,防止版本更新帶來的問題 --><settings><!-- 開啟駝峰命名法 --><setting name="mapUnderscoreToCamelCase" value="false"/><!-- 懶加載 --><setting name="lazyLoadingEnabled" value="true"/><!-- 侵入型懶加載 --><setting name="aggressiveLazyLoading" value="false"/></settings><!-- 3)別名處理器:可以為java類型起別名(注意:別名不區分大小寫)3.1) typeAlias為某一個java類型取別名, type:指定要起別名的類型全類名,默認別名就是類名小寫,employeealias: 指定新的 別名;3.3)--><typeAliases><!-- 3.1) typeAlias為某一個java類型取別名, type:指定要起別名的類型全類名,默認別名就是類名小寫,employeealias: 指定新的 別名;<typeAlias type="com.swjtu.mybatis.bean.Employee" alias="emp"/>--><!-- 3.2)package: 為某個包下的所有類批量起別名:name: 指定包名,為當前包以及所有的子包的每個類都起一個默認別名(類名小寫);--><!-- 3.3) 批量起別名的情況下,可以使用注解 Alias()為某個類型指定新的別名;存在 a 包 和 a.b 包下都有 Employee類的情況;--><package name="com.swjtu.mybatis.bean"/></typeAliases><!-- 4、environments: 環境們, mybatis可以配置多種環境, default指定使用某種環境。可以達到快速切換環境。4.1)environment:配置一個具體的環境信息;必須有兩個標簽:id代表當前環境的唯一標識4.1.1) transactionManager 必須有, 事務管理器,type 事務管理器類型,type="[JDBC|MANAGED]" 自定義事務管理器:實現 TransactionFactory 接口, type指定全類名 Configuration類中定義了別名: org.apache.ibatis.session.ConfigurationtypeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);4.1.2) dataSource 數據源:type:數據源類型 type="[UNPOOLED|POOLED|JNDI]"typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);自定義數據源: 實現 DataSourceFactory 接口 , type 為自定義數據源的全類名 --> <environments default="development"><!-- 測試環境 --><environment id="test"><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><!-- 開發環境 --><environment id="development"><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><!-- 5、databaseIdProvider :支持多數據庫廠商的;type=DB_VENDOR: VendorDatabaseIdProvider作用就是得到數據庫廠商標識(由數據庫驅動自己附帶的 getDatabaseProductName),mybatis根據該標識執行不同的sql;并在 sql 標簽如<select> 添加屬性 databaseId 屬性--> <databaseIdProvider type="DB_VENDOR"><!-- 為不同數據庫廠商起別名 --><property name="Mysql" value="mysql"/><property name="Oracle" value="oracle"/><property name="SQL Server" value="sqlserver"/></databaseIdProvider> <!-- 將我們寫好的sql 映射文件 EmployeeMapper.xml 一定要注冊到全局配置文件 mybatis-config.xml 中;6、mappers:將sql映射注冊到全局配置中;--> <mappers><!-- mapper標簽: 注冊一個sql映射注冊配置文件的兩種方法:方法1:resource: 引用類路徑下的sql 映射文件;方法2:url: 引用網絡路徑或磁盤路徑下的 sql 映射文件;注冊接口的方法:方法1 class: 直接引用(注冊)接口, 寫接口的全類名;補充1: 有sql映射文件,映射文件名必須和接口同名,并且仿造同一個目錄下;補充2: 沒有sql映射文件, 所有sql 利用注解修飾接口上;補充3: 不推薦使用注解,而使用sql映射文件的方式來 進行sql 映射。--><mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/> <!-- 批量注冊: 前提是要把 Mapper類文件和Mapper sql 映射文件放在同目錄下荔枝:<package name="com.swjtu.mybatis.dao"/> 可以吧 該包下的所有mapper 映射文件注冊到全局配置環境中;--><!-- <mapper class="com.swjtu.mybatis.dao.cwn.MYCwnOprDAO"/> --></mappers></configuration>執行sql;
public static void main1() {/*1. 獲取 SqlSessionFactory 對象*/SqlSessionFactory sqlSessionFactory = MyBatisUtils.getSqlSessionFactory();/*2.獲取SqlSession對象, 不會自動提交數據*/SqlSession session = sqlSessionFactory.openSession();try { String flag = "0911A"; List<Map<String, Object>> list = new ArrayList<>();for (int i = 0; i < 5; i++) { String indexFormat = String.format("%8d", i);Map<String, Object> map = new HashMap<>(); map.put("RCRD_ID", flag + indexFormat); map.put("CUST_NUM", "CUST_NUM" +flag+ indexFormat);map.put("CUST_NAME", "CUST_NAME" +flag+ indexFormat);map.put("WARN_TIMES", i); list.add(map); } // mapper.insert2CustWarn(list);session.insert("com.swjtu.mybatis.dao.EmployeeMapper.insert2CustWarn", list); session.commit();} finally { session.close();}System.out.println("bingo!");} mapper文件中的 dml 元素 <insert id="insert2CustWarn">INSERT INTO my_cust_warn_tbl(rcrd_id, cust_num, cust_name , warn_times) VALUES <foreach collection="list" item="item" separator=",">(#{item.RCRD_ID}, #{item.CUST_NUM}, #{item.CUST_NAME}, #{item.WARN_TIMES})</foreach></insert>MyBatisUtils.getSqlSessionFactory();
/*** 獲取SqlSessionFactory * @return*/ public final static SqlSessionFactory getSqlSessionFactory() {String resource = "mybatis-config.xml";InputStream inputStream;try { inputStream = Resources.getResourceAsStream(resource);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);return sqlSessionFactory;} catch (IOException e) {e.printStackTrace();}return null; }【2】創建 SqlSessionFactory 對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
實際上返回的是? DefaultSqlSessionFactory;
// SqlSessionFactoryBuilder public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}?而 build(Configuration config) 中的 config 是從mybatis-config.xml文件解析而來的;
【2.1】XMLConfigBuilder.parse() 產生Configuration
parser.parse() 方法的邏輯如下(XMLConfigBuilder構造器中新建了 Configuration對象):
// SqlSessionFactoryBuilder private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {super(new Configuration());// 新建了一個 configuration對象ErrorContext.instance().resource("SQL Mapper Configuration");this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}public Configuration parse() {if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;parseConfiguration(parser.evalNode("/configuration"));// 解析xml節點return configuration;}private void parseConfiguration(XNode root) {// 解析xml節點try {Properties settings = settingsAsPropertiess(root.evalNode("settings"));//issue #117 read properties firstpropertiesElement(root.evalNode("properties"));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 #631environmentsElement(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-config.xml 文件中在 mapper標簽中配置的resource屬性指定的sql mapper文件,比如:
<mappers> <mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/> <mapper class="com.swjtu.mybatis.dao.EmployeeMapperAnnotation"/> </mappers>?其中 root.evalNode("mappers") 獲取的值就是 mapper 數組;?
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {Class<?> mapperInterface = Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}Xnode parent 長這樣(就是 mybatis-config.xml 中 mappers元素內容):
以上代碼,我們發現,解析 mapper有三種方式;
- 1.通過resource 配置;
- 2.通過url;
- 3.通過mapper class;
下面,我們以resource 為例來講解;
if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse(); }以上代碼發現,每一個mapper文件,都會創建一個 XMLMapperBuilder 對象來解析它;
String resource=“com\swjtu\mybatis\dao\EmployeeMapper.xml”;
XMLMapperBuilder構造器傳入了4個參數
XMLMapperBuilder構造器;該構造器新建了 MapperBuilderAssistant對象(解析mapper文件的助手);
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),configuration, resource, sqlFragments);}private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {super(configuration);this.builderAssistant = new MapperBuilderAssistant(configuration, resource);this.parser = parser;this.sqlFragments = sqlFragments;this.resource = resource;}MapperBuilderAssistant() 長這個樣子;
?【2.2】XmlMapperBuilder().parse()? 解析mappers下的每個mapper文件內容
接著,進入? XmlMapperBuilder().parse() 方法,解析mappers下的mapper元素的內容,mapper元素如 ??? <mapper resource="com\swjtu\mybatis\dao\EmployeeMapper.xml"/>??? ;如下:
代碼步驟:
- step1)解析mapper文件下 mapper元素內容(主要是 dml標簽元素內容);
- step2)把mapper文件名添加到 configuration,以避免二次解析;
- step3)構建命名空間;
【2.2.1】 XmlMapperBuilder.configurationElement(parser.evalNode("/mapper"))如下:?
其中 parser.evalNode("/mapper")或context就是 文件 com\swjtu\mybatis\dao\EmployeeMapper.xml? 的內容,如下:
代碼步驟:
- step1)設置命名空間;
- step2)解析緩存;
- step3)解析 parameterMap 標簽內容;
- step4)解析 resultMap 標簽內容;
- step5)解析 sql 標簽內容;
- step6) 解析 sql 的dml 標簽內容(select或insert或update 或 delete);
最后一步, buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 就是真正解析 sql的 dml語句的標簽了,即解析出sql文本并加載到內存中;?
List<XNode>? list 包含了多個 dml標簽的內容數組;
接著 buildStatementFromContext(List<XNode> list, String requiredDatabaseId) 遍歷list中的每個dml 標簽元素;? 如下:
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}buildStatementFromContext(list, null);}private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}}以上代碼,每個 dml標簽元素 就會創建一個 XMLStatementBuilder 對象;
statementParser.parseStatementNode();?
就用于解析 context 文本(dml標簽元素內容); 如下:
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}以上代碼的步驟如下:
step1)解析 dml元素中各種屬性,包括 parameterMap,parameterType,resultMap,resultType等;
Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");resultType 為map;因為 dml標簽元素如下:
<select id="getEmpByLastNameLikeReturnMap" resultType="map">SELECT id AS ID, last_name AS LASTNAME, email AS EMAIL, gender AS GENDERFROM emp_tbl WHERE last_name like #{lastName}</select>step2)獲取語言驅動器, LanguageDriver langDriver = getLanguageDriver(lang);
step3)??? Class<?> resultTypeClass = resolveClass(resultType);:返回 resultType的class類型;如 resultType為map,則返回 Map.class ;
protected Class<?> resolveClass(String alias) { // 以alias為map為例if (alias == null) {return null;}try {return resolveAlias(alias);} catch (Exception e) {throw new BuilderException("Error resolving class. Cause: " + e, e);}} // BaseBuilder.java protected Class<?> resolveAlias(String alias) {return typeAliasRegistry.resolveAlias(alias);}public <T> Class<T> resolveAlias(String string) {try {if (string == null) {return null;}// issue #748String key = string.toLowerCase(Locale.ENGLISH);Class<T> value;if (TYPE_ALIASES.containsKey(key)) {value = (Class<T>) TYPE_ALIASES.get(key);} else {value = (Class<T>) Resources.classForName(string);}return value;} catch (ClassNotFoundException e) {throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);}}typeAliasRegistry 為類型別名注冊器,如下:
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);}step4)獲取sql語句類型, PREPARED ;
step5)獲取sql命令類型 SqlCommandType -SELECT
String nodeName = context.getNode().getNodeName(); // nodeName為selectSqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));public enum SqlCommandType {UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH; }step6)解析是否需要使用緩存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//false boolean useCache = context.getBooleanAttribute("useCache", isSelect);//true boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);//falsestep7)解析dml元素中 include元素內容(比如 include sql);
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode()); public void applyIncludes(Node source) {Properties variablesContext = new Properties();Properties configurationVariables = configuration.getVariables();if (configurationVariables != null) {variablesContext.putAll(configurationVariables);}applyIncludes(source, variablesContext);}?step8)解析 selectKey;sql中沒有,不用考慮;
processSelectKeyNodes(id, parameterTypeClass, langDriver);?step9)創建sql 映射對象;
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
langDriver 指向了 org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
public class XMLLanguageDriver implements LanguageDriver {@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);}@Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);return builder.parseScriptNode();}@Overridepublic SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {// issue #3if (script.startsWith("<script>")) {XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());return createSqlSource(configuration, parser.evalNode("/script"), parameterType);} else {// issue #127script = PropertyParser.parse(script, configuration.getVariables());TextSqlNode textSqlNode = new TextSqlNode(script);if (textSqlNode.isDynamic()) {return new DynamicSqlSource(configuration, textSqlNode);} else {return new RawSqlSource(configuration, script, parameterType);}}}}builder.parseScriptNode()? 調用的是 XMLScriptBuilder.parseScriptNode() ; 解析動態sql (用${} 包裹的sql );
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;}List<SqlNode> parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<SqlNode>();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 {contents.add(new StaticTextSqlNode(data));}} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628String nodeName = child.getNode().getNodeName();NodeHandler handler = nodeHandlers(nodeName);if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");}handler.handleNode(child, contents);isDynamic = true;}}return contents;}因為我們不是動態sql,所以返回的是 RawSqlSource;
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);step10)解析 resultSets, keyProperty, keyColumn 屬性等 (也不用太關心);?
String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}step11)MapperBuilderAssistant.addMappedStatement() 把解析得到的sql映射對象添加到configuration;
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);參數列表如下:
- 1.id,getEmpByLastNameLikeReturnMap;
- 2.sqlSource,
?parameterMappins 明顯是 參數映射;
- 3.statementType , PREPARED;
- 4.sqlCommandType,SELECT;
- 5.fetchSize,null;
- 6.timeout, null;
- 7.parameterMap,null;
- 8.parameterTypeClass,null;
- 9.resultMap,null;
- 10.resultTypeClass, interface java.util.Map;
- 11.resultSetTypeEnum,null;
- 12.flushCache,false;
- 13.useCache,true;
- 14.resultOrdered,false;
- 15.keyGenerator,org.apache.ibatis.executor.keygen.NoKeyGenerator@5ef8df1e;
- 16.keyProperty,null;
- 17.keyColumn,null;
- 18.databaseId,null;
- 19.langDriver, org.apache.ibatis.scripting.xmltags.XMLLanguageDriver@32057e6;
- 20.resultSets, null;
MapperBuilderAssistant.addMappedStatement(...)? 源碼如下:
public MappedStatement addMappedStatement(...) {if (unresolvedCacheRef) {throw new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;}id = applyCurrentNamespace(id, false); 這一步,給id添加命名空間前綴,
- 執行前;id為? getEmpByLastNameLikeReturnMap ;
- 執行后,id為 com.swjtu.mybatis.dao.EmployeeMapper.getEmpByLastNameLikeReturnMap;
解析每個dml元素時,都要創建一個? MappedStatement.Builder(...) 用于創建 MappedStatement( sql映射信息)
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
MappedStatement statement = statementBuilder.build(); 源碼如下:
public MappedStatement build() {assert mappedStatement.configuration != null;assert mappedStatement.id != null;assert mappedStatement.sqlSource != null;assert mappedStatement.lang != null;mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);return mappedStatement;}補充:MappedStatement對象
MappedStatement 實際是封裝了 sql映射信息的對象 ,sql映射是在 MapperBuilderAssistant.addMappedStatement(...) 方法里面構建;結構如下:
每條sql都是一個 MappedStatement對象,解析完成后,將其添加到 configuration對象中;
?configuration 就是根據mybatis-config.xml 文件解析出的 Configuration 對象;長這個樣子:
【2.2.2】 configuration.addLoadedResource(resource);
把已經解析的resource 文件名添加到 configuration,避免二次解析;
【2.2.3】XMLMapperBuilder.bindMapperForNamespace() 創建命名空間
private void bindMapperForNamespace() {String namespace = builderAssistant.getCurrentNamespace();if (namespace != null) {Class<?> boundType = null;try {boundType = Resources.classForName(namespace);} catch (ClassNotFoundException e) {//ignore, bound type is not required}if (boundType != null) {if (!configuration.hasMapper(boundType)) {// Spring may not know the real resource name so we set a flag// to prevent loading again this resource from the mapper interface// look at MapperAnnotationBuilder#loadXmlResourceconfiguration.addLoadedResource("namespace:" + namespace);configuration.addMapper(boundType);}}}}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}很顯然,當 有mapperclass的時候,才會設置 boundType;如果沒有mapper class也沒有關系;因為catch會忽略異常;
【2.2.4】解析其他
parsePendingResultMaps();parsePendingChacheRefs();parsePendingStatements();【2.2.5】創建 DefaultSqlSession對象并返回
解析完成后,生成Configuration對象;
【3】sqlSessionFactory.openSession()打開會話
調用的是 DefaultSqlSessionFactory.openSession()
// DefaultSqlSessionFactory @Overridepublic SqlSession openSession() {return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);// 返回的是 DefaultSqlSession return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}【3.1】DefaultSqlSessionFactory 中有兩類方法包括,
兩者不同點在于
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Transaction tx = transactionFactory.newTransaction(connection);
// 從數據源獲取會話 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);// 創建事務工廠tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);// 創建事務 final Executor executor = configuration.newExecutor(tx, execType);// 執行器 return new DefaultSqlSession(configuration, executor, autoCommit);// 會話} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}// 從數據庫連接獲取會話 private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {try {boolean autoCommit;try {autoCommit = connection.getAutoCommit();} catch (SQLException e) {// Failover to true, as most poor drivers// or databases won't support transactionsautoCommit = true;} final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);final Transaction tx = transactionFactory.newTransaction(connection);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}獲取會話無論采用 datasource,還是connection方式,其步驟如下:
- step1)獲取環境對象;
- step2)獲取事務工廠;
- step3)創建事務;
- step4)創建執行器;
- step5)創建會話 DefaultSqlSession;
【3.2】事務工廠
// 從環境對象中獲取事務工廠private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {if (environment == null || environment.getTransactionFactory() == null) {return new ManagedTransactionFactory();}return environment.getTransactionFactory();}如果配置了環境元素,則返回配置的環境的事務工廠;否則默認返回 ManagedTransactionFactory;
此外,事務工廠有3個,如下:
【3.3】創建事務-ManagedTransaction
// 可管理的事務工廠 public class ManagedTransactionFactory implements TransactionFactory {private boolean closeConnection = true;@Overridepublic void setProperties(Properties props) {if (props != null) {String closeConnectionProperty = props.getProperty("closeConnection");if (closeConnectionProperty != null) {closeConnection = Boolean.valueOf(closeConnectionProperty);}}}@Overridepublic Transaction newTransaction(Connection conn) {return new ManagedTransaction(conn, closeConnection);} // 創建事務對象@Overridepublic Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {// Silently ignores autocommit and isolation level, as managed transactions are entirely// controlled by an external manager. It's silently ignored so that// code remains portable between managed and unmanaged configurations.return new ManagedTransaction(ds, level, closeConnection);} }可管理事務: ManagedTransaction;?
public class ManagedTransaction implements Transaction {private static final Log log = LogFactory.getLog(ManagedTransaction.class);private DataSource dataSource;private TransactionIsolationLevel level;private Connection connection;private boolean closeConnection;public ManagedTransaction(Connection connection, boolean closeConnection) {this.connection = connection;this.closeConnection = closeConnection;}public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {this.dataSource = ds;this.level = level;this.closeConnection = closeConnection;}@Overridepublic Connection getConnection() throws SQLException {if (this.connection == null) {openConnection();}return this.connection;}@Overridepublic void commit() throws SQLException {// Does nothing}@Overridepublic void rollback() throws SQLException {// Does nothing}@Overridepublic void close() throws SQLException {if (this.closeConnection && this.connection != null) {if (log.isDebugEnabled()) {log.debug("Closing JDBC Connection [" + this.connection + "]");}this.connection.close();}}protected void openConnection() throws SQLException {if (log.isDebugEnabled()) {log.debug("Opening JDBC Connection");}this.connection = this.dataSource.getConnection();if (this.level != null) {this.connection.setTransactionIsolation(this.level.getLevel());}}}?它的commit, rollback 都是空的;
它的數據庫連接的獲取有兩種方式,
【3.4】創建執行器
1)ExecutorType 是枚舉類型,默認為SIMPLE ;
final Executor executor = configuration.newExecutor(tx, execType);// Configuration.newExecutor 方法 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}executor = (Executor) interceptorChain.pluginAll(executor);return executor;}如果為SIMPLE,則返回 SimpleExecutor 執行器;? 但 如果啟用緩存,則封裝SimpleExecutor到緩存執行器;
最后把 執行器添加到攔截器列表,攔截器 Interceptor 列表如下,本質上是一個 ArrayList數組;
// 攔截器 public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}其中SimpleExecutor父類Executor的實現類 包括
2)SimpleExecutor 長這個樣子
public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}@Overridepublic <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}@Overrideprotected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);Statement stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>queryCursor(stmt);}@Overridepublic List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {return Collections.emptyList();}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());handler.parameterize(stmt);return stmt;}}【3.5】 創建會話 DefaultSqlSession
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}【4】session.insert(...)
【4.1】DefaultSqlSession.insert()?
session.insert(...); 它實際調用的是 DefaultSqlSession.insert() 方法;
// DefaultSqlSession @Overridepublic int insert(String statement, Object parameter) {return update(statement, parameter);}@Overridepublic int update(String statement) {return update(statement, null);}@Overridepublic int update(String statement, Object parameter) {try {dirty = true;MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}?executor.update(ms, warpCollection(parameter)); 中的? executor是 SimpleExecutor;
我們接著看下 SimpleExecutor-執行器 ;
// 簡單執行器 public class SimpleExecutor extends BaseExecutor {public SimpleExecutor(Configuration configuration, Transaction transaction) {super(configuration, transaction);}// 執行器基類 public abstract class BaseExecutor implements Executor {private static final Log log = LogFactory.getLog(BaseExecutor.class);protected Transaction transaction;protected Executor wrapper;protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;protected PerpetualCache localCache;protected PerpetualCache localOutputParameterCache;protected Configuration configuration;protected int queryStack = 0;private boolean closed;protected BaseExecutor(Configuration configuration, Transaction transaction) {this.transaction = transaction;this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();this.localCache = new PerpetualCache("LocalCache");this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");this.closed = false;this.configuration = configuration;this.wrapper = this;}BaseExecutor.update(MappedStatement ms, Object parameter) 如下:
@Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed) {throw new ExecutorException("Executor was closed.");}clearLocalCache();return doUpdate(ms, parameter);}SimpleExecutor.doUpdate(...)
@Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);stmt = prepareStatement(handler, ms.getStatementLog());return handler.update(stmt);} finally {closeStatement(stmt);}}上述是執行sql 語句的地方,依賴的對象包括:
Configuration.newStatementHandler(...) 方法如下:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}參數列表為:
參數1-executor;
參數2-mappedStatement;?
參數3-parameterObject;
?
參數4- parameterObject 為:
{collection=[{RCRD_ID=0908A 0,CUST_NUM=CUST_NUM 0,WARN_TIMES=0,CUST_NAME=CUST_NAME 0},{RCRD_ID=0908A 1,CUST_NUM=CUST_NUM 1,WARN_TIMES=1,CUST_NAME=CUST_NAME 1},{RCRD_ID=0908A 2,CUST_NUM=CUST_NUM 2,WARN_TIMES=2,CUST_NAME=CUST_NAME 2},...],list=[{RCRD_ID=0908A 0,CUST_NUM=CUST_NUM 0,WARN_TIMES=0,CUST_NAME=CUST_NAME 0},{RCRD_ID=0908A 1,CUST_NUM=CUST_NUM 1,WARN_TIMES=1,CUST_NAME=CUST_NAME 1},{RCRD_ID=0908A 2,CUST_NUM=CUST_NUM 2,WARN_TIMES=2,CUST_NAME=CUST_NAME 2},]}參數5-rowBounds?
?
?參數6-resultHandler 為null;
參數7-boundSql 為null ;
SimpleExecutor.newStatementHandler(...) 如下:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);?
代碼步驟:
step1)獲取mybatis 配置對象;
step2)獲取 StatementHandler;
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);// 新建語句處理器public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}step3)prepareStatement(StatementHandler handler, Log statementLog) ,把預編譯sql 的參數賦值;
handler 為 PreparedStatementHandler ;
- step3.1)stmt = handler.prepare(connection, transaction.getTimeout()); 獲取預編譯語句;
- step3.2)handler.parameterize(stmt); 對參數進行賦值;
parameterHandler.setParameters(...) ;源碼如下:
遍歷 sql語句中所有參數,并賦值;
public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = configuration.getJdbcTypeForNull();}try {typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);} catch (SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}?parameterMappings 是參數信息,如CUST_NUM ;??
?boundsql 是預編譯sql,帶有問號;?
?
?
?設置參數;
step4)handler.update() 執行具體 sql; ?
@Overridepublic int update(Statement statement) throws SQLException {return delegate.update(statement);} // delegate 代理為 PreparedStatementHandler執行具體sql,如下:
@Overridepublic int update(Statement statement) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute(); // 執行sql int rows = ps.getUpdateCount(); // 獲取更新行數 Object parameterObject = boundSql.getParameterObject();KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);return rows;}總結
以上是生活随笔為你收集整理的mybatis-启动源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Win11麦克风测试在哪里(win 10
- 下一篇: (转)mybatis热部署加载*Mapp