Catalog
Use Mybatis on its own
This article focuses on analyzing the Mybatis framework's processes for executing SQL.
Recalling that we used the Mybatis semi-automation framework on our own, we needed to follow these steps:
- Read the configuration file (mybatis-config.xml) and initialize the configuration class, configuration;
- Create SQLSessionFactory;
- Create SqlSession;
- Execute SQL, process result set
Corresponds to the following code:
public class MyBatisUtils { private final static SqlSessionFactory SQL_SESSION_FACTORY; static { String resource = "mybatis-config.xml"; Reader reader = null; try { reader = Resources.getResourceAsReader(resource); } catch (IOException e) { e.printStackTrace(); } SQL_SESSION_FACTORY = new SqlSessionFactoryBuilder().build(reader); } public static SqlSessionFactory getSqlSessionFactory() { return SQL_SESSION_FACTORY; } } //unit testing public class MapperTest { static SqlSessionFactory sqlSessionFactory = null; static { sqlSessionFactory = MyBatisUtils.getSqlSessionFactory(); } @Test public void testAdd() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setUsername("hello"); user.setAge(5); userMapper.insert(user); sqlSession.commit(); // Be sure to submit here, or the data won't go into the database } finally { sqlSession.close(); } } @Test public void getUser() { SqlSession sqlSession = sqlSessionFactory.openSession(); try { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectByPrimaryKey(32L); System.out.println("name: " + user.getUsername() + "| age: " + user.getAge()); } finally { } } }
Mybatis Execution Process
Based on the above instructions, we have a basic understanding of the execution process, below we will explain the process from the source level.
SqlSessionFactory\SqlSession
When using Mybatis on its own, the first step is to read the configuration file, so we'll start by reading the configuration file.
- SqlSessionFactoryBuilder reads mybatis configuration file and calls build method to create DefaultSqlSessionFactory
/**Create SQLSessionFactory object by parsing mybatis configuration with XMLConfigBuilder*/ public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //The parse() method XMLConfigBuilder class parses the xml file and completes the creation of the configuration property 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. } } } /** * The build() method above calls this method by default, creating a DefaultSqlSessionFactory object * After parsing the mybatis configuration file xml, generate the Configuration object, then generate the SQLSessionFactory object * @param config * @return */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
- After getting the SqlsessionFactory, call the openSession() method to create the SQLSession, which illustrates that SQLSession only provides support for database operations outward. It is execute's duty to actually perform operations on the database.
DefaultSqlSessionFactory class /** * This method is typically invoked by a series of openSession methods * @param execType * @param level * @param autoCommit * @return */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //Get Mybatis configuration information through the Confuguration object, which contains configuration of data sources and transactions final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //Previously, on the surface, we use sqlSession to execute sql statements. Actually, it is executed through excutor, which is the encapsulation of Statement. final Executor executor = configuration.newExecutor(tx, execType); //Create SqlSession return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { // may have fetched a connection so lets call close() closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
After obtaining the SQLSession, the preparation for the CRUD operation on the data is formally completed.
MapperProxy
Mainly handles our Mybatis mapping file.This class is primarily responsible for mapper s in proxy development.So think about how this proxy object is obtained?
Below we trace from SQLSession.
DefaultSQLSession In class public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } /** * Get the proxy object for the Mapper interface */ @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //Get the proxy factory object for the Mapper interface from the cache final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); //Throw an exception if the Mapper interface is not registered if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //**Use proxy factories to create proxy objects for Mapper interfaces**Key Parts return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } MapperProxyFactory class /** * Create a proxy object for mapperInterface */ @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); }
Along the way, we found that the MapperProxy object was created in MapperProxyFactory.
Excutor
We mentioned above that Excutor is responsible for crud operations on data, and the time series diagram above details the execution of SQL.
For each MapperProxy that corresponds to a developer's own Mapper(dao) interface, we'll track how this is done from the source code.
- MappProxy:
/**The method executed by the proxy object that will be called after the proxy by all Mapper method calls*/ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //To determine if it is a generic class, mybatis uses the JDK proxy method, which is interface-oriented, false //method.getDeclaringClass() returns the class of the underlying class object if (Object.class.equals(method.getDeclaringClass())) { //If it's a class, use the reflection mechanism to return the target object return method.invoke(this, args); } else if (isDefaultMethod(method)) { //If it is the default method, the default method is executed, provided by Java 1.8; return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //Gets a MapperMethod object from the cache, creates one if it does not exist in the cache, and adds it to the cache final MapperMethod mapperMethod = cachedMapperMethod(method); //Execute the corresponding SQL statement to return the results of the query /** * 1,First determine the method type of SQL, insert, select, update * 2,map object that converts a parameter name to a relationship between the value of the parameter list * 3,Query the result list based on the relationship between the method name and the parameter list */ return mapperMethod.execute(sqlSession, args); }
- MapperMethod:
/** * Core method for executing corresponding SQL statements * @param sqlSession * @param args * @return */ public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { //param is the corresponding relationship between the parameter name and the parameter value, that is, the map set 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()) { //void type and method with ResultHandler parameter (special parameter), call sqlSession.select to execute executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { //The return type is either a collection or an array and is executed by calling sqlSession. <E>selectList result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { //Return to map, call sqlSession. <K, V>selectMap 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; }
This article mainly analyses the execution process of SQL, so we recommend taking selectList as an example to familiarize you with the process, which will be introduced later
The process of each step.
Return to SQLSession
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { 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(); } }
- BaseExecutor:
/** * Query methods, which specifically provide methods for select execution */ @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //Get Query SQL BoundSql boundSql = ms.getBoundSql(parameter); //Create the cached key, which is the key in HashMap CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //Execute Query return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
- SimpleExecutor:
@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
- preparedStatement:
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.handleResultSets(ps); }
Reference: Spring Source Depth Resolution 2nd Edition