A thorough understanding of mybatis

mybatis execution principle

Core class

  • SqlSessionFactoryBuilder 
    Mainly used for reading mybatis Profiles, creating SqlSessionFactory object
    
  • DefaultSqlSessionFactory
    SqlSessionFactory The default implementation class of the interface, holding Configuration,For obtaining SqlSession
    
  • Configuration
     hold MapperRegistry,And cache stateMentId reach MappedStatement Mapping relationship of
    
  • MapperRegistry
     hold Configuration Reference and cache Class To specific MapperProxyFactory Mapping of
    
  • MapperProxyFactory
     establish mapper The factory of the proxy object. The corresponding data needs to be passed in the construction method mapper Interface and cache Method reach MapperMethod Mapping of
    
  • MapperProxy
     realization InvocationHandler Interface, invoke Call in method sqlsession Complete database operation
    
  • MapperMethod
     Hold specific sql Statement and method related parameters, execute the corresponding sql sentence
    
  • DefaultSqlSession
     hold Configuration References and Executor Reference, call Executor implement sql
    
  • Executor
     The default implementation class is SimpleExecutor,Complete the initialization of database connection in it, and complete jdbc operation
    

Initialization process

  • SqlSessionFactoryBuilder loads the mybatis Configuration, creates the Configuration object, and completes the initialization of MapperRegistry
  • Call the build method to create the DefaultSqlSessionFactory.

Call process

  • Call the openSession method of SqlSessionFactory to obtain the SqlSession object

  • Call the getMapper method of SqlSession to obtain the proxy object. The internal call logic is as follows

    DefaultSqlSession 
        public <T> T getMapper(Class<T> type) {
            return configuration.<T>getMapper(type, this);
        }
    Configuration
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    MapperRegistry
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        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);
        }
      }
    MapperProxyFactory
        public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, 		mapperProxy);
      }  
    
  • Call the interface method of the obtained proxy object. The specific call logic is as follows

    MapperProxy
      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 (isDefaultMethod(method)) {
            return invokeDefaultMethod(proxy, method, args);
          }
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
      }
    MapperMethod
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
        	Object param = method.convertArgsToSqlCommandParam(args);
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          //Omit code such as update and select
          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
      public int insert(String statement, Object parameter) {
        return update(statement, parameter);
      }
      public int update(String statement, Object parameter) {
        try {
          dirty = true;
          MappedStatement ms = configuration.getMappedStatement(statement);
          return executor.update(ms, wrapCollection(parameter));
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    executor.update Then it is the original jdbc logic  
    

Write a simplified version of mybatis

Define SqlSessionFactoryBuilder, SqlSessionFactory and Configuration classes

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(String fileName) {

        InputStream inputStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream(fileName);
        return build(inputStream);
    }

    public SqlSessionFactory build(InputStream inputStream) {
        try {
            Configuration.PROPS.load(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new DefaultSqlSessionFactory(new Configuration());
    }
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
        loadMappersInfo(Configuration.getProperty(Constant.MAPPER_LOCATION).replaceAll("\\.", "/"));
    }

    @Override
    public SqlSession openSession() {
        SqlSession session = new DefaultSqlSession(this.configuration);
        return session;
    }

    private void loadMappersInfo(String dirName) {
        URL resources = DefaultSqlSessionFactory.class.getClassLoader().getResource(dirName);
        File mappersDir = new File(resources.getFile());
        if (mappersDir.isDirectory()) {
            // Display all files under the package
            File[] mappers = mappersDir.listFiles();
            if (CommonUtil.isNotEmpty(mappers)) {
                for (File file : mappers) {
                    // Continue recursion on folders
                    if (file.isDirectory()) {
                        loadMappersInfo(dirName + "/" + file.getName());

                    } else if (file.getName().endsWith(Constant.MAPPER_FILE_SUFFIX)) {

                        // Parsing only XML files
                        XmlUtil.readMapperXml(file, this.configuration);
                    }

                }

            }
        }

    }

}
public class Configuration {
    /**
     * Configuration item
     */
    public static Properties PROPS = new Properties();

    /**
     * mapper Proxy registrar
     */
    protected final MapperRegistry mapperRegistry = new MapperRegistry();

    /**
     * mapper id and SQL statement attribute of the select/update statement of the file
     **/
    protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();

    /**
     * Gets a character type attribute (the default value is an empty string)
     *
     * @param key
     * @return
     */
    public static String getProperty(String key) {
        return getProperty(key, "");
    }

    /**
     * Get character type properties (default values can be specified)
     *
     * @param key
     * @param defaultValue Default value
     * @return
     */
    public static String getProperty(String key, String defaultValue) {
        return PROPS.containsKey(key) ? PROPS.getProperty(key) : defaultValue;
    }

    /**
     * addMapper
     */
    public <T> void addMapper(Class<T> type) {
        this.mapperRegistry.addMapper(type);
    }

    /**
     * getMapper
     */
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

    /**
     * addMappedStatement
     */
    public void addMappedStatement(String key, MappedStatement mappedStatement) {
        this.mappedStatements.put(key, mappedStatement);
    }

    /**
     * Get MappedStatement
     */
    public MappedStatement getMappedStatement(String id) {
        return this.mappedStatements.get(id);
    }

}

Define the built-in objects that are dependent in the configuration. MapperProxy is the key for mybatis to generate proxy objects

public class MapperRegistry {
    /**
     * the knownMappers
     */
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

    /**
     * Registered agent factory
     *
     * @param type
     */
    public <T> void addMapper(Class<T> type) {
        this.knownMappers.put(type, new MapperProxyFactory<T>(type));
    }

    /**
     * Get agent factory instance
     *
     * @param type
     * @param sqlSession
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) this.knownMappers.get(type);

        return mapperProxyFactory.newInstance(sqlSession);
    }
}
public class MapperProxyFactory<T> {

    private final Class<T> mapperInterface;

    /**
     * Initialization method
     *
     * @param mapperInterface
     */
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    /**
     * Create a proxy based on sqlSession
     *
     * @param sqlSession
     * @return
     * @see
     */
    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, this.mapperInterface);
        return newInstance(mapperProxy);
    }

    /**
     * Returns an instance based on the mapper proxy
     *
     * @param mapperProxy
     * @return
     * @see
     */
    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface},
                mapperProxy);
    }
}
public class MapperProxy<T> implements InvocationHandler, Serializable {

    private static final long serialVersionUID = -7861758496991319661L;

    private final SqlSession sqlSession;

    private final Class<T> mapperInterface;

    /**
     * Construction method
     *
     * @param sqlSession
     * @param mapperInterface
     */
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }

    /**
     * Real execution method
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }
            return this.execute(method, args);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    /**
     * Perform corresponding operations according to SQL instructions
     *
     */
    private Object execute(Method method, Object[] args) {
        String statementId = this.mapperInterface.getName() + "." + method.getName();
        MappedStatement ms = this.sqlSession.getConfiguration().getMappedStatement(statementId);

        Object result = null;
        switch (ms.getSqlCommandType()) {
            case SELECT: {
                Class<?> returnType = method.getReturnType();
                // If a list is returned, the method of querying multiple results should be called. Otherwise, just query a single record
                if (Collection.class.isAssignableFrom(returnType)) {
                    //The ID is mapper class full name + method name
                    result = sqlSession.selectList(statementId, args);
                } else {
                    result = sqlSession.selectOne(statementId, args);
                }
                break;
            }
            case UPDATE: {
                sqlSession.update(statementId, args);
                break;
            }
            default: {
                break;
            }
        }
        return result;
    }

}

Define the built-in objects that are dependent in the configuration. MapperProxy is the key for mybatis to generate proxy objects

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;

    private final Executor executor;

    /**
     * Default construction method
     *
     * @param configuration
     */
    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
        this.executor = new SimpleExecutor(configuration);
    }

    /**
     * Query strip record
     */
    @Override
    public <T> T selectOne(String statementId, Object parameter) {
        List<T> results = this.<T>selectList(statementId, parameter);

        return CommonUtil.isNotEmpty(results) ? results.get(0) : null;
    }

    /**
     * Query multiple records
     *
     * @param statementId ID mapper class full name + method name
     * @param parameter   parameter list
     * @return
     */
    @Override
    public <E> List<E> selectList(String statementId, Object parameter) {
        MappedStatement mappedStatement = this.configuration.getMappedStatement(statementId);
        return this.executor.<E>doQuery(mappedStatement, parameter);
    }

    /**
     * to update
     *
     * @param statementId
     * @param parameter
     */
    @Override
    public void update(String statementId, Object parameter) {
        MappedStatement mappedStatement = this.configuration.getMappedStatement(statementId);
        this.executor.doUpdate(mappedStatement, parameter);
    }

    @Override
    public void insert(String statementId, Object parameter) {
        //TODO to be implemented
    }

    /**
     * Get Mapper
     */
    @Override
    public <T> T getMapper(Class<T> type) {
        return configuration.<T>getMapper(type, this);
    }

    /**
     * getConfiguration
     *
     * @return
     */
    @Override
    public Configuration getConfiguration() {
        return this.configuration;
    }

}
public class SimpleExecutor implements Executor {
    /**
     * Database connection
     */
    private static Connection connection;

    static {
        initConnect();
    }

    private Configuration conf;

    /**
     * Initialization method
     *
     * @param configuration
     */
    public SimpleExecutor(Configuration configuration) {
        this.conf = configuration;
    }

    /**
     * Statically initialize database connections
     *
     * @return
     */
    private static void initConnect() {

        String driver = Configuration.getProperty(Constant.DB_DRIVER_CONF);
        String url = Configuration.getProperty(Constant.DB_URL_CONF);
        String username = Configuration.getProperty(Constant.DB_USERNAME_CONF);
        String password = Configuration.getProperty(Constant.db_PASSWORD);

        try {
            Class.forName(driver);
            connection = DriverManager.getConnection(url, username, password);
            System.out.println("Database connection succeeded");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Query database according to parameters
     *
     * @param ms
     * @param parameter
     * @return
     */
    @Override
    public <E> List<E> doQuery(MappedStatement ms, Object parameter) {

        try {
            //1. Get database connection
            Connection connection = getConnect();

            //2. Get MappedStatement information, which contains sql information
            MappedStatement mappedStatement = conf.getMappedStatement(ms.getSqlId());

            //3. Instantiate StatementHandler object,
            StatementHandler statementHandler = new SimpleStatementHandler(mappedStatement);

            //4. Obtain PreparedStatement through StatementHandler and connection
            PreparedStatement preparedStatement = statementHandler.prepare(connection);

            //5. Instantiate the ParameterHandler and add it to the SQL statement? Parameterization
            ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
            parameterHandler.setParameters(preparedStatement);

            //6. Execute SQL to get the result set ResultSet
            ResultSet resultSet = statementHandler.query(preparedStatement);

            //7. Instantiate ResultSetHandler and set the result in ResultSet to the target resultType object through reflection
            ResultSetHandler resultSetHandler = new DefaultResultSetHandler(mappedStatement);
            return resultSetHandler.handleResultSets(resultSet);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * doUpdate
     *
     * @param ms
     * @param parameter
     */
    @Override
    public void doUpdate(MappedStatement ms, Object parameter) {
        try {
            //1. Get database connection
            Connection connection = getConnect();

            //2. Get MappedStatement information, which contains sql information
            MappedStatement mappedStatement = conf.getMappedStatement(ms.getSqlId());

            //3. Instantiate StatementHandler object,
            StatementHandler statementHandler = new SimpleStatementHandler(mappedStatement);

            //4. Obtain PreparedStatement through StatementHandler and connection
            PreparedStatement preparedStatement = statementHandler.prepare(connection);

            //5. Instantiate the ParameterHandler and add it to the SQL statement? Parameterization
            ParameterHandler parameterHandler = new DefaultParameterHandler(parameter);
            parameterHandler.setParameters(preparedStatement);

            //6. Execute SQL
            statementHandler.update(preparedStatement);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * getConnect
     *
     * @return
     * @throws SQLException
     */
    public Connection getConnect() throws SQLException {
        if (null != connection) {
            return connection;
        } else {
            throw new SQLException("Unable to connect to the database, please check the configuration");
        }
    }

}

Define the StatementHandler (sql preprocessing), ParameterHandler (setting parameters), ResultSetHandler (result mapping) that the Executor depends on

public class SimpleStatementHandler implements StatementHandler {
    /**
     * #{}Regular matching
     */
    private static Pattern param_pattern = Pattern.compile("#\\{([^\\{\\}]*)\\}");

    private MappedStatement mappedStatement;

    /**
     * Default construction method
     *
     * @param mappedStatement
     */
    public SimpleStatementHandler(MappedStatement mappedStatement) {
        this.mappedStatement = mappedStatement;
    }

    /**
     * Replace #{} in the SQL statement with?, The source code is parsed in the SqlSourceBuilder class
     *
     * @param source
     * @return
     */
    private static String parseSymbol(String source) {
        source = source.trim();
        Matcher matcher = param_pattern.matcher(source);
        return matcher.replaceAll("?");
    }

    @Override
    public PreparedStatement prepare(Connection paramConnection)
            throws SQLException {
        String originalSql = mappedStatement.getSql();

        if (CommonUtil.isNotEmpty(originalSql)) {
            // Replace #{}, preprocess, prevent SQL injection
            return paramConnection.prepareStatement(parseSymbol(originalSql));
        } else {
            throw new SQLException("original sql is null.");
        }
    }

    /**
     * query
     *
     * @param preparedStatement
     * @return
     * @throws SQLException
     */
    @Override
    public ResultSet query(PreparedStatement preparedStatement)
            throws SQLException {
        return preparedStatement.executeQuery();
    }

    /**
     * update
     *
     * @param preparedStatement
     * @throws SQLException
     */
    @Override
    public void update(PreparedStatement preparedStatement)
            throws SQLException {
        preparedStatement.executeUpdate();
    }

}
public class DefaultParameterHandler implements ParameterHandler {

    private Object parameter;

    public DefaultParameterHandler(Object parameter) {
        this.parameter = parameter;
    }

    /**
     * Set the SQL parameter to PreparedStatement
     */
    @Override
    public void setParameters(PreparedStatement ps) {

        try {

            if (null != parameter) {
                if (parameter.getClass().isArray()) {
                    Object[] params = (Object[]) parameter;
                    for (int i = 0; i < params.length; i++) {
                        //Mapper ensures that the types of the incoming parameters match, so there is no type conversion here
                        ps.setObject(i + 1, params[i]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
public class DefaultResultSetHandler implements ResultSetHandler {

    private final MappedStatement mappedStatement;

    /**
     * @param mappedStatement
     */
    public DefaultResultSetHandler(MappedStatement mappedStatement) {
        this.mappedStatement = mappedStatement;
    }

    /**
     * Process the query results and set them to the returned entity class through reflection
     */
    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> handleResultSets(ResultSet resultSet) {
        try {

            List<E> result = new ArrayList<>();

            if (null == resultSet) {
                return null;
            }

            while (resultSet.next()) {
                // Instantiate the return class through reflection
                Class<?> entityClass = Class.forName(mappedStatement.getResultType());
                E entity = (E) entityClass.newInstance();
                Field[] declaredFields = entityClass.getDeclaredFields();

                for (Field field : declaredFields) {
                    // Assign values to member variables
                    field.setAccessible(true);
                    Class<?> fieldType = field.getType();

                    // At present, only string and int conversions are implemented
                    if (String.class.equals(fieldType)) {
                        field.set(entity, resultSet.getString(field.getName()));
                    } else if (int.class.equals(fieldType) || Integer.class.equals(fieldType)) {
                        field.set(entity, resultSet.getInt(field.getName()));
                    } else {
                        // Other types can be converted by themselves, which is directly set here
                        field.set(entity, resultSet.getObject(field.getName()));
                    }
                }

                result.add(entity);
            }

            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

The related dependencies and tool classes will not be posted, and the source code will be placed at the end of the article

mybatis advanced features

Plug in mechanism

mybatis adopts the responsibility chain mode, organizes multiple plug-ins through dynamic agents, and changes the default sql behavior through plug-ins. myabtis allows plug-ins to intercept four objects: Executor, ParameterHandler, ResultSetHandler and StatementHandler.

Related source code

//Create parameter processor
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    //Create ParameterHandler
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    //The plug-in is inserted here
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  //Create result set processor
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    //Create defaultresultsethandler (the older version 3.1 is to create NestedResultSetHandler or FastResultSetHandler)
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    //The plug-in is inserted here
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  //Create statement processor
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //Create routing statement processor
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    //The plug-in is inserted here
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  //Generating actuator
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //Determine the type of actuator used
    executorType = executorType == null ? defaultExecutorType : executorType;
    //This sentence is another protection to prevent careless people from setting defaultExecutorType to null?
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //Then there are three simple branches, which produce three kinds of executors: batchexecutor / reuseexecution / simpleexecution
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //If caching is required, another cacheingexecution is generated (there is caching by default), decorator mode, so cacheingexecution is returned by default
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //The plug-in is called here. The behavior of the Executor can be changed through the plug-in
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Each interceptor proxies the target class once

     /**
     *@target
     *@return Objects after layer proxy
     */
    public Object pluginAll(Object target) {
        //Loop through each interceptor Plugin method
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

Interceptor interface description

public interface Interceptor {

  /**
   * Method of executing interception logic
   *
   * @param invocation Call information
   * @return Call result
   * @throws Throwable abnormal
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * proxy class
   *
   * @param target
   * @return
   */
  Object plugin(Object target);

  /**
   * Initialize the Interceptor method according to the configuration
   * @param properties
   */
  void setProperties(Properties properties);

}

Annotate interceptor and sign

@Intercepts(@Signature(
        type = StatementHandler.class,  //Types of four objects to intercept
        method = "prepare",             //Which method in the intercept object
        args = {Connection.class, Integer.class}   //Parameters to be passed in
))

Handwriting paging plug-in

Passing paging parameters based on ThreadLocal to intercept StatementHandler

@Intercepts(@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class, Integer.class}
))
public class PagePlugin implements Interceptor {
    // Core business of plug-ins
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        /**
         * 1,Get the original sql statement
         * 2,Modify the original sql and add paging select * from t_user limit 0,3
         * 3,Total number of jdbc queries executed
         */
        // Get our StatementHandler object from invocation
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // Get the original sql statement
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();

        // statementHandler to metaObject
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

        // spring context.getBean("userBean")
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        // Gets the method name selectUserByPage in the mapper interface
        String mapperMethodName = mappedStatement.getId();
        if (mapperMethodName.matches(".*ByPage")) {
            Page page = PageUtil.getPaingParam();
            //  select * from user;
            String countSql = "select count(0) from (" + sql + ") a";
            System.out.println("Total number of queries sql : " + countSql);

            // Perform jdbc operations
            Connection connection = (Connection) invocation.getArgs()[0];
            PreparedStatement countStatement = connection.prepareStatement(countSql);
            ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
            parameterHandler.setParameters(countStatement);
            ResultSet rs = countStatement.executeQuery();
            if (rs.next()) {
                page.setTotalNumber(rs.getInt(1));
            }
            rs.close();
            countStatement.close();

            // Modify sql limit
            String pageSql = this.generaterPageSql(sql, page);
            System.out.println("paging sql: " + pageSql);

            //Set the modified sql back
            metaObject.setValue("delegate.boundSql.sql", pageSql);

        }
        // Hand over the execution process to mybatis
        return invocation.proceed();
    }

    // Add the customized plug-in to mybatis for execution
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    // set a property
    @Override
    public void setProperties(Properties properties) {

    }

    // Generate sql with limit based on the original sql
    public String generaterPageSql(String sql, Page page) {

        StringBuffer sb = new StringBuffer();
        sb.append(sql);
        sb.append(" limit " + page.getStartIndex() + " , " + page.getTotalSelect());
        return sb.toString();
    }

}
Data
@NoArgsConstructor
public class Page {
    public Page(int currentPage,int pageSize){
        this.currentPage=currentPage;
        this.pageSize=pageSize;
    }

    private int totalNumber;// Total number of entries in the current table
    private int currentPage;// The location of the current page

    private int totalPage;	// PageCount 
    private int pageSize = 3;// Page size

    private int startIndex;	// Start of retrieval
    private int totalSelect;// Total number of retrieved

    public void setTotalNumber(int totalNumber) {
        this.totalNumber = totalNumber;
        // calculation
        this.count();
    }

    public void count() {
        int totalPageTemp = this.totalNumber / this.pageSize;
        int plus = (this.totalNumber % this.pageSize) == 0 ? 0 : 1;
        totalPageTemp = totalPageTemp + plus;
        if (totalPageTemp <= 0) {
            totalPageTemp = 1;
        }
        this.totalPage = totalPageTemp;// PageCount 

        if (this.totalPage < this.currentPage) {
            this.currentPage = this.totalPage;
        }
        if (this.currentPage < 1) {
            this.currentPage = 1;
        }
        this.startIndex = (this.currentPage - 1) * this.pageSize;// The starting position is equal to all previous page inputs multiplied by the page size
        this.totalSelect = this.pageSize;// The number of retrievals is equal to the page size
    }
}
@Data
public class PageResponse<T> {
    private int totalNumber;
    private int currentPage;
    private int totalPage;
    private int pageSize = 3;
    private T data;

}
public class PageUtil {
    private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();

    public static void setPagingParam(int offset, int limit) {
        Page page = new Page(offset, limit);
        LOCAL_PAGE.set(page);
    }

    public static void removePagingParam() {
        LOCAL_PAGE.remove();
    }

    public static Page getPaingParam() {
        return LOCAL_PAGE.get();
    }

}

Handwriting plug-in to separate reading and writing

Intercepting Executor based on spring dynamic data source and Theadlocal

@Intercepts({// mybatis execution process
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })
})
@Slf4j
public class DynamicPlugin implements Interceptor {
    private static final Map<String, String> cacheMap = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] objects = invocation.getArgs();
        MappedStatement ms = (MappedStatement) objects[0];

        String dynamicDataSource = null;

        if ((dynamicDataSource = cacheMap.get(ms.getId())) == null) {
            // Reading method
            if (ms.getSqlCommandType().equals(SqlCommandType.SELECT)) { // select * from user;    update insert
                // ! selectKey is the auto increment ID query primary key (SELECT LAST_INSERT_ID()) method, using the main database
                if (ms.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                    dynamicDataSource = "write";
                } else {
                    // Load balancing for multiple read Libraries
                    dynamicDataSource = "read";
                }
            } else {
                dynamicDataSource = "write";
            }

            log.info("method[{"+ms.getId()+"}] Used [{"+dynamicDataSource+"}] data source, SqlCommandType [{"+ms.getSqlCommandType().name()+"}]..");
            // Store the ID (method name) and data source in the map and execute it directly after the next hit
            cacheMap.put(ms.getId(), dynamicDataSource);
        }
        // Sets the data source used by the current thread
        DynamicDataSourceHolder.putDataSource(dynamicDataSource);

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
public final class DynamicDataSourceHolder {

    // Use ThreadLocal to record the data source key of the current thread
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    public static void putDataSource(String name){
        holder.set(name);
    }

    public static String getDataSource(){
        return holder.get();
    }

    /**
     * Clean up data sources
     */
    public static void clearDataSource() {
        holder.remove();
    }

}
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
      return  DynamicDataSourceHolder.getDataSource();
    }


}
@Configuration
public class DataSourceConfig {
    
    @Value("${spring.datasource.db01.jdbcUrl}")
    private String db01Url;
    @Value("${spring.datasource.db01.username}")
    private String db01Username;
    @Value("${spring.datasource.db01.password}")
    private String db01Password;
    @Value("${spring.datasource.db01.driverClassName}")
    private String db01DiverClassName;

    @Bean("dataSource01")
    public DataSource dataSource01(){
        HikariDataSource dataSource01 = new HikariDataSource();
        dataSource01.setJdbcUrl(db01Url);
        dataSource01.setDriverClassName(db01DiverClassName);
        dataSource01.setUsername(db01Username);
        dataSource01.setPassword(db01Password);
        return dataSource01;
    }

    @Value("${spring.datasource.db02.jdbcUrl}")
    private String db02Url;
    @Value("${spring.datasource.db02.username}")
    private String db02Username;
    @Value("${spring.datasource.db02.password}")
    private String db02Password;
    @Value("${spring.datasource.db02.driverClassName}")
    private String db02DiverClassName;

    @Bean("dataSource02")
    public DataSource dataSource02(){
        HikariDataSource dataSource02 = new HikariDataSource();
        dataSource02.setJdbcUrl(db02Url);
        dataSource02.setDriverClassName(db02DiverClassName);
        dataSource02.setUsername(db02Username);
        dataSource02.setPassword(db02Password);
        return dataSource02;
    }
    @Bean("multipleDataSource")
    public DataSource multipleDataSource(@Qualifier("dataSource01") DataSource dataSource01,
                                         @Qualifier("dataSource02") DataSource dataSource02) {
        Map<Object, Object> datasources = new HashMap<Object, Object>();
        datasources.put("write", dataSource01);
        datasources.put("read", dataSource02);
        DynamicDataSource multipleDataSource = new DynamicDataSource();
        multipleDataSource.setDefaultTargetDataSource(dataSource01);
        multipleDataSource.setTargetDataSources(datasources);
        return multipleDataSource;
    }

}
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
      return  DynamicDataSourceHolder.getDataSource();
    }
}
public class DynamicDataSourceTransactionManager extends DataSourceTransactionManager {
    private static final long serialVersionUID = 1L;

    public DynamicDataSourceTransactionManager(DataSource dataSource){
        super(dataSource);
    }

    /**
     * Read only transaction to read library, read-write transaction to write library
     *
     * @param transaction
     * @param definition
     */
    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {

        // set up data sources
        boolean readOnly = definition.isReadOnly();
        if (readOnly) {
            DynamicDataSourceHolder.putDataSource("read");
        } else {
            DynamicDataSourceHolder.putDataSource("write");
        }
        super.doBegin(transaction, definition);
    }

    /**
     * Clean up the data source of the local thread
     *
     * @param transaction
     */
    @Override
    protected void doCleanupAfterCompletion(Object transaction) {
        super.doCleanupAfterCompletion(transaction);
        DynamicDataSourceHolder.clearDataSource();
    }
}
Configuration
@MapperScan("com.example.dao")
@EnableTransactionManagement
public class MybatisConfig implements TransactionManagementConfigurer {

    private static String mybatisConfigPath = "mybatis-config.xml";

    @Autowired
    @Qualifier("multipleDataSource")
    private DataSource multipleDataSource;

    @Bean("sqlSessionFactoryBean")
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(multipleDataSource);
        bean.setTypeAliasesPackage("com.example.entity");
        bean.setConfigLocation(new ClassPathResource(mybatisConfigPath));
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        bean.setMapperLocations(resolver.getResources("classpath*:mapper/*.xml"));
        return bean.getObject();

    }

    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return new DynamicDataSourceTransactionManager(multipleDataSource);
    }
}

mybatis L2 cache

Mybatis enables the L1 cache by default. The L1 cache is at the sqlsession level, so it is useless in the actual scenario. Mybatis L2 cache is closed by default. The usage method is as follows

1. Add in global configuration file

 <settings>
	<setting name="cacheEnabled" value="true" />
 </settings>

2. In mapper using L2 cache Add in XML

<mapper namespace="com.study.mybatis.mapper.UserMapper">
	<!--Open this mapper of namespace L2 cache under-->
	<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
</mapper>
    <!--eviction:It represents the cache recycling policy. At present MyBatis Provide the following policies.
        (1) LRU,The least recently used and the longest unused object
        (2) FIFO,First in, first out, removes objects in the order they enter the cache
        (3) SOFT,Soft reference to remove objects based on garbage collector status and soft reference rules
        (4) WEAK,Weak references, more actively remove objects based on garbage collector status and weak reference rules. What is used here is LRU,Remove the image that has not been used for the longest time
        flushInterval:The refresh interval, in milliseconds, is configured to refresh in 100 seconds. If you do not configure it, when
        SQL The cache will be refreshed only when it is executed.
        size:The number of references, a positive integer, represents the maximum number of objects that can be stored in the cache. It should not be set too large. Setting too high will cause memory overflow.
        1024 objects are configured here
        readOnly:Read only means that the cached data can only be read and cannot be modified. The advantage of this setting is that we can read the cache quickly. The disadvantage is that we can't modify the cache. Its default value is false,We are not allowed to modify
    -->

3. Distributed applications can introduce myabtis redis related dependencies to realize redis based distributed caching

< cache type="org.mybatis.caches.redis.RedisCache" />

4. You can also customize the Cache. myabtis reserves a Cache interface for us

mybatis custom type converter

It is usually used for unified conversion of special fields, encryption of sensitive fields, etc. the usage is as follows

public class MyTypeHandler implements TypeHandler {

    //private static String KEY = "123456";

    /**
     * Set parameters through the preparedStatement object to store T-type data into the database.
     *
     * @param ps
     * @param i
     * @param parameter
     * @param jdbcType
     * @throws SQLException
     */
    @Override
    public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        try {
            String encrypt = EncryptUtil.encode(((String) parameter).getBytes());
            ps.setString(i, encrypt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // The result data can be obtained by column name or subscript, or by CallableStatement.
    @Override
    public Object getResult(ResultSet rs, String columnName) throws SQLException {
        String result = rs.getString(columnName);
        if (result != null && result != "") {
            try {
                return EncryptUtil.decode(result.getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    @Override
    public Object getResult(ResultSet rs, int columnIndex) throws SQLException {
        String result = rs.getString(columnIndex);
        if (result != null && result != "") {
            try {
                return EncryptUtil.decode(result.getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    @Override
    public Object getResult(CallableStatement cs, int columnIndex) throws SQLException {
        String result = cs.getString(columnIndex);
        if (result != null && result != "") {
            try {
                return EncryptUtil.decode(result.getBytes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
}
public class EncryptUtil {

    //base64 decoding
    public static String decode(byte[] bytes) {
        return new String(Base64.decodeBase64(bytes));
    }

    //base64 encoding
    public static String encode(byte[] bytes) {
        return new String(Base64.encodeBase64(bytes));
    }
}

Introducing type converters into mybatis configuration files

  <plugins>
        <plugin interceptor="com.example.plugin.PagePlugin" >
            <property name="type" value="mysql"/>
        </plugin>
  </plugins>

Specify the type converter in the field you want to use

  <resultMap id="resultListUser" type="com.example.entity.User" >
	<result column="password" property="password" typeHandler="com.example.typehandler.MyTypeHandler" />
  </resultMap>

  <update id="updateUser" parameterType="com.example.entity.User">
	UPDATE user userName=#{userName typeHandler="com.example.typehandler.MyTypeHandler"} WHERE id=#{id}
  </update>

Integration principle of spring and mybatis

The integration process is as follows

1. Introduce dependency

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.2.2</version>
</dependency>

2. The following configuration is introduced into the spring configuration file

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- Load data source -->
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath*:mappers/*Mapper.xml"/>
</bean>
 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- Specify the packages to scan, if there are multiple packages to use(comma,)division -->
    <property name="basePackage" value="com.test.bean"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

You can see that the core class has two mappercannerconfigurer and SqlSessionFactoryBean.

MapperScannerConfigurer

The mappercannerconfigurer class is declared as follows

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
	//Code omission
}

The declaration of BeanDefinitionRegistryPostProcessor is as follows

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;

}

The main method in this class is the postprocessbeandefinitionregistry (beandefinitionregistry) method

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

This method creates a spring - mybatis The classpathmappercanner scanner in the jar package inherits spring's ClassPathBeanDefinitionScanner. The main functions of this scanner are as follows:

1. Scan all class classes under the basePackage package

2. Second, encapsulate all class classes into spring's ScannedGenericBeanDefinition sbd object

3. Third, filter sbd objects and accept only interface classes

4. Fourth, set the properties of the sbd object, such as sqlSessionFactory and beanclass (note that the class object of MapperFactoryBean is put in, which will be used later). The specific code is as follows:

ClassPathMapperScanner
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      definition.getPropertyValues().add("addToConfig", this.addToConfig);
      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

5. Fifthly, register the filtered sbd object in the DefaultListableBeanFactory through the beandefinitionregistry register, which is the parameter in the method postprocessbeandefinitionregistry (beandefinitionregistry).

The above is the main work of instantiating mappercannerconfigurer class. To sum up, scan all mapper interface classes under the basePackage package, encapsulate the mapper interface class into a BeanDefinition object, and register it in the BeanFactory container of spring.

SqlSessionFactoryBean

Class declaration

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    //Omit code
}

In the process of creating this bean, the first method called is afterpropertieset, which is the method in the interface InitializingBean.

public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}

Many things have been done in this buildSqlSessionFactory() method. To sum up, several objects have been created, including the core class Configuration of mybatis, the thing factory class SpringManagedTransactionFactory integrated by spring and mybatis, the Environment class of mybatis, and the DefaultSqlSessionFactory class of mybatis. At the same time, the xml file of mybatis has been parsed, And encapsulate the parsing results in the Configuration class.

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    XMLConfigBuilder xmlConfigBuilder = null;
    Configuration configuration;
    configuration = new Configuration();
        configuration.setVariables(this.configurationProperties);
    //Omit code
    if (this.transactionFactory == null) {
        this.transactionFactory = new SpringManagedTransactionFactory();
    }
 
    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);
    
    if (!ObjectUtils.isEmpty(this.mapperLocations)) {
        Resource[] arr$ = this.mapperLocations;
        len$ = arr$.length;
        for(i$ = 0; i$ < len$; ++i$) {
            Resource mapperLocation = arr$[i$];
            if (mapperLocation != null) {
                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } catch (Exception var20) {
                    throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                } finally {
                    ErrorContext.instance().reset();
                }
            }
        }
    }
 
    return this.sqlSessionFactoryBuilder.build(configuration);
}

sqlSessionFactoryBuilder. What build (Configuration) does is very simple. It directly creates DefaultSqlSessionFactory, which also holds a reference to the Configuration object.

SqlSessionFactoryBean implements the FactoryBean interface, so the getObject method will be called during initialization.

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
    return this.sqlSessionFactory;
}

In terms of content, it directly returns an instance of sqlsessionfactory class. If it is empty, it calls afterpropertieset to initialize sqlsessionfactory object.

Instantiation process

Take the following code as an example

@Service
public class UserServiceImpl implements IUserService {
	@Resource
	private UserMapper UserMapper;
 
	public UserServiceImpl(){
	}
}

During the initialization process of spring, the UserServiceImpl class will be created. After creation, the attribute will be assigned. The mybatis interface of UserMapper is an attribute of UserServiceImpl. First, obtain its BeanDefinition from spring's BeanFactory according to the name of the mapper, and then obtain BeanClass from BeanDefinition, The BeanClass corresponding to UserMapper is MapperFactoryBean, which is mentioned in the above analysis.

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    //intentionally empty 
  }
  
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  @Override
  public boolean isSingleton() {
    return true;
  }

  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

The next step is to create the MapperFactoryBean object. After creation, you need to assign a value to the attribute, which is determined when creating the BeanDefinition object corresponding to UserMapper. You can know by reviewing the above part of creating the MapperScannerConfigurer object. One of the properties is SqlSessionFactoryBean, which triggers the creation process of SqlSessionFactoryBean.

After setting the properties of MapperFactoryBean object, call its getObject() method. The subsequent logic is the process of creating proxy object by mybatis mentioned above.

Related source code

https://gitee.com/junlin1991/mybatis-learn.git

Added by amjohnno on Wed, 19 Jan 2022 00:28:41 +0200