Phase source code details:
Through a simple demo for single step debugging, it is convenient to have a deep understanding of each step of Mybatis. The example is as follows:
public class MybatisLearn { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource);// Load profile SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// Get the session factory according to the configuration file SqlSession session = sqlSessionFactory.openSession();// Open a session try { UserMapper mapper = session.getMapper(UserMapper.class);// Get a mapper by class System.out.println(mapper.selectById("sunwukong"));// Call methods in mapper to query data } finally { session.close();// Closing session } } }
Analyze the logic behind each step in the demo in detail. First, get the session factory according to the configuration file, which loads all the configuration information. There are many steps. See the source code below.
(1).SqlSessionFactoryBuilder.java public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// Load all the specified configuration files into memory return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } (2).XMLConfigBuilder.java public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); // Get all the configurations under this node and analyze them return configuration; } private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); //First read the properties information typeAliasesElement(root.evalNode("typeAliases")); // Read class rename information pluginElement(root.evalNode("plugins"));// Read plug-in information objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); settingsElement(root.evalNode("settings")); environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // Read database vendor ID typeHandlerElement(root.evalNode("typeHandlers")); // Read type converter mapperElement(root.evalNode("mappers")); // Read mappers configuration } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } private void propertiesElement(XNode context) throws Exception {//First read the properties information if (context != null) { Properties defaults = context.getChildrenAsProperties(); String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); configuration.setVariables(defaults); // set the read information to Mybatis Configuration information } } private void mapperElement(XNode parent) throws Exception {// Read mappers configuration if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) {// Get package information String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource");//Get more information about mapper configuration String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse();// Further analysis of the configured mapper.xml } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse();// Further analysis of the configured mapper.xml } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface);// If the mapper interface is specified, it will be added to the configuration information. } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } } (3).XMLMapperBuilder.java public void parse() { // Further analysis of the configured mapper.xml if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper"));// Read mapper label configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace");// Get namespace information if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref"));// Get cache related configuration cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap"));// Get information about parameters resultMapElements(context.evalNodes("/mapper/resultMap"));// Get information about return value encapsulation sqlElement(context.evalNodes("/mapper/sql"));// Get information about encapsulated sql segments buildStatementFromContext(context.evalNodes("select|insert|update|delete"));// Building sql in mapper } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } (4).Configuration.java public <T> void addMapper(Class<T> type) {// Mapper interface added to mapper registration information mapperRegistry.addMapper(type); }
The next step is to open a session. The source code is as follows:
DefaultSqlSessionFactory.java public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);// Default execution type, non auto commit transaction } 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); // Create an actuator from the configuration information 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(); } } Configuration.java public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction);// Batch actuator } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction);// Reusable actuator } else { executor = new SimpleExecutor(this, transaction);// Simple actuator } if (cacheEnabled) { // Open cache or not executor = new CachingExecutor(executor);// With cache actuator } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
The Executor has a variety of executors. The diagram is as follows:
Get the specified mapper by class:
(1).DefaultSqlSession.java public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this);// Get the corresponding mapper from the configuration information according to the type } (2).Configuration.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } (3).MapperRegistry.java public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);// According to the class of mapper, get the corresponding mapper agent if (mapperProxyFactory == null) throw new BindingException("Type " + type + " is not known to the MapperRegistry."); try { return mapperProxyFactory.newInstance(sqlSession);// Bind this session to mapper and return a mapper } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } (4).MapperProxyFactory.java public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();// The details of each method in the mapper are saved. public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> 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<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } } (5).MapperMethod.java public class MapperMethod {// Corresponding method details private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, method); } public Object execute(SqlSession sqlSession, Object[] args) {// Practical implementation method Object result; if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { 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; } ...... }
The order of key nodes of MyBatis is as follows:
About caching:
Mybatis cache is divided into level 1 (SqlSession level) and level 2 (namespace level). By default, only level 1 cache is enabled; Level 2 cache needs to be manually enabled and configured.
1. For a session, query a piece of data, which will be placed in the first level cache of the current session;
2. If the session is closed, the data in the first level cache will be saved in the second level cache. The new session query information can refer to the content in the second level cache.
3. The data detected by different namespace s will be placed in their own corresponding cache (map), and the data detected will be placed in the first level cache by default. Only after the session is committed or closed, the data in the first level cache will be transferred to the second level cache.
public class CachingExecutor implements Executor {// Cache related execution classes private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); } ...... @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { flushCacheIfRequired(ms);// Refresh cache when updating data return delegate.update(ms, parameterObject); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// Generate cached key according to series of rules 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 { Cache cache = ms.getCache(); if (cache != null) { // Using caching flushCacheIfRequired(ms);// Refresh cache if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // Data meets cache requirements @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); // Get data from cache first if (list == null) { list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // Data not available in cache, query database tcm.putObject(cache, key, list); // Put data into cache } return list; } } return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } private void flushCacheIfRequired(MappedStatement ms) { // Refresh cache Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache);// Clear cache } } ...... }