<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                Configuration存儲著mybatis運行時所需要的全部配置信息,那么它是如何從mybatis-config.xml轉換過來的呢?實際運行中它又起到什么作用呢?今天我通過一個小例子,結合源碼一步一步探索一下Configuration的解析流程,以便更加深入的了解其運行機制。 # 從Demo開始 下面是一個小小的示例。指定了配置文件mybatis-config.xml,通過SqlSessionFactoryBuilder創建SqlSessionFactory,打開SqlSession獲取Mapper,執行Mapper方法。 ~~~java public static void main(String[] args) throws IOException { // mybatis配置文件 String path = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(path); // 獲取 SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 創建 SqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession()) { CompanyDao dao = sqlSession.getMapper(CompanyDao.class); CompanyDO companyDO = dao.selectById(1, "test"); System.out.println(companyDO); } } 復制代碼 ~~~ 從main方法中我們并沒有看到Configuration的影子,是因為在實際運行中,操作數據庫是使用SqlSession,而SqlSession是由SqlSessionFactory按需創建的,Configuration對象保存在SqlSessionFactory中,當創建SqlSession時會把Configuration傳遞到SqlSession。 # 配置解析流程 示例代碼3-6行就完成了Configuration的解析工作,這個過程發生在方法SqlSessionFactoryBuilder#build()中,進入方法內部會發現實際執行xml解析的是類XMLConfigBuilder。 ~~~java public SqlSessionFactory build(InputStream inputStream) { // 調用重載方法 return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // 創建XMLConfigBuilder對象,這個是Configuration解析類 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 首先把xml解析為Configuration對象,然后創建SqlSessionFactory對象。 // 我們下來主要關心parse()方法 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. } } } 復制代碼 ~~~ 在mybatis中XMLConfigBuilder負責解析mybatis-config.xml,實現配置信息從文件到內存對象的轉換與映射。首先看下構造方法。 ~~~java // 接收配置文件流對象、environment以及通過代碼傳入的屬性配置。 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } 復制代碼 ~~~ XMLConfigBuilder構造方法創建了xml的解析器parser,parser負責處理復雜的xml讀取工作。另外,構造方法還接收了props對象,它會覆蓋從配置文件中解析的同名屬性信息。 parse()方法及parseConfiguration()是解析流程的主干流程,其中,parseConfiguration中逐個完成了xml中各部分的解析,每種配置封裝為了獨立的方法,理解起來比較容易。每個XMLConfigBuilder只能解析一次,重復解析會導致異常。 ~~~java public Configuration parse() { //只能解析一次,否則異常 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // 標記為已解析 parsed = true; //解析configuration節點下的內容 parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析屬性 propertiesElement(root.evalNode("properties")); //解析配置 Properties settings = settingsAsProperties(root.evalNode("settings")); //加載vfs loadCustomVfs(settings); //加載自定義日志 loadCustomLogImpl(settings); //加載類型別名 typeAliasesElement(root.evalNode("typeAliases")); //加載插件 pluginElement(root.evalNode("plugins")); //加載對象工廠 objectFactoryElement(root.evalNode("objectFactory")); //加載對象包裝器工廠 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //加載反射器工廠 reflectorFactoryElement(root.evalNode("reflectorFactory")); //設置配置信息 settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 //加載環境配置 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); //加載類型處理器 typeHandlerElement(root.evalNode("typeHandlers")); //加載mapper mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } 復制代碼 ~~~ 以上每部分配置的加載方法,其內部都會為configuration賦值,最終解析完成所有配置,再由parse方法返回給調用方。下面逐個了解各部分mybatis配置的解析流程。 ## 屬性(properties) 用于定義mybatis運行所需的屬性信息,如數據庫連接相關配置。屬性的定義有三種方式:在mybatis-config.xml#properties中定義屬性、通過外部properties文件定義,然后使用resource方式引入mybatis-config.xml、通過 SqlSessionFactoryBuilder.build() 方法中傳入屬性值。 ~~~java // 入參為root.evalNode("properties"),即properties節點下所有子節點 private void propertiesElement(XNode context) throws Exception { if (context != null) { //讀取所有子節點中配置的屬性信息,所有信息存儲defaults Properties defaults = context.getChildrenAsProperties(); //若以resource或url方式導入外部配置,則加載resource或url中配置信息 String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } //外部配置寫入defaults,這里需要注意:外部配置會覆蓋上面從properties中讀取的配置信息 if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } //從configuration讀取以代碼方式傳入的配置信息,如果有也寫入defaults, //此時也會覆蓋前兩步加載的同名配置 Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } //屬性解析完成,存儲。 parser.setVariables(defaults); configuration.setVariables(defaults); } } 復制代碼 ~~~ 從這個方法的執行流程可知,如果同一個屬性在不同的位置進行重復配置,那么MyBatis 將按照下面的順序來加載: * 首先讀取在 properties 元素體內指定的屬性。 * 然后根據 properties 元素中的 resource 屬性讀取類路徑下屬性文件,或根據 url 屬性指定的路徑讀取屬性文件,并覆蓋之前讀取過的同名屬性。 * 最后讀取作為方法參數傳遞的屬性,并覆蓋之前讀取過的同名屬性。 ## 設置(settings) ### 設置項 這是 MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。通過以下代碼+注釋解釋各個setting及其用途。其中,大部分setting都擁有默認值,實際可按需更改。 ~~~java public class Configuration { //環境配置 protected Environment environment; //是否允許在嵌套語句中使用分頁(RowBounds),默認false protected boolean safeRowBoundsEnabled; //是否允許在嵌套語句中使用結果處理器(ResultHandler),默認true protected boolean safeResultHandlerEnabled = true; //是否開啟駝峰命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn。 protected boolean mapUnderscoreToCamelCase; //開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載。 protected boolean aggressiveLazyLoading; //是否允許單個語句返回多結果集(需要數據庫驅動支持),默認值true protected boolean multipleResultSetsEnabled = true; //允許 JDBC 支持自動生成主鍵,需要數據庫驅動支持。如果設置為 true,將強制使用自動生成主鍵。 // 盡管一些數據庫驅動不支持此特性,但仍可正常工作(如 Derby)。 protected boolean useGeneratedKeys; //使用列標簽代替列名。實際表現依賴于數據庫驅動,具體可參考數據庫驅動的相關文檔,或通過對比測試來觀察。 protected boolean useColumnLabel = true; //全局性地開啟或關閉所有映射器配置文件中已配置的任何緩存,默認true protected boolean cacheEnabled = true; //指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這在依賴于 Map.keySet() 或 null 值進行初始化時比較有用。 // 注意基本類型(int、boolean 等)是不能設置成 null 的。 // 默認false protected boolean callSettersOnNulls; //允許使用方法簽名中的名稱作為語句參數名稱。 //為了使用該特性,你的項目必須采用 Java 8 編譯,并且加上 -parameters 選項。 //默認值true protected boolean useActualParamName = true; //當返回行的所有列都是空時,MyBatis默認返回 null。 當開啟這個設置時,MyBatis會返回一個空實例。 // 請注意,它也適用于嵌套的結果集(如集合或關聯),默認false protected boolean returnInstanceForEmptyRow; //指定 MyBatis 增加到日志名稱的前綴。默認無 protected String logPrefix; //指定 MyBatis 所用日志的具體實現,未指定時將自動查找。 //SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING //默認無 protected Class <? extends Log> logImpl; //指定 VFS 的實現 protected Class <? extends VFS> vfsImpl; //MyBatis 利用本地緩存機制(Local Cache)防止循環引用和加速重復的嵌套查詢。 // 默認值為 SESSION,會緩存一個會話中執行的所有查詢。 若設置值為 STATEMENT,本地緩存將僅用于執行語句,對相同 SqlSession 的不同查詢將不會進行緩存。 // 可選值:SESSION | STATEMENT protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; //當沒有為參數指定特定的 JDBC 類型時,空值的默認 JDBC 類型。 //某些數據庫驅動需要指定列的 JDBC 類型,多數情況直接用一般類型即可,比如 NULL、VARCHAR 或 OTHER。 //默認值:OTHER protected JdbcType jdbcTypeForNull = JdbcType.OTHER; //指定對象的哪些方法觸發一次延遲加載。用逗號分隔的方法列表。 //例如:equals,clone,hashCode,toString protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString")); //設置超時時間,它決定數據庫驅動等待數據庫響應的秒數。默認未設置。 protected Integer defaultStatementTimeout; //為驅動的結果集獲取數量(fetchSize)設置一個建議值。此參數只可以在查詢設置中被覆蓋。默認未設置 protected Integer defaultFetchSize; //配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。 //可選值:SIMPLE,REUSE,BATCH,默認值:SIMPLE protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; //指定 MyBatis 應如何自動映射列到字段或屬性。 // NONE 表示關閉自動映射; // PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 // FULL 會自動映射任何復雜的結果集(無論是否嵌套)。 //默認值:PARTIAL protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL; //指定發現自動映射目標未知列(或未知屬性類型)的行為。 //NONE: 不做任何反應 //WARNING: 輸出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等級必須設置為 WARN) //FAILING: 映射失敗 (拋出 SqlSessionException) //默認值:NONE protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; // 屬性列表 protected Properties variables = new Properties(); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); //每次 MyBatis 創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成實例化工作。 //默認的對象工廠需要做的僅僅是實例化目標類,要么通過默認無參構造方法,要么通過存在的參數映射來調用帶有參數的構造方法。 //如果想覆蓋對象工廠的默認行為,可以通過創建自己的對象工廠來實現。 protected ObjectFactory objectFactory = new DefaultObjectFactory(); protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); //延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置 fetchType 屬性來覆蓋該項的開關狀態。 //默認值:false protected boolean lazyLoadingEnabled = false; protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL protected String databaseId; /** * Configuration factory class. * Used to create Configuration for loading deserialized unread properties. * * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a> */ protected Class<?> configurationFactory; //語言驅動注冊中心 protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); //存儲所有Mapper中的映射聲明,k-v結構 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); //數據緩存,k-v結構 protected final Map<String, Cache> caches = new StrictMap<>("Caches collection"); //存儲所有結果映射,來自Mapper protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection"); //存儲所有參數迎神,來自Mapper protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection"); //存儲已加載的資源文件,如mapper.xml protected final Set<String> loadedResources = new HashSet<>(); //存儲sql片段 protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers"); //存儲未解析完成的聲明 protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>(); //存儲未解析完成的緩存解析 protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>(); //存儲未解析完成的結果映射 protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>(); //存儲未解析完成的方法 protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>(); //…… } 復制代碼 ~~~ ### 加載流程 mybatis提供的配置項很多,為了方便使用,都提供了默認值。多數情況下,我們只需要按需更改即可。settingsAsProperties用于完成setting的配置解析工作: ~~~java private Properties settingsAsProperties(XNode context) { //節點為空,返回空的屬性對象 if (context == null) { return new Properties(); } //讀取所有設置信息,存入props Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class // 通過反射方式檢查props加載的配置信息是否為已知的配置項,如果存在未知配置項,拋出異常。 MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } return props; } 復制代碼 ~~~ 設置加載過程中采用了反射方式檢查配置文件中配置項的合法性,存在未知配置項會導致異常。另外,這個方法把解析到的配置項返回,并沒有直接為configuration設置,而是通過后面的settingsElement方法進行初始化。因為還要繼續加載vfs、logImpl的配置信息。 ## 類型別名 類型別名可為 Java 類型設置一個縮寫名字。 它僅用于 XML 配置,意在降低冗余的全限定類名書寫。例如: ~~~java <typeAliases> <typeAlias type="com.raysonxin.dataobject.CompanyDO" alias="CompanyDO"/> </typeAliases> 復制代碼 ~~~ 別名降低了使用的復雜度,它在類型查找、映射方面起到很大的作用,typeAliasesElement負責完成別名的解析加載工作: ~~~java private void typeAliasesElement(XNode parent) { if (parent != null) { // 逐個遍歷 for (XNode child : parent.getChildren()) { //是否以包名方式配置別名 if ("package".equals(child.getName())) { // 獲取包名 String typeAliasPackage = child.getStringAttribute("name"); //按照包名獲取包下所有類,然后注冊別名:若有注解,則使用注解;否則使用類名首字母小寫作為別名。 configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else {//這是按照類名配置別名的方式 //獲取別名 String alias = child.getStringAttribute("alias"); //獲取類型:全限定類名 String type = child.getStringAttribute("type"); try { //獲取類型信息 Class<?> clazz = Resources.classForName(type); //別名為空,默認類名首字母小寫;不是空,按照alias注冊 if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } } 復制代碼 ~~~ 從代碼可知,mybatis支持兩種別名的注冊方式: * 按照包名注冊:按照指定的包掃描類,若存在別名注解,則使用注解指定的名稱;否則使用類的首字母小寫默認; * 按照類全限定名稱注冊:指定了別名則使用其作為別名,否則使用類的首字母小寫默認; **另外需要注意的是,這是mybatis提供的自定義別名的方式,其實mybatis已經提供了絕大多數常用的別名,具體在Configuration的構造方法中,大家可以自行查看。** ## 插件(plugins) MyBatis 允許我們在映射語句執行過程中的某一點進行攔截調用,這可以很方便的讓我們擴展mybatis的能力。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括: * Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) * ParameterHandler (getParameterObject, setParameters) * ResultSetHandler (handleResultSets, handleOutputParameters) * StatementHandler (prepare, parameterize, batch, update, query) 這些類中方法的細節可以通過查看每個方法的簽名來發現,或者直接查看 MyBatis 發行包中的源代碼。先看下接口Interceptor的定義: ~~~java public interface Interceptor { //攔截接口,這里面實現攔截處理邏輯 Object intercept(Invocation invocation) throws Throwable; //為上述目標增加攔截,增強 Object plugin(Object target); //設置插件屬性 void setProperties(Properties properties); } 復制代碼 ~~~ ### 自定義插件 自定義插件只需實現 Interceptor 接口,并指定想要攔截的方法簽名即可,這里的接口方法和簽名必須與Mybatis源碼中的定義完全一致,下面通過一個例子進行說明。 ~~~java @Intercepts( @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) ) public class CustomInterceptor implements Interceptor { Properties properties = new Properties(); @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("before CustomInterceptor"); Object object = invocation.proceed(); System.out.println("after CustomInterceptor"); return object; } @Override public Object plugin(Object target) { return target instanceof Executor ? Plugin.wrap(target, this) : target; } @Override public void setProperties(Properties properties) { this.properties = properties; } } 復制代碼 ~~~ CustomInterceptor對Executor#query方法進行了攔截,在query前后輸出一些埋點信息(可以替換為自己的業務邏輯);plugin方法實現邏輯是:當目標組件為Executor時使用Plugin.wrap方法會為其增加插件功能。接下來在mybatis-config.xml中進行配置: ~~~xml <plugins> <plugin interceptor="com.raysonxin.plugin.CustomInterceptor"> <property name="name" value="abcd"/> </plugin> </plugins> 復制代碼 ~~~ 看下運行效果: **![image.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e04ba08ff039414ab97508bd57949daf~tplv-k3u1fbpfcp-zoom-1.image)** ### 插件加載與執行流程 直接上代碼:org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement ~~~java //入參為root.evalNode("plugins")節點 private void pluginElement(XNode parent) throws Exception { //節點為空不處理 if (parent != null) { //依次遍歷子節點 for (XNode child : parent.getChildren()) { //獲取插件名稱 String interceptor = child.getStringAttribute("interceptor"); //獲取插件屬性列表 Properties properties = child.getChildrenAsProperties(); //實例化插件對象 Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); //設置插件屬性字段 interceptorInstance.setProperties(properties); //添加插件信息到configurtation configuration.addInterceptor(interceptorInstance); } } } 復制代碼 ~~~ 插件加載流程比較簡單:獲取名稱,獲取屬性,實例化插件,設置插件屬性,最終添加到configuration。XMLConfigBuilder完成插件加載后把插件保存在Configuration#interceptorChain,那實際運行中是如何應用的呢? 如前文所說,mybatis支持在Executor、StatementHandler、ParameterHandler、ResultSetHandler四大組件的若干方法上通過插件增強,mybatis正是在四大組件的創建過程中采用動態代理的方式應用插件的。四大組件的創建方法在Configuration中,如下所示,以newExecutor為例對插件應用過程進行梳理。 ~~~java 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; } 復制代碼 ~~~ 組件對象創建完成后通過interceptorChain.pluginAll()方法依次應用插件,四個方法的邏輯類似。interceptor.plugin()由插件實現,它會調用Plugin#wrap方法進行增強。看下這一段的源碼邏輯: ~~~java public Object pluginAll(Object target) { //循環依次遍歷插件 for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) { this.target = target; this.interceptor = interceptor; this.signatureMap = signatureMap; } public static Object wrap(Object target, Interceptor interceptor) { //獲取插件類的簽名:就是確認插件為哪個方法進行增強 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<?> type = target.getClass(); //獲取插件類實現的所有接口 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { //采用動態代理創建代理類實現,在我們的例子中相當于為Executor對象進行了增強。 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; } 復制代碼 ~~~ 所以,經過Plugin#wrap包裝后的Executor對象其實是一個代理對象,其中包含了Executor原有的邏輯,按照動態代理的原理,我們看下Plugin#invoke的執行邏輯: ~~~java @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //從緩存中查詢緩存的方法 Set<Method> methods = signatureMap.get(method.getDeclaringClass()); //判斷當前方式是否為插件增強的方法 if (methods != null && methods.contains(method)) { //如果是則執行插件的intercept方法,內部會調用原有的method,入口是:Invocation#proceed return interceptor.intercept(new Invocation(target, method, args)); } //未被增強,直接調用原來的方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } 復制代碼 ~~~ 編寫插件需要注意對原有mybatis的影響,防止破壞 MyBatis 的核心模塊。通過 MyBatis 提供的強大機制,使用插件是非常簡單的,只需實現 Interceptor 接口,并指定想要攔截的方法簽名即可。 ## 類型處理器(typeHandler) MyBatis 在設置預處理語句(PreparedStatement)中的參數或從結果集中取出一個值時, 都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。通過TypeHandler接口定義也可以清晰的看到TypeHandler的作用: ~~~java public interface TypeHandler<T> { void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; T getResult(ResultSet rs, String columnName) throws SQLException; T getResult(ResultSet rs, int columnIndex) throws SQLException; T getResult(CallableStatement cs, int columnIndex) throws SQLException; } 復制代碼 ~~~ ### 自定義類處理器 如果默認的類型處理器不能滿足使用需求,可以按照要求自定義類型處理器。 具體做法為:實現 org.apache.ibatis.type.TypeHandler 接口, 或繼承類 org.apache.ibatis.type.BaseTypeHandler, 并且可以(可選地)將它映射到一個 JDBC 類型。 以第二種方式舉例,自定義類型處理器CustomTypeHandler,該處理器對應的java類型是String,jdbc類型是VARCHAR,includeNullJdbcType為true。 ~~~java @MappedJdbcTypes(value = JdbcType.VARCHAR,includeNullJdbcType = true) public class CustomTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } } 復制代碼 ~~~ 然后在mybatis-config.xml中添加如下配置: ~~~xml <typeHandlers> <typeHandler handler="com.raysonxin.typehandler.CustomTypeHandler"/> </typeHandlers> 復制代碼 ~~~ ### 加載流程 類型處理器的注冊過程比較復雜,原因是代碼中包含了許多個重載方法,看上去暈頭轉向,我們先來認識一下TypeHandlerRegistry是如何設計的,直接看代碼。 ~~~java public final class TypeHandlerRegistry { //這個字段存儲了jdbcType到類型處理器的映射,使用EnumMap private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<>(JdbcType.class); //存儲javaType對應的處理器映射,需要根據jdbcType選擇處理器 private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<>(); //未知類型處理器,內部會進一步解析處理器類型并路由 private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this); //存儲所有的類型與類型處理器的映射,jdbcType或者javaType private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<>(); //NULL處理器 private static final Map<JdbcType, TypeHandler<?>> NULL_TYPE_HANDLER_MAP = Collections.emptyMap(); //默認枚舉類型處理器 private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class; //構造方法內默認注冊了常用的類型處理器 public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); //.... } //... } 復制代碼 ~~~ TypeHandlerRegistry內部維護了java類型、jdbc類型與類型處理器之間的關系,分別從不同的角度進行映射: * JdbcType對應的處理器(JDBC\_TYPE\_HANDLER\_MAP):意思是當處理一個jdbc類型時,可以從這里獲取處理器; * javaType對應的處理器(TYPE\_HANDLER\_MAP):這個字段使用了兩層映射。第一層是javaType,代表了該javaType具有的處理器列表;第二層是jdbcType與處理器之間的對應關系。綜合下來,確認一個處理器需要進行最終確認處理器。 * 全類型映射(ALL\_TYPE\_HANDLERS\_MAP):維護了jdbc類型和java類型所對應的所有映射器。 * 空類型處理器和默認枚舉類型處理器。 ~~~java //入參為root.evalNode("typeHandlers")節點 private void typeHandlerElement(XNode parent) { if (parent != null) { //遍歷節點的子節點 for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String typeHandlerPackage = child.getStringAttribute("name"); typeHandlerRegistry.register(typeHandlerPackage); } else { //單個類型處理器注冊的配置方式 //獲取javaType,這個是類型的別名,應該注冊在typeAliasRegistry String javaTypeName = child.getStringAttribute("javaType"); //獲取jdbcType String jdbcTypeName = child.getStringAttribute("jdbcType"); //獲取handler名稱,這是類的全限定名稱 String handlerTypeName = child.getStringAttribute("handler"); //通過javaType獲取其對應的類型,可能為空;若指定了但是沒有找到,會報異常 Class<?> javaTypeClass = resolveClass(javaTypeName); //獲取JdbcType,枚舉類型 JdbcType jdbcType = resolveJdbcType(jdbcTypeName); //獲取處理器對應的類型 Class<?> typeHandlerClass = resolveClass(handlerTypeName); if (javaTypeClass != null) { if (jdbcType == null) { //Case-1:按照java類型、處理器類型注冊 //a、根據typeHandlerClass創建處理器對象typeHandler; //b、獲取typeHandler注解中的jdbcTypes,可能為空 //c、jdbcTypes!=null:注冊javaType-jdbcType-handler,如果注解中includeNullJdbcType=true,注冊空類型; // jdbcTypes==null:僅注冊javaType-handler; typeHandlerRegistry.register(javaTypeClass, typeHandlerClass); } else { //Case-2:按照java類型、jdbc類型、處理器類型注冊 //a、創建typeHandler對象; //b、注冊關聯javaType-jdbcType-handler typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); } } else { //Case-3:按照處理器類型注冊 //a、獲取typeHandlerClass的MappedTypes注解; //b、javaType不為空:按照javatype注冊,內部會獲取MappedJdbcTypes,關聯三者。 //c、這種情況還沒看明白。。。 typeHandlerRegistry.register(typeHandlerClass); } } } } } //Case-1~3,最終都會走到這里 private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) { if (javaType != null) { //從map中查詢已有的javaType對應的處理器 Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); //不存在或者為空,執行初始化與添加操作 if (map == null || map == NULL_TYPE_HANDLER_MAP) { map = new HashMap<>(); TYPE_HANDLER_MAP.put(javaType, map); } //關聯javaType-jdbcType-handler map.put(jdbcType, handler); } //加入總的映射 ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler); } 復制代碼 ~~~ 通過代碼可以發現,JDBC\_TYPE\_HANDLER\_MAP僅僅是在構造方法邏輯中進行了添加,相當于是mybatis默認的處理器。我們自定義的處理器會添加到TYPE\_HANDLER\_MAP、ALL\_TYPE\_HANDLERS\_MAP,雖然有不同的注冊方式,但是殊途同歸,都是通過上述方法實現了注冊。 總結一下: * 通過配置文件自定義的類型處理器,會添加到TYPE\_HANDLER\_MAP、ALL\_TYPE\_HANDLERS\_MAP,而獲取類型處理器是優先使用javaType來查詢的。如果自定義的類型處理器了重復定義了javaType或jdbcType,會對系統默認的處理器產生覆蓋。 * 如果在類型處理器的注解和xml配置中同時聲明了javaType,那么xml中配置的javaType會生效。 ## 映射器(mapper) MyBatis 的真正強大在于它的語句映射,這是它的魔力所在。由于它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 致力于減少使用成本,讓用戶能更專注于 SQL 代碼。——*摘自《XML 映射器》* 映射器是mybatis面向接口編程的利器,它使用動態代理技術,實現了接口與mapper中sql語句的動態綁定,免去了JDBC中手動實現執行Statement的繁雜過程。mybatis提供多種方式支持mapper資源查找:可以使用相對于類路徑的資源引用,或完全限定資源定位符(包括 file:/// 形式的 URL),或類名和包名等。 ### 映射器元素 SQL 映射文件只有很少的幾個頂級元素(按照應被定義的順序列出): * cache?– 該命名空間的緩存配置。 * cache-ref?– 引用其它命名空間的緩存配置。 * resultMap?– 描述如何從數據庫結果集中加載對象,是最復雜也是最強大的元素。 * ~parameterMap?– 老式風格的參數映射。此元素已被廢棄,并可能在將來被移除!請使用行內參數映射。文檔中不會介紹此元素。~ * sql?– 可被其它語句引用的可重用語句塊。 * insert?– 映射插入語句。 * update?– 映射更新語句。 * delete?– 映射刪除語句。 * select?– 映射查詢語句 以上頂級元素還有較多的屬性用于控制其行為,大家可以自行查閱官方文檔,這里不再貼出來了。 ### 加載流程 我們隨著示例及源碼來看下mybatis是如何加載解析mapper的,然后通過另外一篇文章來剖析其執行過程。示例配置信息,示例中定義了CompanyMapper.xml,以資源方式引入到mybatis-config.xml。 ~~~xml <!--mybatis-config.xml--> <mappers> <mapper resource="mapper/CompanyMapper.xml"/> </mappers> <!--CompanyMapper.xml--> <mapper namespace="com.raysonxin.dao.CompanyDao"> <resultMap id="baseResultMap" type="com.raysonxin.dataobject.CompanyDO"> <id column="id" property="id"/> <result column="name" property="name"/> <result column="cpy_type" property="cpyType"/> </resultMap> <sql id="BaseColumns"> id,name,cpy_type </sql> <select id="selectById" resultMap="baseResultMap"> select <include refid="BaseColumns"></include> from company where id= #{id} and name = #{name} </select> </mapper> 復制代碼 ~~~ mapper解析入口為XMLConfigBuilder#mapperElement,通過代碼注釋走下流程。 ~~~java //入參為root.evalNode("mappers"),即示例中的mappers節點,它可包含多個mapper子節點 private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 整包方式添加mapper if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { //這里包含了三種方式:資源導入、資源全限定路徑、類名,分別獲取mapper節點屬性值 //三個值中只能配置一個,否則會報異常(最后的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類進行mapper解析,這里是重點。 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類進行mapper解析,同上。 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."); } } } } } 復制代碼 ~~~ 示例代碼以resource方式引入mapper,按照這條線走下去,創建XMLMapperBuilder后調用其parse方法。這里與XMLConfigBuilder類似,同樣使用XPathParser進行xml解析,另外使用MapperBuilderAssistant構建MappedStatement。 ~~~java 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; } 復制代碼 ~~~ 接下來進入解析流程,即XMLMapperBuilder#parse方法。大體流程是:若資源未被加載過,則解析mapper文件中的元素,把mapper添加到已加載資源,綁定mapper與namespace。無論是否加載過當前資源,都會處理那些之前未完成的ResultMap、CacheRef、Statement。 ~~~java public void parse() { //判斷資源是否已經加載 if (!configuration.isResourceLoaded(resource)) { //這個是mapper解析的核心方法,入參是mapper對應的資源文件,示例中的CompanyMapper.xml configurationElement(parser.evalNode("/mapper")); //添加已加載資源到configuration configuration.addLoadedResource(resource); //這個方法是綁定mapper與namespace bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } 復制代碼 ~~~ 核心解析方式是configurationElement,它依次解析mapper文件中的cache-ref、cache、parameterMap、resultMap、sql、select|insert|update|delete,本次重點分析后面三者的解析過程。 ~~~java private void configurationElement(XNode context) { try { //獲取mapper中的namespace,示例中為:com.raysonxin.dao.CompanyDao String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } //設置builderAssistant的namespace builderAssistant.setCurrentNamespace(namespace); //解析cache-ref內容 cacheRefElement(context.evalNode("cache-ref")); //解析cache內容 cacheElement(context.evalNode("cache")); //解析parameterMap節點 parameterMapElement(context.evalNodes("/mapper/parameterMap")); //解析resultMap節點 resultMapElements(context.evalNodes("/mapper/resultMap")); //解析sql節點 sqlElement(context.evalNodes("/mapper/sql")); //構建statement buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } } 復制代碼 ~~~ #### resultMap 下可以定義多個resultMap,通常我們會把常用的返回字段定義為一個resultMap,方便在查詢語句中引用。我們從resultMapElements開始看下源碼: ~~~java private void resultMapElements(List<XNode> list) throws Exception { for (XNode resultMapNode : list) { try { resultMapElement(resultMapNode); } catch (IncompleteElementException e) { // ignore, it will be retried } } } private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList(), null); } //真正的解析工作從這里開始 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); //獲取resultMap的id屬性,示例中baseResultMap String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); //獲取resultMap的type屬性,后面是默認值設置順序 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); //獲取extends屬性 String extend = resultMapNode.getStringAttribute("extends"); //獲取autoMapping屬性 Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); //獲取type對應的類型對象,首先從typeAliasRegistry獲取,否則按照全路徑限定名稱反射創建 Class<?> typeClass = resolveClass(type); if (typeClass == null) { typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); //依次解析子節點 for (XNode resultChild : resultChildren) { //是否為構造方法 if ("constructor".equals(resultChild.getName())) { //處理構造方法 processConstructorElement(resultChild, typeClass, resultMappings); } //discriminator鑒別器 else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } //其他節點 else { List<ResultFlag> flags = new ArrayList<>(); // 是否為id節點 if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } //構建resultMap:buildResultMappingFromContext resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } } 復制代碼 ~~~ 不考慮復雜的resultMap,我們示例中的節點類型為id和result兩種,我們直接進入方法buildResultMappingFromContext: ~~~java private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception { String property; if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { //獲取property值,對應javaType的字段 property = context.getStringAttribute("property"); } //數據表的字段 String column = context.getStringAttribute("column"); //獲取javaType String javaType = context.getStringAttribute("javaType"); //獲取jdbcType String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); String nestedResultMap = context.getStringAttribute("resultMap", processNestedResultMappings(context, Collections.<ResultMapping> emptyList(), resultType)); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); //類型處理器 String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager")); //獲取javaType類型對象 Class<?> javaTypeClass = resolveClass(javaType); //獲取類型處理器 Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); //jdbc類型枚舉 JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType); //創建ResultMapping對象并返回 return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); } 復制代碼 ~~~ 這個解析過程包含了resultMap高級用法的邏輯,比如association、collections等,我們先從簡單、常用的方式入手。buildResultMappingFromContext最終返回了resultMap中某個字段的映射信息,構建了java類型與數據庫字段的映射關系。這樣,通過遍歷所有字段把resultMap下的所有字段一一映射。 我們再回到方法resultMapElement,遍歷所有節點后得到resultMappings,創建了ResultMapResolver對象。從名稱可以知道,這個類是ResultMap的解析器,它最終把resultMap配置信息轉為mybaits的ResultMap對象并添加到configuration。 #### sql 實際開發中,我們可以使用sql標簽定義那些可復用的sql片段,簡化mapper文件的配置。在mybatis解析sql時,它也只是一個中間過程,它是解析select|update|insert|delete語句的基礎。 ~~~java private void sqlElement(List<XNode> list) { if (configuration.getDatabaseId() != null) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null); } private void sqlElement(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); //拼接namespace,結果為:namespace+"."+id id = builderAssistant.applyCurrentNamespace(id, false); //是否符合datbaseId要求 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { //sqlFragments保存所有sql片段 sqlFragments.put(id, context); } } } 復制代碼 ~~~ #### select|insert|update|delete select|insert|update|delete對于sql語句中的同名命令,是我們使用最頻繁的部分,最終mybatis會把這些標簽轉為MappedStatement,存儲在configuration,同時與Mapper接口方法在運行時綁定。 ~~~java 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); } } } 復制代碼 ~~~ 從代碼可以看到XMLStatementBuilder,它是命令標簽的處理器,核心方法是parseStatementNode。到這里,我們已經見過了XMLConfigBuilder、XMLMapperBuilder。看下處理過程: ~~~java public void parseStatementNode() { //獲取標簽中的id屬性,如:selectById String id = context.getStringAttribute("id"); //databaseId,為空 String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //屬性fetchSize Integer fetchSize = context.getIntAttribute("fetchSize"); //屬性timeout Integer timeout = context.getIntAttribute("timeout"); //屬性parameterMap String parameterMap = context.getStringAttribute("parameterMap"); //屬性parameterType String parameterType = context.getStringAttribute("parameterType"); //解析參數類型信息 Class<?> parameterTypeClass = resolveClass(parameterType); //獲取屬性resultMap String resultMap = context.getStringAttribute("resultMap"); //獲取屬性resultType String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); //獲取屬性statementType,未設置時使用默認值PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); //獲取nodeName,就是標簽的類型,select、insert之類 String nodeName = context.getNode().getNodeName(); //命令類型 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); //判斷是否為select命令 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 parsing //這里解析sql語句中的include標簽 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. //解析selectKey標簽 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) //創建SqlSource,必須在inclue和selectKey解析完成后執行, //內部會根據sql語句是否包含動態標簽創建不同類型的sqlSource,解析sql所需的參數映射信息 //這個地方需要一篇單獨的文章來分析 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)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } //創建MappedStatement,添加到configuration builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } 復制代碼 ~~~ 源碼的過程梳理一下: * 首先解析標簽的基礎屬性,如id、fetchSize等, * 解析sql語句中inclue標簽(把include的內容替換拼接進來), * 處理selcetKey標簽(特定的databaseProvider需要), * 接下來創建SqlSource:這個是mybatis的核心內容,涉及到sql語句中參數提取、動態賦值等,以后在詳細說明; * 處理KeyGenerator; * 使用builderAssistant創建MappedStatement,添加到configuration # 總結 Configuration是mybatis的全局配置類,mybatis運行時所需的一切都緩存在這里,它伴隨mybatis的整個生命周期。理論上講,Configuration是配置文件mybatis-config.xml的代碼映射,mybatis提供了充分的靈活性、可擴展性,方便開發人員通過配置文件改變其運行行為。 本文通過示例及源碼把mybatis-config.xml的解析過程、Configuration配置的大部分內容進行了梳理,通過梳理確實獲益匪淺。由于水平優先,文中很多內容確實沒有描述清楚,隨著學習的不斷深入,再做完善。 作者:碼路印記 鏈接:https://juejin.cn/post/6878979290252361741 來源:掘金 著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看