您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關mybatis核心流程的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
我們先寫個例子。首先要配置一個資源文件 app.properties,配置一些屬性,比如環境變量。
# 環境配置 env=local
再配置 mybatis-config.xml,這是 mybatis 的配置文件,是配置 mybatis 的各種配置信息,主要有:屬性 properties、全局設置 settings、別名 typeAliases、環境 environments、映射 mappers:
<?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> <!-- autoMappingBehavior should be set in each test case --> <!-- 讀取資源文件--> <properties resource="org/apache/ibatis/autoconstructor/app.properties"/> <settings> <!-- 開啟二級緩存--> <setting name="cacheEnabled" value="true"/> <!-- 開啟駝峰式命名--> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!-- 別名配置 --> <typeAliases> <package name="org.apache.ibatis.autoconstructor"/> </typeAliases> <!-- 環境配置 --> <environments default="${env}"> <environment id="local"> <transactionManager type="JDBC"> <property name="" value=""/> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver"/> <!-- 此配置是基于內存連接的--> <property name="url" value="jdbc:hsqldb:mem:automapping"/> <property name="username" value="sa"/> </dataSource> </environment> <environment id="dev"> <transactionManager type="JDBC"> <property name="" value=""/> </transactionManager> <dataSource type="UNPOOLED"> <property name="driver" value="org.hsqldb.jdbcDriver"/> <!-- 此配置是基于內存連接的--> <property name="url" value="jdbc:hsqldb:mem:automapping"/> <property name="username" value="sa"/> </dataSource> </environment> </environments> <mappers> <!-- 掃描指定的映射文件 --> <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/> </mappers> </configuration>
接著配置映射文件 AutoConstructorMapper.xml,它就是寫 SQL 的地方:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper"> <!--開啟二級緩存--> <cache/> <!--<select id="selectOneById" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">--> <select id="selectOneById" resultType="primitiveSubject"> SELECT * FROM subject WHERE id = #{id} </select> </mapper>
然后給出基本的 POJO 和 mapper 接口:
public class PrimitiveSubject implements Serializable { private final int id; private final String name; private final int age; private final int height; private final int weight; private final boolean active; private final Date dt; public PrimitiveSubject(final int id, final String name, final int age, final int height, final int weight, final boolean active, final Date dt) { this.id = id; this.name = name; this.age = age; this.height = height; this.weight = weight; this.active = active; this.dt = dt; } @Override public String toString() { return "PrimitiveSubject{ hashcode="+ this.hashCode() + ", id=" + id + ", name='" + name + '\'' + ", age=" + age + ", height=" + height + ", weight=" + weight + ", active=" + active + ", dt=" + dt + '}'; } } /** * mapper 接口 */ public interface AutoConstructorMapper { PrimitiveSubject selectOneById(int id); }
初始化 SQL 數據 CreateDB.sql
DROP TABLE subject IF EXISTS; DROP TABLE extensive_subject IF EXISTS; CREATE TABLE subject ( id INT NOT NULL, name VARCHAR(20), age INT NOT NULL, height INT, weight INT, active BIT, dt TIMESTAMP ); INSERT INTO subject VALUES (1, 'a', 10, 100, 45, 1, CURRENT_TIMESTAMP), (2, 'b', 10, NULL, 45, 1, CURRENT_TIMESTAMP), (2, 'c', 10, NULL, NULL, 0, CURRENT_TIMESTAMP);
最后編寫測試類,這個測試類中初始化了 SqlSessionFactory,同時裝配了內存數據庫;它通過 sqlSessionFactory 開啟了一個 SqlSession,然后獲取 AutoConstructorMapper 對象,執行了它的 selectOneById 方法:
class AutoConstructorTest { private static SqlSessionFactory sqlSessionFactory; @BeforeAll static void setUp() throws Exception { // create a SqlSessionFactory try ( Reader reader = Resources .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml") ) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } // populate in-memory database BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), "org/apache/ibatis/autoconstructor/CreateDB.sql"); } @Test void selectOneById() { try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // 測試環境 Environment environment = sqlSessionFactory.getConfiguration().getEnvironment(); System.out.println("environment = " + environment.getId()); final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class); PrimitiveSubject ps1 = mapper.selectOneById(1); System.out.println("ps1 = " + ps1); } } }
這樣,一個簡單的例子就編寫完畢了。下面我們開始進入 mybatis 的源碼中,探索下它的內部流程機制。
我們將它的源碼分析分為以下幾個流程:
解析 mybatis-config.xml 文件,構建 Configuration 配置類信息流程;
解析 mapper.xml 進行構建緩存、映射聲明等流程;
創建 SqlSession 流程;
通過 SqlSession 獲取 mapper 接口執行目標方法流程;
下面我們正式開始解析源碼。
這個流程在上面的例子中的單元測試類代碼中有體現,具體的相關代碼如下:
SqlSessionFactory sqlSessionFactory; // ...省略... try ( Reader reader = Resources .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml") ) { sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); } // ...省略...
上面的邏輯是,加載 mybatis-config.xml 文件到一個輸入流中,然后創建一個 SqlSessionFactoryBuilder 對象,進行構建出一個 SqlSessionFactory 實例,這個實例的生命周期非常長,它是隨著應用程序的關閉而關閉的。
我們看下它的源碼:
public class SqlSessionFactoryBuilder { public SqlSessionFactory build(Reader reader) { return build(reader, null, null); } // ...省略無關方法... public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { // 創建一個 XMLConfigBuilder 進行解析流,解析為一個 Configuration 實例 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } // ...省略無關方法... /** * 構建一個 SQLsession 工廠 * @param config * @return */ public SqlSessionFactory build(Configuration config) { // 創建一個默認的 SQLsession 工廠 return new DefaultSqlSessionFactory(config); } }
可以看到,上面的代碼邏輯,主要是創建一個 XMLConfigBuilder 類型的對象,我們看下它的構造器
public XMLConfigBuilder(Reader reader, String environment, Properties props) { this(new XPathParser(reader, 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; }
發現它會創建一個 Configuration 對象,關聯到父類中。看下 Configuration 的構造器:
public Configuration() { // 配置各種基礎類的別名 // 事務管理器 typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); // 數據源工廠 typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); // 緩存類別名 typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); typeAliasRegistry.registerAlias("FIFO", FifoCache.class); typeAliasRegistry.registerAlias("LRU", LruCache.class); typeAliasRegistry.registerAlias("SOFT", SoftCache.class); typeAliasRegistry.registerAlias("WEAK", WeakCache.class); typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); // 日志類別名 typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); // 動態代理別名 typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); // xml 腳本解析器 languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); }
可以看到,它的構造器方法中會注冊一些基礎配置的類的別名,這些別名一般是用在 xml 配置文件中的屬性值,后續會根據別名來解析出對應的實際類型。
回過頭來繼續看 XMLConfigBuilder 的解析方法 parse() 方法,這個方法是把 mybatis 的 xml 文件解析成為一個 Configuration 類型,最后再創建一個 DefaultSqlSessionFactory 類型返回。org.apache.ibatis.builder.xml.XMLConfigBuilder#parse :
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 進行解析 parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { // 解析 properties 屬性 // issue #117 read properties first propertiesElement(root.evalNode("properties")); // 解析設置 setting Properties settings = settingsAsProperties(root.evalNode("settings")); 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")); // 解析數據庫 ID 提供者 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); } }
上面的代碼也很好理解,主要是針對 mybatis-config.xml 文件中的各個標簽元素進行解析:
解析 properties 屬性配置;
解析 setting 屬性配置;
解析 typeAliases 類型別名配置;
解析插件 plugins 配置;
解析 objectFactory 對象工廠配置;
解析 objectWrapperFactory 對象包裝工廠配置;
解析 reflectorFactory 反射工廠配置;
解析 environments 環境配置;
解析 databaseIdProvider 數據庫 ID 提供者配置;
解析 typeHandlers 類型處理器配置;
解析 mappers 映射文件配置。
這些解析內容中,mappers 解析最為重要,我們詳細看下它的解析過程。
解析 mappers 的邏輯在 org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 方法中:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { // 解析 package 屬性 String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { // 解析 resource 屬性 String resource = child.getStringAttribute("resource"); // URL 屬性 String url = child.getStringAttribute("url"); // class 屬性 String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { // resource 不為空,URL 和 class 為空 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) { // URL 不為空,resource 和 class 為空 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 不為空,resource 和 URL 為空 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."); } } } } }
可以看到這里的邏輯是獲取了 mappers 標簽中子標簽 package 和 mapper,獲取它們的 name、url、class、resource 屬性,進行加載解析對應的 mapper.xml 文件。
流程為:
如果 package 標簽存在,就獲取其 name 屬性值,即包名,將它放入 configuration 配置中保存起來, 通過 MapperAnnotationBuilder 類進行解析;
如果 package 不存在,就獲取 mapper 標簽。
獲取它們的 resource、url、class 屬性,這里進行了判斷,這三個屬性只能存在一個;
其中 resource 和 url 是通過 XMLMapperBuilder 實例進行解析的;
class 屬性的值也是會放入到 configuration 配置中進行解析并且保存起來,隨后通過 MapperAnnotationBuilder 類進行解析。
我們這里主要看下 XMLMapperBuilder 類的解析流程。看下它的 parse() 方法,這個方法就是開始了對 mapper.xml 文件進行解析。org.apache.ibatis.builder.xml.XMLMapperBuilder#parse:
/** * 執行解析 mapper.xml 文件 */ public void parse() { if (!configuration.isResourceLoaded(resource)) { // 配置 mapper 根元素 configurationElement(parser.evalNode("/mapper")); // 保存資源路徑 configuration.addLoadedResource(resource); // 構建命令空間映射 bindMapperForNamespace(); } // 解析待定的結果集映射 parsePendingResultMaps(); // 解析待定的緩存引用 parsePendingCacheRefs(); // 解析待定的 SQL 聲明 parsePendingStatements(); }
這里執行了以下幾個解析邏輯:
執行 configurationElement() 方法,解析 mapper 根元素;
保存資源路徑到 configuration 實例中;
執行 bindMapperForNamespace() 方法,根據命名空間加載對應的映射接口;
執行 parsePendingResultMaps() 方法,解析待定的 ResultMap 結果集映射;
執行 parsePendingCacheRefs() 方法,解析待定的 CacheRef 緩存引用;
執行 parsePendingStatements(),解析待定的 Statement SQL 聲明。
這主要的方法是 configurationElement(),我們看下它的邏輯 org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement:
private void configurationElement(XNode context) { try { // 構建命名空間 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.isEmpty()) { throw new BuilderException("Mapper's namespace cannot be empty"); } 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")); // 構建 SQL 語句聲明 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); } }
它主要執行的邏輯是:
構建緩存引用 cache-ref 元素;
構建二級緩存 cache 元素;
構建 parameterMap 元素;
構建 resultMap 元素;
構建 SQL 元素;
構建 SQL 語句聲明(解析 select|insert|update|delete 標簽,這一步最為重要);
接著我們看下它的構建二級緩存的流程。它是在 org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement 方法中實現的:
/** * 構建二級緩存 cache 元素 * * @param context */ private void cacheElement(XNode context) { if (context != null) { // 配置默認的 cache 類型 String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // 過期策略 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // 刷新時間 Long flushInterval = context.getLongAttribute("flushInterval"); // 緩存大小 Integer size = context.getIntAttribute("size"); // 是否只讀,默認是 false,即 boolean readWrite = !context.getBooleanAttribute("readOnly", false); // 是否阻塞,為了解決緩存擊穿問題(同一時刻出現大量的訪問同一個數據的請求) boolean blocking = context.getBooleanAttribute("blocking", false); // 其他屬性 Properties props = context.getChildrenAsProperties(); // 構建緩存 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
注意這里的 cache 標簽,是在 mapper.xml 文件中聲明的。它的邏輯:
獲取 cache 標簽的類型 type 屬性值,默認為 PERPETUAL,它對應 PerpetualCache 類型;
獲取過期策略 eviction 屬性值,默認為 LRU 最近最少過期策略,它對應 LruCache 類型;
獲取刷新時間 flushInterval 屬性值;
獲取緩存大小 size 屬性值;
獲取是否只讀 readOnly 屬性值,默認是 false,如果設置了 true,那么就需要 POJO 實現 Serializable 接口;
獲取是否阻塞 blocking 屬性值,這是用來解決緩存擊穿問題的,稍后將構建緩存時會具體講解;
獲取以及其他屬性;
通過調用 MapperBuilderAssistant 映射構建器輔助器的 useNewCache() 方法來構建緩存。
我們看下 MapperBuilderAssistant 映射構建器輔助器的 useNewCache() 方法,org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache:
public Cache useNewCache(Class<? extends Cache> typeClass, Class<? extends Cache> evictionClass, Long flushInterval, Integer size, boolean readWrite, boolean blocking, Properties props) { // 緩存構建器 Cache cache = new CacheBuilder(currentNamespace) // 這里默認使用 PerpetualCache 緩存類型實現,具體的緩存實現類 .implementation(valueOrDefault(typeClass, PerpetualCache.class)) // 添加 LruCache 緩存裝飾器 .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) // 開始構建緩存 .build(); // 把緩存放入配置類中 configuration.addCache(cache); currentCache = cache; return cache; }
這里又用到了 CacheBuilder 緩存構建器來構建緩存,,可以看到緩存使用 PerpetualCache 類型實現,并且添加了一個 添加 LruCache 緩存裝飾器來裝飾緩存,看下它的 build 方法 org.apache.ibatis.mapping.CacheBuilder#build:
/** * 構建一個緩存 * * @return */ public Cache build() { // 設置默認實現類,和初始化的裝飾器 LruCache setDefaultImplementations(); // 通過反射創建一個 PerpetualCache 對象 Cache cache = newBaseCacheInstance(implementation, id); // 設置緩存屬性 setCacheProperties(cache); // 不要為自定義的緩存應用裝飾器 // issue #352, do not apply decorators to custom caches if (PerpetualCache.class.equals(cache.getClass())) { // 如果是 PerpetualCache 類型的緩存,那么就給它設置裝飾器 for (Class<? extends Cache> decorator : decorators) { // 創建一個緩存裝飾器實例 cache = newCacheDecoratorInstance(decorator, cache); setCacheProperties(cache); } // 設置其他標準的裝飾器 cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { cache = new LoggingCache(cache); } return cache; } /** * 設置緩存的默認實現 */ private void setDefaultImplementations() { if (implementation == null) { implementation = PerpetualCache.class; if (decorators.isEmpty()) { decorators.add(LruCache.class); } } } /** * 設置標準的緩存裝飾器 * * @param cache * @return */ private Cache setStandardDecorators(Cache cache) { try { // 獲取緩存的元對象 MetaObject metaCache = SystemMetaObject.forObject(cache); // 設置元數據的信息 if (size != null && metaCache.hasSetter("size")) { metaCache.setValue("size", size); } if (clearInterval != null) { // 根據清除間隔屬性,設置定時刷新緩存的裝飾器緩存 ScheduledCache cache = new ScheduledCache(cache); ((ScheduledCache) cache).setClearInterval(clearInterval); } if (readWrite) { // 根據是否可讀寫屬性,設置序列化緩存裝飾器 SerializedCache cache = new SerializedCache(cache); } // 設置日志緩存裝飾器 LoggingCache cache = new LoggingCache(cache); // 設置同步緩存裝飾器 SynchronizedCache cache = new SynchronizedCache(cache); if (blocking) { // 根據是否阻塞,設置阻塞緩存裝飾器 cache = new BlockingCache(cache); } return cache; } catch (Exception e) { throw new CacheException("Error building standard cache decorators. Cause: " + e, e); } }
梳理下這里的邏輯:
執行 setDefaultImplementations() 方法,如果沒有實現類,那就設置默認的實現類 PerpetualCache,添加裝飾器 LruCache;
通過反射創建一個 Cache 實現類的實例;
如果緩存實例是 PerpetualCache 類型的,則遍歷裝飾器集合,通過反射創建裝飾器實例,并且執行 setStandardDecorators() 方法為緩存實例設置其他標準的裝飾器;這里的邏輯有:
獲取緩存的元對象,這是 size 屬性;
根據 flushInterval 刷新間隔屬性,設置 ScheduledCache 定時刷新緩存的裝飾器對緩存進行裝飾;
根據 readWrite 是否可讀寫屬性,設置 SerializedCache 序列化緩存裝飾器對緩存進行裝飾;
設置 LoggingCache 日志緩存裝飾器對緩存進行裝飾;
設置 SynchronizedCache 同步緩存裝飾器對緩存進行裝飾;
根據 blocking 是否阻塞屬性,設置 BlockingCache 阻塞緩存裝飾器對緩存進行裝飾;
如果緩存實例不是 LoggingCache 類型,那就設置 LoggingCache 日志緩存裝飾器對緩存進行裝飾;
返回緩存實例。
可以看到這里是創建了二級緩存 Cache 接口實例,這里有很多 Cache 裝飾器,下面我們深入其中研究下。
我們先看下 Cache 接口的類圖:
可以看到 Cache 接口有多個實現。
上面構建緩存的流程中,我們看到了它首先會創建具體的真正存數據的緩存實例 PerpetualCache,看下它的實現:
/** * 永久緩存,用于一級緩存 * * @author Clinton Begin */ public class PerpetualCache implements Cache { private final String id; /** * 使用一個 hashmap 作為緩存 */ private final Map<Object, Object> cache = new HashMap<>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); } @Override public boolean equals(Object o) { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } if (this == o) { return true; } if (!(o instanceof Cache)) { return false; } Cache otherCache = (Cache) o; return getId().equals(otherCache.getId()); } @Override public int hashCode() { if (getId() == null) { throw new CacheException("Cache instances require an ID."); } return getId().hashCode(); } }
它有兩個屬性,String 類型的 id 屬性、和一個 HashMap 類型的 cache 屬性,可以看到查詢的數據會存儲到這個 cache 屬性中。
接著它會創建一個 LruCache 緩存對 PerpetualCache 實例進行包裝,LruCache 的實現如下:
/** * Lru (least recently used) cache decorator. * * @author Clinton Begin */ public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; private Object eldestKey; public LruCache(Cache delegate) { this.delegate = delegate; setSize(1024); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } public void setSize(final int size) { // 重寫 LinkedHashMap 的 removeEldestEntry() 方法,實現 LRU 算法 keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { private static final long serialVersionUID = 4267176411845948333L; @Override protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { boolean tooBig = size() > size; if (tooBig) { eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject(Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject(Object key) { // 這里獲取 key 是為了讓 key 保持最新,不至于被 LRU 清除掉 keyMap.get(key); // touch return delegate.getObject(key); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); keyMap.clear(); } private void cycleKeyList(Object key) { keyMap.put(key, key); if (eldestKey != null) { delegate.removeObject(eldestKey); eldestKey = null; } } }
可以看到,它持有一個緩存實例 Cache 類型的 delegate 屬性,這是一個委派的緩存實例;還有持有一個重寫了 LinkedHashMap 類的 keyMap 屬性,它重寫了 removeEldestEntry() 方法,實現了 LRU 最近最少使用算法;同時還持有一個年級最長的 Object 類型的 key。
當有新的數據要放入緩存時,并且 keyMap 中的數據已經滿了的時候,會把年級最長的緩存 key 刪除掉,再存入新的數據。
接著看 ScheduledCache 定時刷新緩存裝飾器:
public class ScheduledCache implements Cache { private final Cache delegate; protected long clearInterval; protected long lastClear; public ScheduledCache(Cache delegate) { this.delegate = delegate; this.clearInterval = TimeUnit.HOURS.toMillis(1); this.lastClear = System.currentTimeMillis(); } public void setClearInterval(long clearInterval) { this.clearInterval = clearInterval; } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { clearWhenStale(); return delegate.getSize(); } @Override public void putObject(Object key, Object object) { clearWhenStale(); delegate.putObject(key, object); } @Override public Object getObject(Object key) { return clearWhenStale() ? null : delegate.getObject(key); } @Override public Object removeObject(Object key) { clearWhenStale(); return delegate.removeObject(key); } @Override public void clear() { lastClear = System.currentTimeMillis(); delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } private boolean clearWhenStale() { if (System.currentTimeMillis() - lastClear > clearInterval) { clear(); return true; } return false; } }
這個類同樣也是持有一個委派的 Cache 實例,并且它提供了一個 clearWhenStale() 方法。這個方法會根據當前時間、上次清理的時間,與配置的刷新的間隔時間進行判斷,是否需要清理緩存。與當前時間,在獲取緩存數據、保存緩存數據、移除緩存數據、查詢緩存數據數量的時候進行調用。
接著看 SerializedCache 類:
public class SerializedCache implements Cache { private final Cache delegate; public SerializedCache(Cache delegate) { this.delegate = delegate; } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public void putObject(Object key, Object object) { if (object == null || object instanceof Serializable) { delegate.putObject(key, serialize((Serializable) object)); } else { throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object); } } @Override public Object getObject(Object key) { Object object = delegate.getObject(key); return object == null ? null : deserialize((byte[]) object); } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } private byte[] serialize(Serializable value) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(value); oos.flush(); return bos.toByteArray(); } catch (Exception e) { throw new CacheException("Error serializing object. Cause: " + e, e); } } private Serializable deserialize(byte[] value) { Serializable result; try (ByteArrayInputStream bis = new ByteArrayInputStream(value); ObjectInputStream ois = new CustomObjectInputStream(bis)) { result = (Serializable) ois.readObject(); } catch (Exception e) { throw new CacheException("Error deserializing object. Cause: " + e, e); } return result; } public static class CustomObjectInputStream extends ObjectInputStream { public CustomObjectInputStream(InputStream in) throws IOException { super(in); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { return Resources.classForName(desc.getName()); } } }
它是一個序列化緩存裝飾器,用于在保存數據時,把數據序列化成 byte[] 數組,然后把 byte[] 數組保存到委派的緩存實例中去,在查詢數據時,再把查詢出來的數據反序列化為對應的對象。這里要求保存的數據類要實現 Serializable 接口。
接著看 LoggingCache 類型:
public class LoggingCache implements Cache { private final Log log; private final Cache delegate; protected int requests = 0; protected int hits = 0; public LoggingCache(Cache delegate) { this.delegate = delegate; this.log = LogFactory.getLog(getId()); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public void putObject(Object key, Object object) { delegate.putObject(key, object); } @Override public Object getObject(Object key) { requests++; final Object value = delegate.getObject(key); if (value != null) { hits++; } if (log.isDebugEnabled()) { log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio()); } return value; } @Override public Object removeObject(Object key) { return delegate.removeObject(key); } @Override public void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } private double getHitRatio() { return (double) hits / (double) requests; } }
這個緩存裝飾器的功能就是在查詢緩存的時候打印日志,會根據緩存的請求次數與實際命中的次數計算出的命中率,并且打印出來。
接著看 SynchronizedCache 類:
public class SynchronizedCache implements Cache { private final Cache delegate; public SynchronizedCache(Cache delegate) { this.delegate = delegate; } @Override public String getId() { return delegate.getId(); } @Override public synchronized int getSize() { return delegate.getSize(); } @Override public synchronized void putObject(Object key, Object object) { delegate.putObject(key, object); } @Override public synchronized Object getObject(Object key) { return delegate.getObject(key); } @Override public synchronized Object removeObject(Object key) { return delegate.removeObject(key); } @Override public synchronized void clear() { delegate.clear(); } @Override public int hashCode() { return delegate.hashCode(); } @Override public boolean equals(Object obj) { return delegate.equals(obj); } }
它是一個實現同步功能的緩存裝飾器,在調用查詢緩存、保存緩存、刪除緩存、清空緩存方法時進行同步,防止多線程同時操作。
我們看最后一個緩存裝飾器 BlockingCache:
/** * 一個簡單的阻塞裝飾器。 * 一個簡單的低效的 EhCache's BlockingCache 裝飾器。當元素不存在緩存中的時候,它設置一個鎖。 * 這樣其他線程將會等待,直到元素被填充,而不是直接訪問數據庫。 * 本質上,如果使用不當,它將會造成死鎖。 * * <p>Simple blocking decorator * * <p>Simple and inefficient version of EhCache's BlockingCache decorator. * It sets a lock over a cache key when the element is not found in cache. * This way, other threads will wait until this element is filled instead of hitting the database. * * <p>By its nature, this implementation can cause deadlock when used incorrecly. * * @author Eduardo Macarron * */ public class BlockingCache implements Cache { private long timeout; private final Cache delegate; private final ConcurrentHashMap<Object, CountDownLatch> locks; public BlockingCache(Cache delegate) { this.delegate = delegate; this.locks = new ConcurrentHashMap<>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public void putObject(Object key, Object value) { try { delegate.putObject(key, value); } finally { releaseLock(key); } } @Override public Object getObject(Object key) { // 獲取鎖 acquireLock(key); // 獲取對象 Object value = delegate.getObject(key); if (value != null) { // 獲取的數據不為空,釋放鎖 releaseLock(key); } // 如果 value 為空,則一直不釋放鎖,讓其他查詢此 key 的線程永久阻塞,直到該 key 對應的 value 被添加到緩存中,或者調用刪除 key 操作,才會釋放鎖。 // 這樣的操作是用于解決緩存穿透問題,防止大量請求訪問一個目前不存在的數據 return value; } @Override public Object removeObject(Object key) { // despite of its name, this method is called only to release locks releaseLock(key); return null; } @Override public void clear() { delegate.clear(); } private void acquireLock(Object key) { // 創建一個倒計時閉鎖 CountDownLatch newLatch = new CountDownLatch(1); while (true) { // 根據給定的 key,放入對應的閉鎖 // 如果 key 對應的閉鎖不存在,則放入閉鎖,如果存在則不放入,返回以前的值 CountDownLatch latch = locks.putIfAbsent(key, newLatch); if (latch == null) { // latch 為 null 說明放入成功,則退出 break; } // latch 不為空,說已經有線程放入了 key 對應的閉鎖,那就讓閉鎖阻塞 await,直到閉鎖被放入它的線程解鎖 try { if (timeout > 0) { boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS); if (!acquired) { throw new CacheException( "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId()); } } else { latch.await(); } } catch (InterruptedException e) { throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e); } } } /** * 釋放鎖,它會在保存對象、查詢到對象、移除對象時進行調用 * * @param key */ private void releaseLock(Object key) { // 釋放一個鎖 CountDownLatch latch = locks.remove(key); if (latch == null) { throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen."); } // 倒計時 latch.countDown(); } public long getTimeout() { return timeout; } public void setTimeout(long timeout) { this.timeout = timeout; } }
這個類是借助了 CountDownLatch 閉鎖實現了先阻塞操作。當一個線程嘗試獲取緩存數據時,會創建一個 CountDownLatch,然后再去獲取數據,當獲取的數據不為空,就把這個 CountDownLatch 刪除,否則不刪除閉鎖,返回空數據。
這樣其他線程獲取相同 key 對應的緩存時,會拿到這個 CountDownLatch,然后調用它的 await() 方法,該線程就會被阻塞起來,直到這個 CountDownLatch 執行了 countDown() 方法。
當 key 對應的數據被獲取到、被刪除、被重新填入時,會調用到 CountDownLatch 的 countDown() 方法,喚醒其他被該閉鎖阻塞的線程。
這樣做的目的是為了防止緩存擊穿。在一個 session 當訪問一個數據庫中一直不存在的數據時,會觸發一次數據庫查詢,此時當 session 還沒有提交事務時,此時出現了大量的 session 也是查詢該 key 對應的數據,這樣就會導致它們都會查詢數據庫,可想而知,后來這些 session 的查詢數據庫行為是無效的,而且如果此時 session 過多,可能會打死數據庫。
為了避免這樣的情況,為一個 key 增加一個閉鎖,阻塞那些獲取該數據的線程,直到數據被填充或釋放鎖才能被喚醒。
這樣的做是比較低效的,容易引發死鎖,比如一個線程如果一直訪問緩存中不存在,并且數據庫中也不存在的數據時,會創建一個閉鎖,查詢數據結束也不會釋放鎖。其他獲取該 key 數據的線程訪問時將會永久的阻塞,嚴重的消耗的系統資源。
這個類一般是不用的,cache 元素中的 block 屬性默認是 false。
上述就是緩存裝飾器的全部的介紹了,上面的這些緩存裝飾器是使用了適配器模式,如下圖:
這樣設計的好處是,根據各個功能設計出各個裝飾器,讓它們各司其職。
接著看構建 SQLStatement 邏輯,它通過調用 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法來執行。
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
/** * 從上下文構建狀態 * * @param list */ 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) { // 遍歷所有的 select、insert、update、delete 的語句 for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 解析 SQL 語句 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { // 添加不完整的聲明 configuration.addIncompleteStatement(statementParser); } } }
可以看到,這里獲取了 select|insert|update|delete 這些元素,然后遍歷,通過創建一個 XMLStatementBuilder 類,調用了它的 parseStatementNode() 方法來進行解析,說明一個 select|insert|update|delete 語句對應著一個 XMLStatement,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode:
/** * 解析增刪改查 SQL 語句聲明,一個增刪改查 SQL 語句就對應一個 MappedStatement */ public void parseStatementNode() { // SQL 的 ID 屬性 String id = context.getStringAttribute("id"); // 數據庫 ID String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 節點名稱 String nodeName = context.getNode().getNodeName(); // 根據節點名稱解析 SQL 的類型:增刪改查 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); // 是否為查詢類型 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; // 是否刷新緩存,除了 select 類型的 SQL 預計,執行的時候都會刷新緩存 boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); // 是否使用緩存,默認不填寫時是使用緩存的,如果是 select 類型,則默認是啟用緩存 boolean useCache = context.getBooleanAttribute("useCache", isSelect); // 結果排序,false boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // 解析 includes // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // 解析參數類型 String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 解析語言驅動 String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // 解析查詢尋的 key // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // 解析 selectKey // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) 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; } // 創建數據源 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); // 聲明類型,默認是 PREPARED 類型,預裝配模式 StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); // fetchSize Integer fetchSize = context.getIntAttribute("fetchSize"); // 超時屬性 Integer timeout = context.getIntAttribute("timeout"); // 參數映射 String parameterMap = context.getStringAttribute("parameterMap"); // 結果類型 String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); // 結果映射 String resultMap = context.getStringAttribute("resultMap"); // 結果集類型 String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } // key 屬性 String keyProperty = context.getStringAttribute("keyProperty"); // key 列 String keyColumn = context.getStringAttribute("keyColumn"); // 結果集 String resultSets = context.getStringAttribute("resultSets"); // 構建映射聲明對象 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
可以看到它的邏輯:
獲取元素的 id 屬性、 databaseId 屬性;
根據節點名稱解析 SQL 命令類型(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH);
獲取元素的是否查詢類型 isSelect、是否刷新緩存 isSelect、是否使用緩存 isSelect、是否對結果排序 resultOrdered;
解析 include 元素節點;
解析元素的 parameterType 屬性、解析語言驅動 lang 屬性、解析 selectKey;
創建 keyGenerator;
創建數據源 sqlSource;
解析 StatementType 類型,默認是 PREPARED 類型;
獲取 fetchSize、timeout 超時屬性、parameterMap 參數映射、resultType 結果類型、resultMap 結果集、resultSetType 結果集類型、
獲取元素的 keyProperty 屬性、keyColumn、resultSets
通過 MapperBuilderAssistant 映射構建器輔助器調用 addMappedStatement() 方法,創建并添加映射 Statement。
我們看下 org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement() 方法:
public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } // 解析聲明 ID 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 中 configuration.addMappedStatement(statement); return statement; } public String applyCurrentNamespace(String base, boolean isReference) { if (base == null) { return null; } if (isReference) { // is it qualified with any namespace yet? if (base.contains(".")) { return base; } } else { // is it qualified with this namespace yet? if (base.startsWith(currentNamespace + ".")) { return base; } if (base.contains(".")) { throw new BuilderException("Dots are not allowed in element names, please remove it from " + base); } } // 格式為:命令空間 + "." + base return currentNamespace + "." + base; }
這里的邏輯:
根據命令空間以及元素 ID 生成一個 MappedStatement 的 ID 屬性;
創建一個 MappedStatement.Builder 實例構建 MappedStatement 實例;
添加到 configuration 實例中,返回 MappedStatement 實例。
這個 MappedStatement 的生命周期是和 configuration 一樣,也是和應用程序的生命周期一樣。
這個方法是根據 mapper.xml 中的命名空間來注冊對應的 Mapper 接口類,org.apache.ibatis.builder.xml.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 && !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#loadXmlResource // 保存命令空間 configuration.addLoadedResource("namespace:" + namespace); // 保存映射,這里進行了注冊 configuration.addMapper(boundType); } } }
邏輯:
首先獲取了命令空間值,然后加載這個類型,得到的就是對應的聲明的 Mapper 接口;
保存命令空間到 Configuration 配置中;
把 Mapper 接口注冊到 Configuration 中。
我們再看下 configuration.addMapper(boundType);
這個邏輯,org.apache.ibatis.session.Configuration#addMapper:
// org.apache.ibatis.session.Configuration#addMapper: public <T> void addMapper(Class<T> type) { // mapperRegistry 是 MapperRegistry 類型 mapperRegistry.addMapper(type); }
里邊又調用了 org.apache.ibatis.binding.MapperRegistry#addMapper() 方法:
public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 添加一個映射器代理工廠 knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. // 映射注解構建器 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
我們看到了這里的邏輯:
把要注冊的類保存到 Map<Class<?>, MapperProxyFactory<?>> 類型的 knownMappers
屬性中,它的 key 為注冊的類型,value 為 MapperProxyFactory 映射代理工廠類型實例;
創建一個 MapperAnnotationBuilder 映射注解解析器,對目標類型進行解析。
我們看下這個類:
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethodInvoker> getMethodCache() { return methodCache; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
這個類中維護目標接口類型信息、方法與映射方法執行器屬性。
它提供了創建實例方法 newInstance(),通過 JDK 的動態代理對象創建一個目標接口的代理對象。
上面 JDK 動態代理對象時候,傳入了一個 MapperProxy 類型的參數,它的實現為:
/** * 方法代理器,實現了 JDK 動態代理的執行處理器 InvocationHandler 接口 * * @author Clinton Begin * @author Eduardo Macarron */ public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -4724728412955527868L; private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC; private static final Constructor<Lookup> lookupConstructor; private static final Method privateLookupInMethod; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethodInvoker> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } static { Method privateLookupIn; try { privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class); } catch (NoSuchMethodException e) { privateLookupIn = null; } privateLookupInMethod = privateLookupIn; Constructor<Lookup> lookup = null; if (privateLookupInMethod == null) { // JDK 1.8 try { lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); lookup.setAccessible(true); } catch (NoSuchMethodException e) { throw new IllegalStateException( "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.", e); } catch (Exception e) { lookup = null; } } lookupConstructor = lookup; } /** * 動態代理執行器的 invoke 方法 * * @param proxy * @param method * @param args * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 調用 MapperMethodInvoker 映射方法執行器 return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372 // It should be removed once the fix is backported to Java 8 or // MyBatis drops Java 8 support. See gh-1929 // 從方法緩存中獲取映射方法執行器 MapperMethodInvoker invoker = methodCache.get(method); if (invoker != null) { return invoker; } // 創建一個新的方法執行器,并放入 methodCache 緩存中 return methodCache.computeIfAbsent(method, m -> { if (m.isDefault()) { // 如果方法是一個接口的 default 方法,那就創建一個 DefaultMethodInvoker 類型 try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { // 否則就創建普通的 PlainMethodInvoker 類型執行器 return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } private MethodHandle getMethodHandleJava9(Method method) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Class<?> declaringClass = method.getDeclaringClass(); return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial( declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass); } private MethodHandle getMethodHandleJava8(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException { final Class<?> declaringClass = method.getDeclaringClass(); return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass); } interface MapperMethodInvoker { Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable; } private static class PlainMethodInvoker implements MapperMethodInvoker { private final MapperMethod mapperMethod; public PlainMethodInvoker(MapperMethod mapperMethod) { super(); this.mapperMethod = mapperMethod; } /** * JDK 動態代理對象的的處理器方法 * * @param proxy * @param method * @param args * @param sqlSession * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { // 執行目標方法 return mapperMethod.execute(sqlSession, args); } } private static class DefaultMethodInvoker implements MapperMethodInvoker { private final MethodHandle methodHandle; public DefaultMethodInvoker(MethodHandle methodHandle) { super(); this.methodHandle = methodHandle; } @Override public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable { // 通過 MethodHandle 方法處理器,綁定代理對象,執行方法 return methodHandle.bindTo(proxy).invokeWithArguments(args); } }
再看下它的類圖:
它實現了 InvocationHandler 接口的 invoke() 方法,里邊主要的邏輯是:
調用 cachedInvoker() 方法,創建一個 MapperMethodInvoker;
先從 methodCache 緩存中獲取,有的話直接返回;
methodCache 緩存沒有的話,則創建一個 PlainMethodInvoker 類型的執行器,這個構造器會被傳入一個 org.apache.ibatis.binding.MapperMethod 類型對象。
調用 MapperMethodInvoker 實例的 invoke() 執行目標方法,實際最終會執行 MapperMethod 實例的 execute() 方法。
我們看下 MapperMethod 類:
/** * 映射方法 * * @author Clinton Begin * @author Eduardo Macarron * @author Lasse Voss * @author Kazuki Shimizu */ public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { // SQL 命令 this.command = new SqlCommand(config, mapperInterface, method); // 方法簽名 this.method = new MethodSignature(config, mapperInterface, method); } /** * 執行方法 * * @param sqlSession * @param args * @return */ public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { // 新增類型 Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { // 修改 Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { // 刪除 Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // 查詢 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { // 返回多條 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 返回 map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 返回游標 result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: // 刷新 result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } private void executeWithResultHandler(SqlSession sqlSession, Object[] args) { MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName()); if (!StatementType.CALLABLE.equals(ms.getStatementType()) && void.class.equals(ms.getResultMaps().get(0).getType())) { throw new BindingException("method " + command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation," + " or a resultType attribute in XML so a ResultHandler can be used as a parameter."); } Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args)); } else { sqlSession.select(command.getName(), param, method.extractResultHandler(args)); } } /** * 查詢多條記錄 * * @param sqlSession * @param args * @param <E> * @return */ private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; // 轉換參數 Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { // 有行綁定 RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectList(command.getName(), param, rowBounds); } else { result = sqlSession.selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; } private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) { Cursor<T> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectCursor(command.getName(), param, rowBounds); } else { result = sqlSession.selectCursor(command.getName(), param); } return result; } private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) { Map<K, V> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds); } else { result = sqlSession.selectMap(command.getName(), param, method.getMapKey()); } return result; } // ...省略無關方法...
重點看下它 execute() 方法邏輯:
判斷 SQL 執行類型:insert、update、delete、select;
根據執行類型最終都會調用 SqlSession 的對應方法,而 SqlSession 的對應方法內部最終會調用 Executor 的對應方法。
上面我們講了解析 mybatis-config.xml 以及 mapper.xml 的流程,現在我們來看下獲取一個 SqlSession 的流程。
從 1. 例子的單元測類中可以看到,它是通過 SqlSession sqlSession = sqlSessionFactory.openSession()
來獲取一個 SqlSession,sqlSessionFactory.openSession 是 DefaultSqlSessionFactory 類型的,我們看下它的 openSession() 方法,org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession():
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } /** * 打開一個 session * * @param execType * @param level * @param autoCommit * @return */ 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(); } } /** * 從環境信息中創建一個事務工廠 * * @param environment * @return */ private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) { if (environment == null || environment.getTransactionFactory() == null) { // 創建默認的管理的事務工廠 return new ManagedTransactionFactory(); } // 從環境中獲取事務工廠 return environment.getTransactionFactory(); } /** * 關閉事務 * * @param tx */ private void closeTransaction(Transaction tx) { if (tx != null) { try { tx.close(); } catch (SQLException ignore) { // Intentionally ignore. Prefer previous error. } } }
可以看到,它的主要流程為:
獲取環境 Environment 信息;
獲取一個 TransactionFactory 事務工廠實例;
通過事務工廠創建一個事務 Transaction 實例;
通過配置類創建一個 Executor 執行器;
創建一個 DefaultSqlSession 對象返回;
遇到異常關閉事務。
因為我們在 mybatis-config.xml 中配置了環境信息 environment,其中 transactionManager 元素的 type 為 JDBC ,所以 它會獲取到的事務工廠為 JdbcTransactionFactory 類型。
然后通過它來創建了一個事務,org.apache.ibatis.transaction.TransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean):
/** * Creates {@link JdbcTransaction} instances. * * @author Clinton Begin * * @see JdbcTransaction */ public class JdbcTransactionFactory implements TransactionFactory { @Override public Transaction newTransaction(Connection conn) { return new JdbcTransaction(conn); } @Override public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); } }
我們看下 newTransaction() 方法返回的 JdbcTransaction 類型:
它的實現:
/** * {@link Transaction} that makes use of the JDBC commit and rollback facilities directly. * It relies on the connection retrieved from the dataSource to manage the scope of the transaction. * Delays connection retrieval until getConnection() is called. * Ignores commit or rollback requests when autocommit is on. * * @author Clinton Begin * * @see JdbcTransactionFactory */ public class JdbcTransaction implements Transaction { private static final Log log = LogFactory.getLog(JdbcTransaction.class); protected Connection connection; protected DataSource dataSource; protected TransactionIsolationLevel level; protected boolean autoCommit; public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) { dataSource = ds; level = desiredLevel; autoCommit = desiredAutoCommit; } public JdbcTransaction(Connection connection) { this.connection = connection; } @Override public Connection getConnection() throws SQLException { if (connection == null) { openConnection(); } return connection; } @Override public void commit() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); } } @Override public void rollback() throws SQLException { if (connection != null && !connection.getAutoCommit()) { if (log.isDebugEnabled()) { log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); } } @Override public void close() throws SQLException { if (connection != null) { resetAutoCommit(); if (log.isDebugEnabled()) { log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); } } protected void setDesiredAutoCommit(boolean desiredAutoCommit) { try { if (connection.getAutoCommit() != desiredAutoCommit) { if (log.isDebugEnabled()) { log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); } } catch (SQLException e) { // Only a very poorly implemented driver would fail here, // and there's not much we can do about that. throw new TransactionException("Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). " + "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e); } } protected void resetAutoCommit() { try { if (!connection.getAutoCommit()) { // MyBatis does not call commit/rollback on a connection if just selects were performed. // Some databases start transactions with select statements // and they mandate a commit/rollback before closing the connection. // A workaround is setting the autocommit to true before closing the connection. // Sybase throws an exception here. if (log.isDebugEnabled()) { log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(true); } } catch (SQLException e) { if (log.isDebugEnabled()) { log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e); } } } protected void openConnection() throws SQLException { if (log.isDebugEnabled()) { log.debug("Opening JDBC Connection"); } connection = dataSource.getConnection(); if (level != null) { connection.setTransactionIsolation(level.getLevel()); } setDesiredAutoCommit(autoCommit); } @Override public Integer getTimeout() throws SQLException { return null; } }
這是一個jdbc 事務,里邊提供了一些獲取數據庫連接、提交事務、回滾、關閉事務操作。
接著通過 configuration 創建一個執行器 Executor,org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType):
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) { // 創建一個 CachingExecutor 類型,使用裝飾器模式 executor = new CachingExecutor(executor); } // 添加攔截器,這里用戶可以實現自定義的攔截器 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
這里的邏輯:
判斷參數 ExecutorType 的類型,根據它的類型來創建不同的執行器,默認是 SIMPLE 類型;
ExecutorType.BATCH 類型,則創建 BatchExecutor 執行器;
ExecutorType.REUSE 類型,則創建 ReuseExecutor 執行器;
否則創建 SimpleExecutor 執行器;
如果啟用了二級緩存,則創建 CachingExecutor 緩存執行器來包裝上述執行器。默認是啟用二級緩存;
為添加攔截器,這里用戶可以實現自定義的攔截器;
返回執行器。
我們看下執行器 Executor 的類圖:
可以看到,Executor 的繼承類圖,CachingExecutor 是一個裝飾器,里邊維護了一個真正的執行器,它默認實現的 SimpleExecutor 類型。
我們先看下 BaseExecutor 類的實現如下:
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; private boolean closed; protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; // 這是干啥的? this.deferredLoads = new ConcurrentLinkedQueue<>(); // 本地 this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; } @Override public Transaction getTransaction() { if (closed) { throw new ExecutorException("Executor was closed."); } return transaction; } @Override public void close(boolean forceRollback) { try { try { rollback(forceRollback); } finally { if (transaction != null) { transaction.close(); } } } catch (SQLException e) { // Ignore. There's nothing that can be done at this point. log.warn("Unexpected exception on closing transaction. Cause: " + e); } finally { transaction = null; deferredLoads = null; localCache = null; localOutputParameterCache = null; closed = true; } } @Override public boolean isClosed() { return closed; } @Override public 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); } @Override public List<BatchResult> flushStatements() throws SQLException { return flushStatements(false); } public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException { if (closed) { throw new ExecutorException("Executor was closed."); } // 執行刷新聲明 return doFlushStatements(isRollBack); } @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 綁定一個 SQL BoundSql boundSql = ms.getBoundSql(parameter); // 構建一個一級緩存 key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { // 清除本地緩存 clearLocalCache(); } List<E> list; try { queryStack++; // 從一級緩存中獲取 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { // 查詢數據庫 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { // TODO: 2020/9/18 引用隊列? for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } @Override public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); return doQueryCursor(ms, parameter, rowBounds, boundSql); } @Override public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { if (closed) { throw new ExecutorException("Executor was closed."); } DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType); if (deferredLoad.canLoad()) { deferredLoad.load(); } else { // 這是干甚的? deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType)); } } /** * 創建二級緩存 key * * @param ms * @param parameterObject * @param rowBounds * @param boundSql * @return */ @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = 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); } cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } @Override public boolean isCached(MappedStatement ms, CacheKey key) { return localCache.getObject(key) != null; } @Override public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } // 清除本地緩存 clearLocalCache(); // 刷新聲明 flushStatements(); if (required) { // 事務提交 transaction.commit(); } } @Override public void rollback(boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true); } finally { if (required) { transaction.rollback(); } } } } @Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } } protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException; protected void closeStatement(Statement statement) { if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } } /** * Apply a transaction timeout. * * @param statement * a current statement * @throws SQLException * if a database access error occurs, this method is called on a closed <code>Statement</code> * @since 3.4.0 * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer) */ protected void applyTransactionTimeout(Statement statement) throws SQLException { StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout()); } /** * 處理本地緩存輸出參數 * * @param ms * @param key * @param parameter * @param boundSql */ private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) { // 處理 callable 類型,存儲過程、存儲函數 if (ms.getStatementType() == StatementType.CALLABLE) { final Object cachedParameter = localOutputParameterCache.getObject(key); if (cachedParameter != null && parameter != null) { final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter); final MetaObject metaParameter = configuration.newMetaObject(parameter); for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) { if (parameterMapping.getMode() != ParameterMode.IN) { final String parameterName = parameterMapping.getProperty(); final Object cachedValue = metaCachedParameter.getValue(parameterName); metaParameter.setValue(parameterName, cachedValue); } } } } } /** * 從數據庫獲取 * * @param ms * @param parameter * @param rowBounds * @param resultHandler * @param key * @param boundSql * @param <E> * @return * @throws SQLException */ private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // 放入占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // 開始真正的查詢數據 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 清除本地緩存 localCache.removeObject(key); } // 放入本地緩存 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { // 如果是調用存儲過程、存儲函數,則把參數放入緩存中 localOutputParameterCache.putObject(key, parameter); } return list; } protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = transaction.getConnection(); if (statementLog.isDebugEnabled()) { return ConnectionLogger.newInstance(connection, statementLog, queryStack); } else { return connection; } } @Override public void setExecutorWrapper(Executor wrapper) { this.wrapper = wrapper; } private static class DeferredLoad { private final MetaObject resultObject; private final String property; private final Class<?> targetType; private final CacheKey key; private final PerpetualCache localCache; private final ObjectFactory objectFactory; private final ResultExtractor resultExtractor; // issue #781 public DeferredLoad(MetaObject resultObject, String property, CacheKey key, PerpetualCache localCache, Configuration configuration, Class<?> targetType) { this.resultObject = resultObject; this.property = property; this.key = key; this.localCache = localCache; this.objectFactory = configuration.getObjectFactory(); this.resultExtractor = new ResultExtractor(configuration, objectFactory); this.targetType = targetType; } public boolean canLoad() { return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER; } public void load() { @SuppressWarnings("unchecked") // we suppose we get back a List List<Object> list = (List<Object>) localCache.getObject(key); Object value = resultExtractor.extractObjectFromList(list, targetType); resultObject.setValue(property, value); } } }
這個類是抽象類,它實現了 Executor 接口的核心方法,留下一些抽象方法和模板方法交給了子類實現。這個類主要提供幾個主要的屬性:
PerpetualCache 類型的 localCache 屬性,這是一個一級緩存,在同一個 sqlSession 查詢相同接口數據時,提供緩存數據,避免查詢相同查詢語句和參數再次查詢數據庫。在查詢時會從緩存中查找,以及保存緩存,在更新、刪除都會清空緩存;
持有事務 Transaction 屬性,用于在執行完一些事務提交、回滾、操作操作時,委派事務執行對應的邏輯;
默認的實際執行器是 SimpleExecutor 類型,看下它的實現:
public class SimpleExecutor extends BaseExecutor { public SimpleExecutor(Configuration configuration, Transaction transaction) { super(configuration, transaction); } @Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { // 獲取配置類型 Configuration configuration = ms.getConfiguration(); // 獲取 StatementHandler 處理器 StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); // 創建 Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } @Override public <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 處理器 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 創建 Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } @Override protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException { // 獲取配置類型 Configuration configuration = ms.getConfiguration(); // 獲取 StatementHandler 處理器 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql); // 創建 Statement Statement stmt = prepareStatement(handler, ms.getStatementLog()); Cursor<E> cursor = handler.queryCursor(stmt); stmt.closeOnCompletion(); return cursor; } @Override public List<BatchResult> doFlushStatements(boolean isRollback) { return Collections.emptyList(); } /** * 準備一個 Statement * * @param handler * @param statementLog * @return * @throws SQLException */ private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 獲取連接 Connection connection = getConnection(statementLog); // 通過 StatementHandler 創建一個 Statement stmt = handler.prepare(connection, transaction.getTimeout()); // 初始化參數 handler.parameterize(stmt); return stmt; } }
這個類主要實現了 BaseExecutor 抽象的類的抽象的模板方法:doUpdate()、doQuery()、doQueryCursor()、doFlushStatements() 方法,這些方法主要的邏輯為:
獲取 Configuration 配置類;
通過配置類 Configuration 的 newStatementHandler() 方法來創建 StatementHandler 類;
調用 prepareStatement() 方法,通過 StatementHandler 創建 Statement;
再通過 StatementHandler 執行對應的查詢、更新相關方法。
在上述的 SimpleExecutor 類中,通過配置類 Configuration 的 newStatementHandler() 方法獲取 StatementHandler 實例,我們先看下 StatementHandler 的類圖:
我們看下它的實現,org.apache.ibatis.session.Configuration#newStatementHandler:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 創建一個 RoutingStatementHandler 路由的聲明處理器 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 對 StatementHandler 應用插件 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
它的邏輯:
創建一個 RoutingStatementHandler 路由的聲明處理器;
對 StatementHandler 應用插件;
返回 statementHandler。
繼續看下 RoutingStatementHandler 這個類:
public class RoutingStatementHandler implements StatementHandler { /** * 關聯一個真正的 RoutingStatementHandler */ private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { return delegate.prepare(connection, transactionTimeout); } @Override public void parameterize(Statement statement) throws SQLException { delegate.parameterize(statement); } @Override public void batch(Statement statement) throws SQLException { delegate.batch(statement); } @Override public int update(Statement statement) throws SQLException { return delegate.update(statement); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { return delegate.query(statement, resultHandler); } @Override public <E> Cursor<E> queryCursor(Statement statement) throws SQLException { return delegate.queryCursor(statement); } @Override public BoundSql getBoundSql() { return delegate.getBoundSql(); } @Override public ParameterHandler getParameterHandler() { return delegate.getParameterHandler(); } }
可以看到,這個類實現了 StatementHandler 接口,并且根據 MappedStatement 獲取 StatementType,創建對應的 StatementHandler,有:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。默認是會創建 PreparedStatementHandler 實例。
它的其他方法都是使用委派的 StatementHandler 實例去執行,比如 prepare()、parameterize()、batch()、update()、query()、queryCursor()、getBoundSql()、getParameterHandler() 方法。
我們看下實際的 PreparedStatementHandler 類:
public class PreparedStatementHandler extends BaseStatementHandler { public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); } @Override public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執行更新 ps.execute(); // 獲取更新的行數 int rows = ps.getUpdateCount(); // 獲取參數對象 Object parameterObject = boundSql.getParameterObject(); // 獲取鍵生成器 KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); // 后置處理器鍵,比如這里會針對 insert 語句,會設置插入之后的主鍵到參數對象上。 keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; } @Override public void batch(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 批量查詢 ps.addBatch(); } @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執行查詢 ps.execute(); // 通過結果集處理器處理結果 return resultSetHandler.handleResultSets(ps); } @Override public <E> Cursor<E> queryCursor(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執行查詢 ps.execute(); // 結果集處理器處理數據 return resultSetHandler.handleCursorResultSets(ps); } /** * 初始化一個 Statement * * @param connection * @return * @throws SQLException */ @Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { // return connection.prepareStatement(sql); } else { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } } @Override public void parameterize(Statement statement) throws SQLException { // 使用參數化對象進行設置參數 parameterHandler.setParameters((PreparedStatement) statement); } }
這個類就是實際真正執行目標 SQL 邏輯的類,它的一些方法邏輯:
update() 方法中,會通過 PreparedStatement 執行 SQL,然后獲取參數對象、鍵生成器,對參數進行后置處理;
query()、queryCursor() 方法中,會通過 PreparedStatement 執行 SQL,然后通過結果集處理器對結果進行處理;
接著該看 CachingExecutor 類了:
/** * 緩存執行器,裝飾器模式,聲明周期是一個 session * * @author Clinton Begin * @author Eduardo Macarron */ public class CachingExecutor implements Executor { /** * 委派的執行器 */ private final Executor delegate; /** * 事務緩存管理器 */ private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } @Override public Transaction getTransaction() { return delegate.getTransaction(); } @Override public void close(boolean forceRollback) { try { // issues #499, #524 and #573 if (forceRollback) { tcm.rollback(); } else { tcm.commit(); } } finally { delegate.close(forceRollback); } } @Override public boolean isClosed() { return delegate.isClosed(); } @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } @Override public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException { flushCacheIfRequired(ms); return delegate.queryCursor(ms, parameter, rowBounds); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 綁定 SQL BoundSql boundSql = ms.getBoundSql(parameterObject); // 構建緩存key CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 獲取二級緩存配置,它是從解析 mapper.xml 和 mapper 接口的 @CacheNamespace 注解得出來的 Cache cache = ms.getCache(); if (cache != null) { // 是否需要刷新緩存 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 緩存管理器,把緩存 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } // 委派實際的 BaseExecutor 類型的查詢 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public List<BatchResult> flushStatements() throws SQLException { return delegate.flushStatements(); } @Override public void commit(boolean required) throws SQLException { // 提交事務 delegate.commit(required); // 事務緩存管理器提交 tcm.commit(); } @Override public void rollback(boolean required) throws SQLException { try { delegate.rollback(required); } finally { if (required) { tcm.rollback(); } } } private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) { if (ms.getStatementType() == StatementType.CALLABLE) { for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) { if (parameterMapping.getMode() != ParameterMode.IN) { throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement."); } } } } @Override public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); } @Override public boolean isCached(MappedStatement ms, CacheKey key) { return delegate.isCached(ms, key); } @Override public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) { delegate.deferLoad(ms, resultObject, property, key, targetType); } @Override public void clearLocalCache() { delegate.clearLocalCache(); } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { // 查詢之前,先清空二級緩存 tcm.clear(cache); } } @Override public void setExecutorWrapper(Executor executor) { throw new UnsupportedOperationException("This method should not be called"); } }
這個類是一個 Executor 的裝飾器類,主要提供了二級緩存功能。它在查詢數據、更新數據、提交、回滾操作時,會對二級緩存進行處理。
它的查詢數據邏輯:
構建一個 CacheKey 類型的緩存 key;
從 MappedStatement 中獲取二級緩存 Cache;
如果 cache 為空,則執行實際的委派執行器執行查詢數據;
如果 cache 不為空,則先判斷是否需要刷新緩存,如果需要刷新則通過 TransactionalCacheManager 清除緩存;然后從 TransactionalCacheManager 對象中獲取 key 對應的二級緩存數據,緩存數據不為空直接返回,否則就繼續執行實際委派執行器查詢數據,然后把數據緩存到二級緩存中。
最后返回數據。
看下 TransactionalCacheManager 的實現:
public class TransactionalCacheManager { private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); public void clear(Cache cache) { getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { // 獲取 cache 對應的 TransactionalCache,然后把 key 和 value 存入 getTransactionalCache(cache).putObject(key, value); } public void commit() { // 遍歷事務緩存 for (TransactionalCache txCache : transactionalCaches.values()) { // 提交事務 txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { // 如果 transactionalCaches 中的 cache 鍵沒有對應的數據,則創建 TransactionalCache 對象 // 把 cache 對象當做 TransactionalCache 構造器的參數傳入 return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new); } }
這個類持有一個 key 是 Cache 類型,value 為 TransactionalCache 類型的 HashMap 類型屬性 transactionalCaches,來保存事務緩存數據。
它的 getTransactionalCache() 方法中,參數 cache 是外部傳入的二級緩存,當 transactionalCaches 沒有這個 cache 對應的 value 時,就創建一個 TransactionalCache 類,并且把 cache 作為參數傳入它的構造器中,保存起來。
它的結構為:
TransactionalCacheManager 這個個在保存緩存數據時,會調用 TransactionalCache 的 putObject() 方法,在提交事務、回滾事務的時候,會調用 TransactionalCache 的 commit() 和 rollback() 方法。
我們詳細看下這個類。還記得上面 2.2.2 中我們講過的緩存裝飾器嗎?沒錯這里又看見了一個緩存裝飾器 TransactionalCache,它是實現如下:
/** * The 2nd level cache transactional buffer. * <p> * This class holds all cache entries that are to be added to the 2nd level cache during a Session. * Entries are sent to the cache when commit is called or discarded if the Session is rolled back. * Blocking cache support has been added. Therefore any get() that returns a cache miss * will be followed by a put() so any lock associated with the key can be released. * * @author Clinton Begin * @author Eduardo Macarron */ public class TransactionalCache implements Cache { private static final Log log = LogFactory.getLog(TransactionalCache.class); private final Cache delegate; private boolean clearOnCommit; /** * 事務未提交前的保存的緩存數據 */ private final Map<Object, Object> entriesToAddOnCommit; /** * 事務未提交前未命中的緩存數據 */ private final Set<Object> entriesMissedInCache; public TransactionalCache(Cache delegate) { this.delegate = delegate; this.clearOnCommit = false; this.entriesToAddOnCommit = new HashMap<>(); this.entriesMissedInCache = new HashSet<>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public Object getObject(Object key) { // issue #116 Object object = delegate.getObject(key); if (object == null) { entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; } } @Override public void putObject(Object key, Object object) { // 把數據先臨時保存起來 entriesToAddOnCommit.put(key, object); } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { clearOnCommit = true; entriesToAddOnCommit.clear(); } public void commit() { if (clearOnCommit) { // 提交的時候清理二級緩存 delegate.clear(); } // 提交的時候,刷新查詢的數據,用于保存到二級緩存中 flushPendingEntries(); reset(); } public void rollback() { // 回滾時解析未命中的數據 unlockMissedEntries(); reset(); } private void reset() { clearOnCommit = false; entriesToAddOnCommit.clear(); entriesMissedInCache.clear(); } private void flushPendingEntries() { // 提交的時候,把臨時保存的數據,真正放入二級緩存中 for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } private void unlockMissedEntries() { // 移除未命中的數據 for (Object entry : entriesMissedInCache) { try { delegate.removeObject(entry); } catch (Exception e) { log.warn("Unexpected exception while notifiying a rollback to the cache adapter. " + "Consider upgrading your cache adapter to the latest version. Cause: " + e); } } } }
這個類它也是有持有一個實際的委派的緩存,它默認是我們在 2.2.2 節中講到的 SynchronizedCache 裝飾過的二級緩存。
這個類還有個兩個屬性:Map<Object, Object> entriesToAddOnCommit 和 Set<Object> entriesMissedInCache,它們的作用是在 session 事務沒有提交之前,臨時保存緩存數據,等待真正的事務提交 commit() 時才會把緩存同步到二級緩存中,在回滾 rollback() 等時會清除未命中的緩存。
我們通過在它的 getObject() 方法中打斷點,可以得到如下所示的結論。它是一個緩存裝飾器,一層層的包裝。
注意了 TransactionalCache 的聲明周期不與委派的二級緩存一樣,它是和一個 SqlSession 的聲明一樣的。而委派的二級緩存是和應用程序的生命周期一樣的。
我們再看下為執行器應用插件的邏輯 interceptorChain.pluginAll(executor)
:
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<>(); 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); } }
這里會遍歷所有的實現了 Interceptor 接口的攔截器類,調用它們的 plugin() 方法,對目標類進行攔截。實際上攔截器的調用一共有四個地方:
分別是:
創建 ParameterHandler 參數處理器時的攔截;
創建 ResultSetHandler 結果集處理器的攔截;
創建 StatementHandler 的攔截;
創建 Executor 的攔截。
我們可以實現自己的攔截器,根據自己的需求針對這四種類型進行攔截調用。比如可以針對 ParameterHandler 類型進行攔截,實現自動查詢增加分頁 SQL 的功能等等。
最后一步是根據已經創建好的 Executor 和 Configuration 來創建一個 DefaultSqlSession 實例。
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; private final boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList; public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) { this.configuration = configuration; this.executor = executor; this.dirty = false; this.autoCommit = autoCommit; } public DefaultSqlSession(Configuration configuration, Executor executor) { this(configuration, executor, false); } @Override public <T> T selectOne(String statement) { return this.selectOne(statement, null); } @Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } } @Override public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) { return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT); } @Override public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) { final List<? extends V> list = selectList(statement, parameter, rowBounds); final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); final DefaultResultContext<V> context = new DefaultResultContext<>(); for (V o : list) { context.nextResultObject(o); mapResultHandler.handleResult(context); } return mapResultHandler.getMappedResults(); } @Override public <T> Cursor<T> selectCursor(String statement) { return selectCursor(statement, null); } @Override public <T> Cursor<T> selectCursor(String statement, Object parameter) { return selectCursor(statement, parameter, RowBounds.DEFAULT); } @Override public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds); registerCursor(cursor); return cursor; } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public <E> List<E> selectList(String statement) { return this.selectList(statement, null); } @Override public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); } @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 從配置類中獲取映射聲明對象 // MappedStatement 聲明周期很長,隨著容器的關閉而關閉 MappedStatement ms = configuration.getMappedStatement(statement); // 查詢數據 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public void select(String statement, Object parameter, ResultHandler handler) { select(statement, parameter, RowBounds.DEFAULT, handler); } @Override public void select(String statement, ResultHandler handler) { select(statement, null, RowBounds.DEFAULT, handler); } @Override public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { try { MappedStatement ms = configuration.getMappedStatement(statement); executor.query(ms, wrapCollection(parameter), rowBounds, handler); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public int insert(String statement) { return insert(statement, null); } @Override public int insert(String statement, Object parameter) { return update(statement, parameter); } @Override public int update(String statement) { return update(statement, null); } @Override public 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(); } } @Override public int delete(String statement) { return update(statement, null); } @Override public int delete(String statement, Object parameter) { return update(statement, parameter); } @Override public void commit() { commit(false); } @Override public void commit(boolean force) { try { executor.commit(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public void rollback() { rollback(false); } @Override public void rollback(boolean force) { try { executor.rollback(isCommitOrRollbackRequired(force)); dirty = false; } catch (Exception e) { throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public List<BatchResult> flushStatements() { try { return executor.flushStatements(); } catch (Exception e) { throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public void close() { try { executor.close(isCommitOrRollbackRequired(false)); closeCursors(); dirty = false; } finally { ErrorContext.instance().reset(); } } private void closeCursors() { if (cursorList != null && !cursorList.isEmpty()) { for (Cursor<?> cursor : cursorList) { try { cursor.close(); } catch (IOException e) { throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e); } } cursorList.clear(); } } @Override public Configuration getConfiguration() { return configuration; } @Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } @Override public Connection getConnection() { try { return executor.getTransaction().getConnection(); } catch (SQLException e) { throw ExceptionFactory.wrapException("Error getting a new connection. Cause: " + e, e); } } @Override public void clearCache() { executor.clearLocalCache(); } private <T> void registerCursor(Cursor<T> cursor) { if (cursorList == null) { cursorList = new ArrayList<>(); } cursorList.add(cursor); } private boolean isCommitOrRollbackRequired(boolean force) { return (!autoCommit && dirty) || force; } private Object wrapCollection(final Object object) { return ParamNameResolver.wrapToMapIfCollection(object, null); } /** * @deprecated Since 3.5.5 */ @Deprecated public static class StrictMap<V> extends HashMap<String, V> { private static final long serialVersionUID = -5741767162221585340L; @Override public V get(Object key) { if (!super.containsKey(key)) { throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet()); } return super.get(key); } } }
這個類實現了 SqlSession 接口的增刪改查方法,最終還是委派 Executor 去執行。
接下來,該看通過創建好的 SqlSession 來獲取映射接口執行目標方法的流程了。
// 通過 SqlSession 獲取映射接口 AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class); // 執行目標方法 PrimitiveSubject ps1 = mapper.selectOneById(999);
從上面的分析,我們知道了 sqlSession 是 DefaultSqlSession 類型,它的 getMapper() 方法,我們在 2.3.5 中看到了它的實現,org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper:
@Override public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); }
它會通過配置類 Configuration 根據類型獲取對應的 Mapper 類型,org.apache.ibatis.session.Configuration#getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
最后再調用 MapperRegistry 實例的 getMapper() 方法,org.apache.ibatis.binding.MapperRegistry#getMapper:
/** * 獲取映射器 * * @param type * @param sqlSession * @param <T> * @return */ @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { // 映射器代理工廠獲取代理對象 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
看到這里,我們就就比較熟悉了,在 2.2.4 節中講了解析 mapper.xml 文件時,會根據 xml 中的命名空間來注冊對應的 mapper 接口,會以一個 key 為目標接口類型,value 為 MapperProxyFactory 實例的形式保存到一個 HashMap 實例中。
這里就是獲取除了目標類型對應的 MapperProxyFactory 類型,然后調用它的 newInstance() 方法,通過 JDK 動態代理創建代理實例類。
最后,用這個代理對象來執行目標方法。
我們在 org.apache.ibatis.executor.statement.PreparedStatementHandler#query 方法處,打個端點看下它的方法調用棧信息:
// 調用 PreparedStatementHandler 的 query 方法 query(Statement, ResultHandler):71, PreparedStatementHandler (org.apache.ibatis.executor.statement), PreparedStatementHandler.java // 調用 RoutingStatementHandler 的 query 方法 query(Statement, ResultHandler):82, RoutingStatementHandler (org.apache.ibatis.executor.statement), RoutingStatementHandler.java // 調用 SimpleExecutor 的 doQuery() 方法 doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql):69, SimpleExecutor (org.apache.ibatis.executor), SimpleExecutor.java // 調用 BaseExecutor 的 queryFromDatabase() 方法 queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):381, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java // 調用 CachingExecutor 的 query() 方法 query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):173, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):116, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java // 調用 DefaultSqlSession 的 select() 方法 selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java // 調用 MapperMethod 類的 execute() 方法 execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java // 調用 MapperProxy 類的 invoke() 方法 invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java // 調用 JDK 動態代理類的 selectOneById() 方法 selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source // 單元測試類的查詢方法 testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java ...省略無關棧信息...
它的時序圖:
sequenceDiagram # 單元測試入口 AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 單元測試方法 AutoConstructorTest->>$Proxy15:selectOneById() # JDK代理對象 $Proxy15->>MapperProxy:invoke() 執行 # 代理查詢 MapperProxy->>PlainMethodInvoker:invoke() PlainMethodInvoker->>MapperMethod:execute() # 委派 DefaultSqlSession MapperMethod->>DefaultSqlSession:selectOne() DefaultSqlSession->>DefaultSqlSession:selectList() # 委派 CachingExecutor DefaultSqlSession->>CachingExecutor:query() CachingExecutor->>CachingExecutor:query() # BaseExecutor CachingExecutor->>BaseExecutor:query() BaseExecutor->>BaseExecutor:queryFromDatabase() BaseExecutor->>SimpleExecutor:doQuery() # RoutingStatementHandler SimpleExecutor->>RoutingStatementHandler:query() RoutingStatementHandler->>PreparedStatementHandler:query()
我們再在查詢二級緩邏輯處打斷點,看下它的調用棧信息:
// 調用 PerpetualCache 的 getObject() 方法 getObject(Object):59, PerpetualCache (org.apache.ibatis.cache.impl), PerpetualCache.java // 調用 LruCache 的 getObject() 方法 getObject(Object):75, LruCache (org.apache.ibatis.cache.decorators), LruCache.java // 調用 SerializedCache 的 getObject() 方法 getObject(Object):63, SerializedCache (org.apache.ibatis.cache.decorators), SerializedCache.java // 調用 LoggingCache 的 getObject() 方法 getObject(Object):55, LoggingCache (org.apache.ibatis.cache.decorators), LoggingCache.java // 調用 SynchronizedCache 的 getObject() 方法 getObject(Object):48, SynchronizedCache (org.apache.ibatis.cache.decorators), SynchronizedCache.java // 調用 TransactionalCache 的 getObject() 方法 getObject(Object):75, TransactionalCache (org.apache.ibatis.cache.decorators), TransactionalCache.java // 調用 TransactionalCacheManager 的 getObject() 方法 getObject(Cache, CacheKey):35, TransactionalCacheManager (org.apache.ibatis.cache), TransactionalCacheManager.java // 調用 CachingExecutor 的 query() 方法 query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):114, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java // 調用 DefaultSqlSession 的 selectList() 方法 selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java // 調用 DefaultSqlSession 的 selectOne() 方法 selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java // 調用 MapperMethod 的 execute() 方法 execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java // 調用 PlainMethodInvoker 的 invoke() 方法 invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java // 調用 MapperProxy 的 invoke() 方法 invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java // 調用代理對象的 selectOneById() 方法 selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source // 單元測試類的方法 testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java ...省略無關棧信息...
畫出二級緩存調用的時序圖:
sequenceDiagram # 單元測試入口 AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 單元測試方法 AutoConstructorTest->>$Proxy15:selectOneById() # JDK代理對象 $Proxy15->>MapperProxy:invoke() 執行 # 代理查詢 MapperProxy->>PlainMethodInvoker:invoke() PlainMethodInvoker->>MapperMethod:execute() # 委派 DefaultSqlSession MapperMethod->>DefaultSqlSession:selectOne() DefaultSqlSession->>DefaultSqlSession:selectList() # 委派 CachingExecutor DefaultSqlSession->>CachingExecutor:query() CachingExecutor->>CachingExecutor:query() # 事務緩存管理器 CachingExecutor->>TransactionalCacheManager:getObject() # 事務緩存裝飾器 TransactionalCacheManager->>TransactionalCache:getObject() # 同步緩存裝飾器 TransactionalCache->>SynchronizedCache:getObject() # 日志緩存裝飾器 SynchronizedCache->>LoggingCache:getObject() # 序列化裝飾器 LoggingCache->>SerializedCache:getObject() # Lru 緩存裝飾器 SerializedCache->>LruCache:getObject() # 實際的緩存 LruCache->>PerpetualCache:getObject()
這里的調用邏輯中,二級緩存的調用鏈可以配合著 2.2.2.9 的緩存小結圖來閱讀。
關于“mybatis核心流程的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。