MyBatis
一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)爲數據庫中的記錄。(摘抄至官網)html
前言:若是您對MyBatis的底層感興趣,想知道發起一條Sql語句執行,底層走了什麼操做,那您能夠花點時間,認真閱讀下本篇文章,相信會給你很多收穫的。本篇文章步驟條例很清晰的。java
你們好,我是AIO生活,關注我,後續連載更多技術重難點,文章有不足之處,歡迎你們留言指正,謝謝你們啦!sql
順便提一下,幫你們整理了些JAVA電子書,掃碼回覆「1024」獲取電子書合集,也可簽到領現金紅包。數據庫
MyBatis底層是怎麼運行的呢?
public static void main(String[] args) throws IOException { String configName = "mybatis_config.xml"; Reader reader = Resources.getResourceAsReader(configName); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); }
1:啓動,加載配置文件
- new SqlSessionFactoryBuilder().build(reader),SqlSessionFactoryBuilder建立出SqlSessionFactory,reader參數接收一個mybatis-config.xml的流文件。
- 建立 XMLConfigBuilder:config.xml解析器。
- 實例化 XMLConfigBuilder父類(BaseBuilder)的Configuration類。
- 解析config.xml數據,加載到Configuration對象中。
- new DefaultSqlSessionFactory(config) 建立一個SqlSessionFactory實例,默認是DefaultSqlSessionFactory。
如圖所示
apache
1.1:new SqlSessionFactoryBuilder().build(reader)
建立SqlSessionFactory對象實例
數組
1.2:new XMLConfigBuilder(reader, environment, properties);
解析 mybatis-config.xml文件緩存
public class XMLConfigBuilder extends BaseBuilder { /* 標記是否已經解析過配置文件 */ private boolean parsed; /* 解析器 */ private final XPathParser parser; /** * 數據源,SqlSessionFactoryBuilder.build(Reader reader, String environment, Properties properties) * 不指定爲空 */ private String environment; public XMLConfigBuilder(XPathParser parser, String environment, Properties props) { /* 初始化 Configuration */ super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); /* 設置格外的屬性 */ this.configuration.setVariables(props); /* 標記初始化爲 false */ this.parsed = false; this.environment = environment; this.parser = parser; } }
1.3:new Configuration()
建立 Configuration 實例bash
public class Configuration { /** * 類型別名註冊 * 好比 <dataSource type="POOLED"> * 其中的 type="POOLED" 會使用 PooledDataSourceFactory類建立數據源鏈接池 */ protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); public Configuration() { /** * JDBC 對應使用的 事務工廠類 * <transactionManager type="JDBC"></transactionManager> */ typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); /** * MANAGED 對應使用的 事務工廠類 */ typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); /** * JNDI 對應使用的 數據源 */ typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); /** * POOLED 對應使用的 數據源 * <dataSource type="POOLED"> */ typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); /** * UNPOOLED 對應使用的 數據源 */ 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); languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); languageRegistry.register(RawLanguageDriver.class); } }
1.4 XMLConfigBuilder.parse()
解析 mybatis-config.xml
session
private void parseConfiguration(XNode root) { try { //issue #117 read properties first /** * 解析 <properties resource="my.properties" /> 配置文件 */ propertiesElement(root.evalNode("properties")); /** * 解析settings配置文件 * <settings> * <setting name="logImpl" value="STDOUT_LOGGING"/> * </settings> */ Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); /** * 解析 typeAliases配置文件 */ typeAliasesElement(root.evalNode("typeAliases")); /** * 解析 plugins 配置文件 * 這個是插件,能夠動態的攔截sql執行 */ pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); /** * 加載 mapper.xml 文件 */ mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
1.4.1: mapperElement(root.evalNode(「mappers」));
解析mapper.xml文件,以及接口方法的註解mybatis
public class XMLConfigBuilder extends BaseBuilder { /** * mapper隱射文件解析 * @param parent * @throws Exception */ private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { /** * 解析 <package * <mappers> * <package name="com.mapper"/> * </mappers> */ if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); /** * 添加全部包下的接口 * 實際調用 configuration.addMapper(mapperInterface); */ configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); /** * 解析 <mapper resource * <mappers> * <mapper resource="mapper/UserMapper.xml"/> * </mappers> */ if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); /** * mapper.xml 解析器 */ XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); /** * 開始解析 mapper.xml文件 * 重點分析這個 一樣也會調用 configuration.addMapper(mapperInterface); 這個方法 */ mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { /** * 解析 <mapper url */ 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) { /** * 解析 <mapper class */ Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { /** * 如下代碼能夠看出 url resource class 三個屬性只能選擇一個,不然就會報錯 */ throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } }
1.4.2:mapperParser.parse();解析
個人UserMapper.xml文件內容爲:
<?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="com.mapper.UserMapper"> <select id="findById" resultType="com.entity.User"> select * from `user` where userId = #{userId} </select> </mapper>
UserMapper接口內容爲:
package com.mapper; import com.entity.User; import org.apache.ibatis.annotations.Select; public interface UserMapper { @Select("select * from `user` where userId = 2") User findById(int userId); }
疑問?UserMapper.xml有<select id=「findById」,而在接口中的findById方法我又加了一個@Select註解;那麼執行會選擇哪一條Sql執行仍是報錯呢?
以UserMapper.xml爲例子解析,能夠看到 resource = mapper/UserMapper.xml
public class XMLMapperBuilder extends BaseBuilder { public void parse() { if (!configuration.isResourceLoaded(resource)) { /** * 解析 mapper.xml文件內容 */ configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); /** * 解析 mapper.xml的<mapper namespace="com.mapper.UserMapper"> * namespace指定的{UserMapper}接口的註解信息 */ bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } }
1.4.3:configurationElement(parser.evalNode("/mapper"));
解析 mapper.xml文件內容
public class XMLMapperBuilder extends BaseBuilder { private void configurationElement(XNode context) { try { /** * namespace屬性 */ String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { /** * 不指定 namespace會報錯哦 由此得知 namespace屬性是必須指定的 */ 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語句 select|insert|update|delete * 重點分析這裏,這裏的解析會關聯到 mapper接口的執行方法 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); } } }
1.4.4:buildStatementFromContext(context.evalNodes(「select|insert|update|delete」));
解析 sql語句 select|insert|update|delete;list參數內容是select|insert|update|delete的Sql語句
XMLStatementBuilder Sql語句的解析器
1.4.5:statementParser.parseStatementNode();解析Sql語句
builderAssistant.addMappedStatement,並非添加一個mapper.xml文件隱射的實例,而是爲每個Sql語句建立一個實例
public class XMLStatementBuilder extends BaseBuilder { private final MapperBuilderAssistant builderAssistant; public void parseStatementNode() { /** * 此處省略一大推代碼... */ /** * <select id="findById" resultType="com.entity.User"> * select * from `user` where userId = #{userId} * </select> * 參數解析 * * id:標籤指定的id = findById * sqlSource:Sql語句,Sql參數佔位 * statementType:sql執行類型 參考{@link StatementType} * STATEMENT: 直接操做sql,不進行預編譯 ${} * PREPARED: 預處理,參數,進行預編譯 #{} * CALLABLE: 執行存儲過程 * sqlCommandType:sql語句類型 參考{@link SqlCommandType} * UNKNOWN:未知,INSERT:新增,UPDATE:修改,DELETE:刪除,SELECT:查詢,FLUSH:刷新 * * 其餘參數可查看官網:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html */ builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } }
1.4.6:builderAssistant.addMappedStatement();
建立一個MappedStatement實例添加到 Configuration.mappedStatements的Map集合中
public class XMLStatementBuilder extends BaseBuilder { public MappedStatement addMappedStatement() { /** * 此處省略一大推代碼... */ MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) /** * 建造者模式 * 用於設置 MappedStatement的屬性 * 此處省略一大推代碼... */ /** * 設置參數入參類型 parameterType屬性 * <select id="findById" parameterType="int" resultType="com.entity.User"> * select * from `user` where userId = #{userId} * </select> */ ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } /** * 建立一個 {@link MappedStatement} 實例 */ MappedStatement statement = statementBuilder.build(); /** * MappedStatement實例添加到 {@link #configuration.mappedStatements} Map集合中 * MappedStatement 是對應一個Sql語句的實例對象 * * configuration.mappedStatements 存放全部的MappedStatement實例,後面會詳細介紹 */ configuration.addMappedStatement(statement); return statement; } }
以上流程執行完後,又回到1.4.2:mapperParser.parse();解析,如今開始解析 namespace指定的接口的註解信息,並建立該接口的代理工廠對象,UserMapper接口。
1.4.7:bindMapperForNamespace();
開始解析接口註解,並添加一個MapperProxyFactory代理工廠的對象到configuration.mapperRegistry.knownMappers;key是Mapper接口
public class XMLMapperBuilder extends BaseBuilder { private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { /** * java 反射 Class.classForName */ boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { /** * 這裏並無拋出異常,說明 namespace 能夠指定一個不存在的接口 */ //ignore, bound type is not required 忽略,綁定類型不是必需的 } if (boundType != null) { if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); /** * 添加一個Mapper接口的代理工廠對象到configuration.mapperRegistry.knownMappers集合中 * 參考 {@link Configuration#mapperRegistry}, * {@link MapperRegistry#knownMappers} */ configuration.addMapper(boundType); } } } } }
1.4.8:configuration.addMapper(boundType);
public class Configuration { protected final MapperRegistry mapperRegistry = new MapperRegistry(this); public <T> void addMapper(Class<T> type) { /** * mapperRegistry = {@link MapperRegistry} mapper接口註冊器,存放全部的mapper接口信息 */ mapperRegistry.addMapper(type); } }
mapperRegistry.addMapper(type);
爲Mapper接口建立一個代理工廠,方便後期使用Mapper接口時建立代理類
解析mapper接口的註解信息
public class MapperRegistry { 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 { /** * {@link MapperProxyFactory} 代理接口工廠 */ 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. /** * {@link MapperAnnotationBuilder} mapper接口註解解析器 */ MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); /* 開始解析 */ parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } }
1.4.9 parser.parse(); MapperAnnotationBuilder.parse()
解析mapper接口的註解信息,parseStatement(method)主要在這個方法中完成
public class MapperAnnotationBuilder { public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); /** * 解析 @CacheNamespace 註解 */ parseCache(); /** * 解析 CacheNamespaceRef 註解 */ parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { /** * 解析Sql相關注解 列如 @Select|@Update 之類的註解 * * 重點關注 */ parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } /** * 解析待定方法 */ parsePendingMethods(); } }
1.5.0:parseStatement(method);
建立一個MappedStatement實例添加到 Configuration.mappedStatements的Map集合中
public class MapperAnnotationBuilder { private final MapperBuilderAssistant assistant; void parseStatement(Method method) { /** * 此處省略一大推代碼... */ assistant.addMappedStatement( mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, // ParameterMapID null, parameterTypeClass, resultMapId, getReturnType(method), resultSetType, flushCache, useCache, // TODO gcode issue #577 false, keyGenerator, keyProperty, keyColumn, // DatabaseID null, languageDriver, // ResultSets options != null ? nullOrEmpty(options.resultSets()) : null); } }
1.5.1:assistant.addMappedStatement();
這裏能夠參考1.4.6:builderAssistant.addMappedStatement();如出一轍的操做
都調用了configuration.addMappedStatement(statement);
這裏重點分析Configuration.addMappedStatement(statement);在作什麼操做,而且解決 1.4.2留下的疑點;UserMapper.xml和UserMapper接口都有findById的Sql語句定義
public class Configuration { public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); } }
mappedStatements.put(ms.getId(), ms); 實際調用 Configuration.StrictMap.put()方法
Configuration.StrictMap是一個重寫的HashMap,put方法會先校驗key是否存在
public class Configuration { /** * mappedStatements Sql語句的對象 * * Configuration.StrictMap 實現了HashMap */ protected final Map<String, MappedStatement> mappedStatements = new Configuration.StrictMap<MappedStatement>("Mapped Statements collection") .conflictMessageProducer((savedValue, targetValue) -> ". please check " + savedValue.getResource() + " and " + targetValue.getResource()); protected static class StrictMap<V> extends HashMap<String, V> { @Override @SuppressWarnings("unchecked") public V put(String key, V value) { /** * key 是否存在 存在就拋出異常 * * 由此得知,方法映射Sql語句只能定義一個,要麼在 mapper.xml中定義,要麼就註解定義 */ if (containsKey(key)) { throw new IllegalArgumentException(name + " already contains value for " + key + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value))); } if (key.contains(".")) { final String shortKey = getShortName(key); if (super.get(shortKey) == null) { super.put(shortKey, value); } else { super.put(shortKey, (V) new org.apache.ibatis.session.Configuration.StrictMap.Ambiguity(shortKey)); } } return super.put(key, value); } } }
debug調試,key已經存在,就會報錯。由此得知,方法映射Sql語句只能定義一個,要麼在 mapper.xml中定義,要麼就註解定義
到此,MyBatis的啓動流程就走完了。
2:接下來就來看下是如何執行Sql查詢的。
public class Main { public static void main(String[] args) throws IOException { String configName = "mybatis_config.xml"; Reader reader = Resources.getResourceAsReader(configName); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); /** * 獲取一個會話鏈接 */ SqlSession sqlSession = sqlSessionFactory.openSession(); /** * 拿到 UserMapper 的代理類 */ UserMapper userMapper = sqlSession.getMapper(UserMapper.class); /** * 執行Sql查詢 */ User user = userMapper.findById(1); System.out.println(user); } }
輸出結果:User{userId=1, username=‘張三’, sex=‘男’, age=12}
一行代碼的查詢,底層既然走了那麼多流程;
流程圖:
2.1:sqlSessionFactory.openSession();打開會話鏈接
調用DefaultSqlSessionFactory.openSessionFromDataSource();
public class DefaultSqlSessionFactory implements SqlSessionFactory { private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { /** * mybatis_config.xml配置的 * <environment>數據源<environment/> */ final Environment environment = configuration.getEnvironment(); /** * transactionManager 配置的事務管理器工廠 type="JDBC" {@link JdbcTransactionFactory} *<environments default="developmentss"> * <environment id="developmentss"> * <transactionManager type="JDBC"></transactionManager> * </environment> * </environments> */ final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); /** * 建立事務管理器,因爲上面指定的事務管理器工廠是 {@link JdbcTransactionFactory} * 因此建立的事務管理器是 {@link JdbcTransaction} * * @param level 事務隔離級別 * @param autoCommit 是否自動提交事務 */ tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); /** * 建立Sql執行器 * * @param execType 建立執行器類型 defaultExecutorType若是不指定 默認就是 SIMPLE * <settings> * <setting name="defaultExecutorType" value="SIMPLE"/> * </settings> */ final Executor executor = configuration.newExecutor(tx, execType); /** * 建立一個默認的 SqlSession實例 */ 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(); } } }
2.2:configuration.newExecutor(tx, execType);建立執行器
public class Configuration { 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); /** * 執行器會重用預處理語句(PreparedStatement) */ } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { /** * 普通的執行器 也是默認的執行器 */ executor = new SimpleExecutor(this, transaction); } /** * 若是開啓了二級緩存 cacheEnabled,建立一個CachingExecutor緩存執行器 * cacheEnabled 默認爲true * <settings> * <setting name="cacheEnabled" value="true"/> * </settings> */ if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
2.3:sqlSession.getMapper(UserMapper.class);
獲取Mapper接口代理類實例
public class DefaultSqlSession implements SqlSession { @Override public <T> T getMapper(Class<T> type) { /** * 用 Configuration 類的 getMapper方法 */ return configuration.getMapper(type, this); } }
public class Configuration { public <T> T getMapper(Class<T> type, SqlSession sqlSession) { /** * 調用 MapperRegistry Mapper接口註冊器 */ return mapperRegistry.getMapper(type, sqlSession); } }
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); public <T> T getMapper(Class<T> type, SqlSession sqlSession) { /** * knownMappers 從緩存中獲取 MapperProxyFactory Mapper接口代理工廠 * 若是沒有找到就會拋出異常, * 說明獲取Mapper接口代理實例時,須要事先定義好 --> 至關於Spring的掃包 */ 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.3.1:使用JDK代理建立Mapper接口代理
InvocationHandler是MapperProxy
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; /** * MapperMethod 緩存 */ private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { /** * JDK 生產代理類 */ 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); } }
2.4:userMapper.findById(1); 調用Mapper接口方法Sql查詢
會走代理類 MapperProxy.invoke
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @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 if (method.isDefault()) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } /** * 根據方法全限名 獲取一個MapperMethod實例,而且緩存 * * 注意:這裏的 methodCache 只是一個引用,緩存的全部對象都在 {@link MapperProxyFactory#methodCache}中 */ final MapperMethod mapperMethod = cachedMapperMethod(method); /** * 開始執行 */ return mapperMethod.execute(sqlSession, args); } /** * 添加到緩存 methodCache {@link MapperProxyFactory#methodCache}中 * computeIfAbsent HashMap 存在就獲取,不存在就新增 * @param method * @return */ private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }
2.5:mapperMethod.execute(sqlSession, args);
執行Sql語句查詢,因爲個人返回結果是一個 User對象,因此會走到
result = sqlSession.selectOne(command.getName(), param);這一行,查詢一條記錄
實際走到 DefaultSqlSession.selectOne()
public class MapperMethod { private final org.apache.ibatis.binding.MapperMethod.SqlCommand command; private final org.apache.ibatis.binding.MapperMethod.MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new org.apache.ibatis.binding.MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new org.apache.ibatis.binding.MapperMethod.MethodSignature(config, mapperInterface, method); } /** * 開始執行Sql查詢 */ public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { // 省略代碼... 執行 insert語句 <insert>/@Insert break; } case UPDATE: { // 省略代碼... 執行 insert語句 <update>/@Update break; } case DELETE: { // 省略代碼... 執行 delete語句 <delete>/@Delete break; } case SELECT: // 執行 select語句 <select>/@Select if (method.returnsVoid() && method.hasResultHandler()) { // 返回類型是否爲空 通常狀況作Sql操做都要有返回結果的 executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { /** * 是否返回多個結果集 {@link Collection}集合/數組 */ result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { /** * 返回類型是否爲 Map 集合 */ result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { /** * 返回類型是不是 遊標 {@link org.apache.ibatis.cursor.Cursor} */ result = executeForCursor(sqlSession, args); } else { /** * 將參數轉換爲Sql命令參數 */ Object param = method.convertArgsToSqlCommandParam(args); /** * 發起查詢 調用的是 sqlSession中的方法 */ 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; } }
DefaultSqlSession.selectOne()能夠看出實際是調用selectList(),並且若是返回了多個結果集就會報錯。錯誤信息以下
Expected one result (or null) to be returned by selectOne(), but found: 2
2.6:DefaultSqlSession.selectList()
查詢多結果集
public class DefaultSqlSession implements SqlSession { /** * * @param statement 方法全限名 好比:com.mapper.UserMapper.findById * @param parameter 參數 * @param rowBounds 分頁 * @param <E> * @return */ @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { /** * 根據方法全限名在 configuration.mappedStatements緩存集合中拿到方法對應的Sql Statement對象實例 */ MappedStatement ms = configuration.getMappedStatement(statement); /** * 使用執行器執行 由當前設置的執行器執行 * <setting name="defaultExecutorType" value="SIMPLE"/> * * <setting name="cacheEnabled" value="true"/> * 注意:cacheEnabled因爲開啓二級緩存默認爲true,會先使用 {@link CachingExecutor} 緩存執行器查詢 */ 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(); } } }
2.7:executor.query執行器查詢
執行器的建立查看 2.2:configuration.newExecutor(tx, execType);建立執行器
/** * 二級緩存執行器 */ public class CachingExecutor implements Executor { @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { /** * 獲取指定的二級緩存 * mapper.xml 指定的 <cache type="com.domain.something.MyCustomCache"/> * 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) { /** * 若是沒有數據查詢Sql */ list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); /** * 設置緩存 */ tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } /** * delegate = SimpleExecutor */ return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } }
2.8:delegate.query(); delegate = SimpleExecutor
由於我沒有指定二級緩存,因此直接走向delegate.query, 由於SimpleExecutor繼承了BaseExecutor可是沒有重寫query方法,因此走的是BaseExecutor.query()
public abstract class BaseExecutor implements Executor { @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."); } /** * 是否須要清空一級緩存 flushCache設置爲true生效 * <select flushCache="true"></select> */ if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { // 防止多線程重複調用處理 queryStack++; /** * localCache.getObject(key) 獲取一級緩存 * {@link BaseExecutor.localCache} 類型 org.apache.ibatis.cache.impl.PerpetualCache */ 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) { for (org.apache.ibatis.executor.BaseExecutor.DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } }
2.9:queryFromDatabase(),
數據庫查詢 會執行到子類的doQuery()方法,這裏的字類是SimpleExecutor 因此執行 SimpleExecutor.doQuery()
public abstract class BaseExecutor implements Executor { 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 { /** * 調用子類的 doQuery()方法 * * 這裏的字類是SimpleExecutor 因此執行 SimpleExecutor.doQuery() */ 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; } }
2.9.1:SimpleExecutor.doQuery()
執行數據庫查詢
public class SimpleExecutor extends BaseExecutor { 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(); /** * 建立 RoutingStatementHandler Sql語句執行類型處理器 */ StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); /** * 建立 java.sql.Statement */ stmt = prepareStatement(handler, ms.getStatementLog()); /** * 發起數據庫查詢 */ return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } }
2.9.2:configuration.newStatementHandler();
建立 RoutingStatementHandler Sql語句執行類型處理器
public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: /** * 簡單的Sql執行處理器 */ delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: /** * 預編譯的Sql執行處理器 * 這是默認的 */ delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: /** * 存儲過程執行的Sql執行處理器 */ delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } }
2.9.3:prepareStatement()
建立 java.sql.Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); return stmt; }
3.0:handler.query(stmt, resultHandler);
delegate 參考2.9.2:configuration.newStatementHandler()
public class RoutingStatementHandler implements StatementHandler { private final StatementHandler delegate; @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { /** * 當前的 delegate = PreparedStatementHandler */ return delegate.query(statement, resultHandler); } }
3.1:delegate.query(statement, resultHandler);
執行 PreparedStatementHandler.query()
public class PreparedStatementHandler extends BaseStatementHandler { @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; /** * 發起數據庫查詢 */ ps.execute(); /** * 封裝結果集 resultSetHandler = DefaultResultSetHandler */ return resultSetHandler.handleResultSets(ps); } }
3.2:resultSetHandler.handleResultSets(ps);
封裝結果集
public class DefaultResultSetHandler implements ResultSetHandler { /** * 封裝結果集 * @param stmt * @return * @throws SQLException */ @Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; /** * 包裝 ResultSet */ ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } /** * 包裝成 List */ return collapseSingleResultList(multipleResults); } }
到這裏MyBatis的Sql查詢就結束了,同理添加,修改,刪除也是一樣的流程。
能夠本身跟着以上步驟,斷點調試追蹤下源碼,一切迷霧都將會撥開的。
總結:
- 核心依賴於動態代理模式建立Mapper接口代理實例
- SqlSessionFactory負責建立SqlSession,全局惟一實例(相似於單例)
- Configuration 核心中的核心,走到哪裏都有它的存在,並且是全局惟一實例(相似於單例)
你們好,我是AIO生活,關注我,後續連載更多技術重難點,文章有不足之處,歡迎你們留言指正,謝謝你們啦!
順便提一下,幫你們整理了些JAVA電子書,掃碼回覆「1024」獲取電子書合集,也可簽到領現金紅包。