1. Traditional methods
1.1 Source Profiling-Initialization
// 1. Read configuration file, read as byte input stream, note: not parsed yet InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); // 2. Parse the configuration file, encapsulate the Configuration object, create the DefaultSqlSessionFactory object SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // 3. Produced DefaultSqlsession instance object set transaction not autocommit completed executor object creation SqlSession sqlSession = sqlSessionFactory.openSession(); // 4. (1) Get the specified MappedStatement object from the map collection in Configuration based on statementid //(2) Delegate query task to executor executor User user = sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1); System.out.println(user); User user2 = sqlSession.selectOne("com.lagou.mapper.IUserMapper.findById",1); System.out.println(user2); // 5. Release resources sqlSession.close();
1.2 Initialization
// 1The build we initially called public SqlSessionFactory build (InputStream inputStream){ //Invoke overloaded method return build(inputStream, null, null); } // 2. Called overloaded methods public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // Create XMLConfigBuilder, which is a class dedicated to parsing mybatis configuration files XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // Perform XML parsing // Create DefaultSqlSessionFactory object 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. } } } /** * 3 Parse the XML into a Configuration object. * * @return Configuration object */ public Configuration parse() { // If resolved, throw BuilderException exception if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } // Tag parsed parsed = true; ///parser is an XPathParser parser object that reads data within a node, <configuration>is the top-level tag in the MyBatis configuration file // Parse XML configuration node parseConfiguration(parser.evalNode("/configuration")); return configuration; } /** * 4,Parsing XML * * For specific XML tags for MyBatis, see XML Mapping Profile http://www.mybatis.org/mybatis-3/zh/configuration.html * * @param root root node */ private void parseConfiguration(XNode root) { try { //issue #117 read properties first // Resolve <properties />tags propertiesElement(root.evalNode("properties")); // Resolve <settings />tags Properties settings = settingsAsProperties(root.evalNode("settings")); // Loading custom VFS implementation classes loadCustomVfs(settings); // Resolve <typeAliases />Tags typeAliasesElement(root.evalNode("typeAliases")); // Resolve <plugins />tags pluginElement(root.evalNode("plugins")); // Resolve <objectFactory />Tags objectFactoryElement(root.evalNode("objectFactory")); // Resolve <objectWrapperFactory />Tags objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // Resolve <reflectorFactory />Tags reflectorFactoryElement(root.evalNode("reflectorFactory")); // Assign <settings /> to Configuration property settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // Resolve <environments />tags environmentsElement(root.evalNode("environments")); // Resolve <databaseIdProvider />Label databaseIdProviderElement(root.evalNode("databaseIdProvider")); // Resolve <typeHandlers />Tags typeHandlerElement(root.evalNode("typeHandlers")); // Resolve <mappers />tags mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // 5. Called overloaded methods public SqlSessionFactory build(Configuration config) { //The DefaultSqlSessionFactory object is created and passed into the Configuration object. return new DefaultSqlSessionFactory(config); }
- When MyBatis is initialized, it loads all the configuration information for MyBatis into memory and stores it using the org.apache.ibatis.session.Configuratio n instance
Introduction to the 1.2.1 Configuration object
Configuration Object structure and xml Configuration files have nearly the same objects. Review xml What are the configuration labels in: properties (attribute),settings (Set up),typeAliases (Type Alias),typeHandlers (Type Processor),objectFactory (Object Factory),mappers (Mapper)etc. Configuration There are also corresponding object properties to encapsulate them That is, the essence of initializing profile information is creating Configuration Object that will be resolved xml Encapsulate data into Configuration In internal properties
Introduction to 1.2.2 MappedStatement
MappedStatement corresponds to a select/update/insert/delete node in the Mapper configuration file. The tags configured in mapper are encapsulated in this object and are used primarily to describe an SQL statement
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement> ("Mapped Statements collection")
2. Source Profiling-Executing SQL Process
Introduction to 2.1 SqlSession
SqlSession is an interface that has two implementation classes: DefaultSqlSession (default) and SqlSession Manager (deprecated, not described)
SqlSession is the top-level class in MyBatis for interacting with databases and is usually bound to ThreadLocal. A session uses a SqlSession and needs to close after use
SqlSession Two of the most important parameters in the configuration Same as at initialization, Executor For Executors public class DefaultSqlSession implements SqlSession { private final Configuration configuration; private final Executor executor; j
2.2 Introduction to Executor
Executor is also an interface and has three common implementation classes:
- BatchExecutor (reuse statements and perform bulk updates)
- ReuseExecutor (reuse preprocessing statement prepared statements)
- SimpleExecutor (normal executor, default)
SqlSession sqlSession = factory.openSession(); List<User> list = sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName"); //6. Enter the o penSession method. public SqlSession openSession() { //getDefaultExecutorType() passes SimpleExecutor return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //7. Enter openSessionFromDataSource. //ExecutorType is Executor type, TransactionIsolationLevel is transaction isolation level, whether autoCommit opens transaction //Multiple overloaded methods of openSession can specify the Executor type of the resulting SeqSession and transaction handling private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // Get Environment Object final Environment environment = configuration.getEnvironment(); // Create Transaction Object final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // Create Executor Object final Executor executor = configuration.newExecutor(tx, execType); // Create DefaultSqlSession object return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { // Close Transaction object if an exception occurs closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
Execute api in sqlsession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //Remove the MappedStatement object from the mapped Map based on the incoming fully qualified name + method name MappedStatement ms = configuration.getMappedStatement(statement); // Execute Query // Call method processing in Executor //RowBounds are used for logical division // wrapCollection(parameter) is used to decorate a collection or array parameter 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(); } }
Source Profiling-executor
//This method is implemented in the parent BaseExecutor of SimpleExecutor @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //Gets the SQL statement dynamically based on the parameters passed in, and returns the result as a BoundSql object BoundSql boundSql = ms.getBoundSql(parameter); //Create a cached Key for this query CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); // query return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } // Read operation from database private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; // In the cache, add a placeholder object. The placeholder here, related to delayed loading, is visible `DeferredLoad#canLoad()`method localCache.putObject(key, EXECUTION_PLACEHOLDER); try { // Perform read operations list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // Remove placeholder from cache localCache.removeObject(key); } // Add to Cache localCache.putObject(key, list); // Ignore for now, stored procedure related if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } 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(); // Incoming parameter creates StatementHanlder object to execute query StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // Creating statement objects in jdbc stmt = prepareStatement(handler, ms.getStatementLog()); // Perform StatementHandler, read return handler.query(stmt, resultHandler); } finally { // Close StatementHandler object closeStatement(stmt); } } // Initialize StatementHandler object private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // Get Connection Object Connection connection = getConnection(statementLog); // Create a Statement or PrepareStatement object stmt = handler.prepare(connection, transaction.getTimeout()); // Set parameters on SQL, such as placeholders on PrepareStatement objects handler.parameterize(stmt); return stmt; }
After several transitions in the Executor.query() method, a StatementHandler object is created, and the necessary parameters are passed to it
StatementHandler, which uses StatementHandler to complete a query to the database and ultimately returns a List result set
As you can see from the code above, Executor functions as follows:
Source Profiling-StatementHandler
The StatementHandler object does two main things:
- For objects of type PreparedStatement of JDBC, how many SQL statement strings do we use during creation? Placeholder, we will set the value of the placeholder later. StatementHandler sets the value of S tatement by parameterize(statement) method;
- StatementHandler completes execution of Statement by using the List query(Statement statement, ResultHandler resultHandler) method and encapsulates the resultSet returned by the Statement object into a List
public void parameterize(Statement statement) throws SQLException { //Setting Statement using the ParameterHandler object parameterHandler.setParameters((PreparedStatement) statement); } public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // Traversing the ParameterMapping array List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { // Get the ParameterMapping object ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { // Get value Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params 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); } // Get the typeHandler, jdbcType properties TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } // Set up? Placeholder parameters try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } }
The implementation of the Statement statement (ResultHandler resultHandler) method of the StatementHandler is a call to the handleResultSets(Statement) method of the ResultSetHandler.
The handleResultSets(Statement) method of ResultSetHandler converts the resultSet result set generated after the Statement statement is executed into a List result set
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // Execute Query ps.execute(); // Processing Return Results return resultSetHandler.handleResultSets(ps); } public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); // A result set of multiple ResultSets, one Object object for each ResultSet. In fact, each Object is a List<Object>object. // Regardless of the multiple ResultSets of stored procedures, a common query is actually a ResultSet, that is, a multipleResults is at most one element. final List<Object> multipleResults = new ArrayList<>(); int resultSetCount = 0; // Get the first ResultSet object and encapsulate it as a ResultSetWrapper object ResultSetWrapper rsw = getFirstResultSet(stmt); // Get ResultMap Array // Regardless of the multiple ResultSets of stored procedures, a common query is actually a ResultSet, that is, a resultMaps is an element. List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); // check while (rsw != null && resultMapCount > resultSetCount) { // Get ResultMap Objects ResultMap resultMap = resultMaps.get(resultSetCount); // Processing ResultSet, adding results to multipleResults handleResultSet(rsw, resultMap, multipleResults, null); // Get the next ResultSet object and encapsulate it as a ResultSetWrapper object rsw = getNextResultSet(stmt); // Clear cleanUpAfterHandlingResultSet(); // resultSetCount ++ resultSetCount++; } // Because `mappedStatement.resultSets'is only used in stored procedures, this series is ignored for the moment 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++; } } // If it is a multipleResults element, the first element returns return collapseSingleResultList(multipleResults); }
2. Mapper Agent Method
Handling of interfaces during MyBatis initialization: MapperRegistry is a property of Configuration that maintains a HashMap factory class for the mapper interface, one for each interface. The package path of an interface or a specific interface class can be configured in mappers.
Source Profiling-getmapper()
public <T> T getMapper(Class<T> type) { return configuration.getMapper(type, this); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // Get MapperProxyFactory object final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); // If it does not exist, throw a BindingException exception if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } ///Generate instances through dynamic agent factories. try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } //newInstance method in MapperProxyFactory class public T newInstance(SqlSession sqlSession) { // An implementation class mapperProxy for the invocationHandler interface that creates the JDK dynamic proxy final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); // Invoke overloaded method return newInstance(mapperProxy); } protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); }
Source Profiling-invoke()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // If it is an Object-defined method, call it directly if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // Get MapperMethod Object final MapperMethod mapperMethod = cachedMapperMethod(method); // Here's the point: MapperMethod ultimately calls the method of execution return mapperMethod.execute(sqlSession, args); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; //Determine the type of method in mapper, and ultimately the method in SqlSession switch (command.getType()) { case INSERT: { // Conversion parameters Object param = method.convertArgsToSqlCommandParam(args); // Perform INSERT operation // Convert rowCount result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { // Conversion parameters Object param = method.convertArgsToSqlCommandParam(args); // Convert rowCount result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { // Conversion parameters Object param = method.convertArgsToSqlCommandParam(args); // Convert rowCount result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: // If there is no return and there are ResultHandler method parameters, the results of the query are submitted to ResultHandler for processing if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; // Execute the query and return the list } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); // Execute the query and return to Map } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); // Execute the query and return to Cursor } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); // Execute the query and return a single object } else { // Conversion parameters Object param = method.convertArgsToSqlCommandParam(args); // Query Single 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()); } // A BindingException exception is thrown if the result is null and the return type is base 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 results return result; }