您好,登录后才能下订单哦!
这篇文章将为大家详细讲解有关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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。