How Mybatis works

Posted by rochakchauhan on Wed, 12 Jan 2022 09:13:53 +0100

1. First create the SqlSessionFactory factory class through SqlSessionFactory builder, and then create the SqlSession object through the SqlSessionFactory factory class.

  • Each mybatis config XML corresponds to a SqlSessionFactory object, that is, if there are multiple sets of environments, each environment creates a SqlSessionFactory
  • Once SqlSessionFactory is created, it should always exist during the operation of the application. It is recommended to use singleton mode or static singleton mode.
//sqlSessionFactory produces sqlsessions in factory mode
public class MybatisUtils {
 
    private static SqlSessionFactory sqlSessionFactory;
 
    static {
        //The first step in using the mybatis tool class -- get the SqlSessionFactory object
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    //Now that we have SqlSessionFactory, as the name suggests, we can get an instance of SqlSession from it.
    // SqlSession completely contains all the methods required to execute SQL commands for the database.
    // You can directly execute the mapped SQL statements through the SqlSession instance.
    public static SqlSession getSqlSession(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
 
}

2. SqlSession interface

//The SqlSession interface source code is as follows:

package org.apache.ibatis.session;

import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.executor.BatchResult;

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  <E> List<E> selectList(String statement);

  <E> List<E> selectList(String statement, Object parameter);

  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);

  <K, V> Map<K, V> selectMap(String statement, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);

  <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}
  • As its name implies, Sqlsession corresponds to a database session.
  • It is a single thread object that performs interactive operations between the application and the persistence layer. The SqlSession object completely contains all methods for executing SQL operations based on the database. Its bottom layer encapsulates the JDBC connection, and the mapped SQL statements can be directly executed by the SqlSession instance
  • Because the database session is not permanent, the life cycle of the Sqlsession should not be permanent. On the contrary, you need to create it every time you access the database (of course, it does not mean that sql can only be executed once in the Sqlsession. You can execute multiple times. Once the Sqlsession is closed, you need to recreate it).
  • There is only one place to create a Sqlsession, that is, the openSession method of SqlsessionFactory
    public SqlSession openSession() {
        return openSessionFromDataSource(configuration.getDefaultExecutorType(),null, false);
    }
  • We can see that the place where SqlSession is actually created is openSessionFromDataSource, as follows:
    private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevellevel, boolean autoCommit) {
 
        Connection connection = null;
 
        try {
 
            finalEnvironment environment = configuration.getEnvironment();
 
            final DataSourcedataSource = getDataSourceFromEnvironment(environment);
 
           TransactionFactory transactionFactory =getTransactionFactoryFromEnvironment(environment);
 
           connection = dataSource.getConnection();
 
            if (level != null) {
 
               connection.setTransactionIsolation(level.getLevel());
 
            }
 
           connection = wrapConnection(connection);
 
           Transaction tx = transactionFactory.newTransaction(connection,autoCommit);
 
            Executor executor = configuration.newExecutor(tx, execType);
 
            return newDefaultSqlSession(configuration, executor, autoCommit);
 
        } catch (Exceptione) {
 
           close Connection(connection);
 
            throwExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
 
        } finally {
 
           ErrorContext.instance().reset();
 
        }
 
    }

It can be seen that the following main steps are taken to create an sqlsession:

1) Get the Environment from the configuration;

2) Obtain DataSource from Environment;

3) Obtain TransactionFactory from Environment;

4) Obtain the database Connection object Connection from the DataSource;

5) Create a Transaction object Transaction on the obtained database connection;

6) Create the Executor object (this object is very important. In fact, all operations of sqlsession are completed through it);

7) Create an sqlsession object.
 

3. Executor interface: executor will be created when sqlsession is created. Sqlsession's operations on the database are completed through the executor, and the access to sqlsession methods will eventually fall to the corresponding methods of the executor.

public interface Executor {
 
 
 
  ResultHandler NO_RESULT_HANDLER = null;
 
 
 
  int update(MappedStatement ms, Object parameter) throws SQLException;
 
 
 
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
 
 
 
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
 
 
 
  List<BatchResult> flushStatements() throws SQLException;
 
 
 
  void commit(booleanrequired) throws SQLException;
 
 
 
  void rollback(booleanrequired) throws SQLException;
 
 
 
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
 
 
 
  boolean isCached(MappedStatement ms, CacheKey key);
 
 
 
  void clearLocalCache();
 
 
 
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
 
 
 
  Transaction getTransaction();
 
 
 
  void close(booleanforceRollback);
 
 
 
  boolean isClosed();
 
 
 
  void setExecutorWrapper(Executor executor);
 
 
 
}
  • Executor interface implementation class: Mybatis. The default executor is simpleexecution

The Executor mainly completes the following contents:

  1. Processing cache, including L1 cache and L2 cache
  2. Get database connection
  3. Create a Statement or PrepareStatement object
  4. Accessing the database and executing SQL statements
  5. Processing database returned results

I have to mention the first level cache and second level cache of mybatis

There are L1 cache and L2 cache in Mybatis. By default, L1 cache is enabled and cannot be closed. L2 cache is off by default.

The first level cache refers to the SqlSession level cache. When the same SQL statement query is performed in the same SqlSession, the query after the second time will not be queried from the database, but directly obtained from the cache. The first level cache can cache up to 1024 SQL statements.

L2 cache refers to a cache that can span sqlsessions. It is a mapper level cache. For mapper level cache, different sqlsessions can be shared.

  • The scope of L1 cache is within an sqlsession;
  • The L2 cache scope is to cache mapper;

L1 cache

public class CacheTest extends BaseMapperTest {
    @Test
    public void testL1Cache(){
        //Get SqlSession
        SqlSession sqlSession = getSqlSession();
        SysUser user1 = null;
        try {
            //Get UserMapper interface
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //Call the selectById method
            user1 = userMapper.selectById(1L);
            //Reassign the currently acquired object
            user1.setUserName("New Name");
            //Query again to obtain users with the same id
            SysUser user2 = userMapper.selectById(1L);
            //Although the database is not updated, user1 and user2 have the same name
            Assert.assertEquals("New Name",user2.getUserName());
            //In any case, user1 and user2 are the same instance
            Assert.assertEquals(user1,user2);
        }finally {
            sqlSession.close();
        }
        System.out.println("Open new SqlSession");
        //Open a new sqlsession
        sqlSession = getSqlSession();
        try {
            //Get UserMapper interface
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            SysUser user2 = userMapper.selectById(1L);
            Assert.assertNotEquals("New Name",user2.getUserName());
            //user2 here is different from the result of the previous session query
            Assert.assertNotEquals(user1,user2);
            //Perform delete operation
            userMapper.deleteById(2L);
            //Get user3
            SysUser user3 = userMapper.selectById(1L);
            //user2 and user3 here are two different instances
            Assert.assertNotEquals(user2,user3);
        }finally {
            sqlSession.close();
        }
    }
}

The running results are as follows: in the first try statement block, we wrote a statement to query the database twice, but only one sql was printed, which proves that the second item did not query the database, but the cache, and user1 and user2 are the same instance. First, obtain the value of user1 from the database, and user1 assigns a new value to userName, The second query of user2 found that the userName of user2 is the modified value of user1.
Mybatis level-1 cache exists in the life cycle of SqlSession. When querying in the same SqlSession, mybatis will generate cached key values through the algorithm for the executed methods and parameters, and put the key values and query results into a Map. If the methods and parameters executed in the same SqlSession are completely consistent, the same key values will be generated through the algorithm, When the key value already exists in the Map cache object, the cached object will be returned
In the second try statement block, we retrieve a new sqlsession. The query result shows that user2 has no relationship with user1 in the first statement block. After we delete, we execute the same query with the same sqlsession, and the result is assigned to user3. The result indicates that user2 and user3 are different instances because of all inserts, updates, The delete operation will empty the L1 cache.

Different sqlsession s

 @Test
public void differSqlSession() {
    SqlSession sqlSession = null;
    SqlSession sqlSession2 = null;
    try {
        sqlSession = sqlSessionFactory.openSession();

        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        // Execute first query
        List<Student> students = studentMapper.selectAll();
        for (int i = 0; i < students.size(); i++) {
            System.out.println(students.get(i));
        }
        System.out.println("=============Start different Sqlsession Second query of============");
        // Create a new sqlSession2 for the second query
        sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        List<Student> stus = studentMapper2.selectAll();
        // Unequal
        Assert.assertNotEquals(students, stus);
        for (int i = 0; i < stus.size(); i++) {
            System.out.println("stus:" + stus.get(i));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (sqlSession != null) {
            sqlSession.close();
        }
        if (sqlSession2 != null) {
            sqlSession2.close();
        }
    }
}

L2 cache

In Mybatis global configuration settings, there is a parameter cacheEnabled, which is the global switch of L2 cache The default is true, so it can not be configured. If it is set to false, all cached configurations in the back will not work.

The L2 cache in mybatis is a mapper level cache

It is worth noting that different mappers have a L2 cache, that is, the L2 cache between different mappers does not affect each other. To describe L2 cache more clearly, let's take a look at a schematic diagram:

  • sqlSession1 queries the user information with user id 1. If the user information is queried, the query data will be stored in the secondary cache of the UserMapper.
  • If SqlSession3 executes sql under the same mapper and commit s, the data in the secondary cache area under the UserMapper will be emptied.
  • sqlSession2 queries the user information with user id 1 to find out whether there is data in the cache. If there is data, it can be directly extracted from the cache.
     

CachingExecutor

Cacheingexecution is used to process the L2 cache. If there is no data to query in the cache, the query request is delegated to other executors. If the SQL is added, deleted or modified, the cacheingexecution will empty the L2 cache.

 BaseExecutor

BaseExecutor is the base class of other Executor implementation classes except cachingeexecutor. This class mainly deals with L1 cache.

When calling the query method of this class, first check whether there is data in the L1 cache. If so, it will be obtained directly from the cache. If not, it will be obtained from the database by calling the query method of the subclass.

SimpleExecutor 

Simpleexecution inherits from BaseExecutor, which is relatively simple.
When adding, deleting, and modifying queries are performed, this class obtains the database connection, creates PrepareStatement or Statement objects, and executes SQL statement Finally, the returned result of the database is converted into the set object. Take the doQuery method as an example:

  //This method is called when the parent class executes the query
  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();
       //Create StatementHandler object
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //Get the database connection and create a Statement or PrepareStatement
      stmt = prepareStatement(handler, ms.getStatementLog());
      //Execute SQL statements to convert the returned results of the database into set objects, such as List, Map or POJO
      return handler.<E>query(stmt, resultHandler);
    } finally {
      //Close the Statement object
      closeStatement(stmt);
    }
  }

Topics: Java Mybatis