Mybatis Framework Source Code Notes (10) Design Patterns in Mybatis

1 Design patterns applied in the Mybatis framework

  • 1. Singleton mode: such as LogFactory, ErrorContext

  • 2. Factory mode: such as SqlSessionFactory, ObjectFactory, MapperProxyFactory

  • 3. Builder mode: such as SqlSessionFactoryBuilder, XMLConfigBuilder, XMLMapperBuilder, XMLStatementBuilder, CacheBuilder

  • 4. Proxy mode: the core of mybatis implementation, such as MapperProxy, ConnectionLogger, dynamic proxy of jdk, etc.

  • 5. Combination mode: For example, SqlNode and each subclass ChooseSqlNode, etc. [Generate tree structure data]

  • 6. Template method pattern: such as BaseExecutor and SimpleExecutor, as well as BaseTypeHandler and its subclasses

  • 7. Adapter mode: For example, Log’s Mybatis interface and its adaptation implementation of various log frameworks such as jdbc, log4j, etc.

  • 8. Decorator pattern: For example, the implementation of each decorator in the cache.decorators sub-package in the Cache package

  • 9. Iterator mode: For example, iterator mode PropertyTokenizer

2 Core code description of each design pattern in the Mybatis framework

2.1 Singleton pattern

Mybatis's exception information splicing classErrorContext uses the singleton mode during design.

Insert image description here

2.2 Factory mode

In the process of creating SqlSession, the factory mode is used in the creation and other processes of the dynamic proxy pair of the Mapper layer interface.

Insert image description here

2.3 Builder mode

In the process of SqlSession creation, global configuration file parsing, mapper mapping file parsing and other processes, a large number of builder patterns are used to complete the creation of complex objects.

SqlSessionFactoryBuilder
Insert image description here
MappedStatement

package org.apache.ibatis.mapping;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;

/**
 * @author Clinton Begin
 */
public final class MappedStatement {
    
    

  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

  MappedStatement() {
    
    
    // constructor disabled
  }

  public static class Builder {
    
    
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
    
    
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.resultSetType = ResultSetType.DEFAULT;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<>()).build();
      mappedStatement.resultMaps = new ArrayList<>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
    
    
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }

    public Builder resource(String resource) {
    
    
      mappedStatement.resource = resource;
      return this;
    }

    public String id() {
    
    
      return mappedStatement.id;
    }

    public Builder parameterMap(ParameterMap parameterMap) {
    
    
      mappedStatement.parameterMap = parameterMap;
      return this;
    }

    public Builder resultMaps(List<ResultMap> resultMaps) {
    
    
      mappedStatement.resultMaps = resultMaps;
      for (ResultMap resultMap : resultMaps) {
    
    
        mappedStatement.hasNestedResultMaps = mappedStatement.hasNestedResultMaps || resultMap.hasNestedResultMaps();
      }
      return this;
    }

    public Builder fetchSize(Integer fetchSize) {
    
    
      mappedStatement.fetchSize = fetchSize;
      return this;
    }

    public Builder timeout(Integer timeout) {
    
    
      mappedStatement.timeout = timeout;
      return this;
    }

    public Builder statementType(StatementType statementType) {
    
    
      mappedStatement.statementType = statementType;
      return this;
    }

    public Builder resultSetType(ResultSetType resultSetType) {
    
    
      mappedStatement.resultSetType = resultSetType == null ? ResultSetType.DEFAULT : resultSetType;
      return this;
    }

    public Builder cache(Cache cache) {
    
    
      mappedStatement.cache = cache;
      return this;
    }

    public Builder flushCacheRequired(boolean flushCacheRequired) {
    
    
      mappedStatement.flushCacheRequired = flushCacheRequired;
      return this;
    }

    public Builder useCache(boolean useCache) {
    
    
      mappedStatement.useCache = useCache;
      return this;
    }

    public Builder resultOrdered(boolean resultOrdered) {
    
    
      mappedStatement.resultOrdered = resultOrdered;
      return this;
    }

    public Builder keyGenerator(KeyGenerator keyGenerator) {
    
    
      mappedStatement.keyGenerator = keyGenerator;
      return this;
    }

    public Builder keyProperty(String keyProperty) {
    
    
      mappedStatement.keyProperties = delimitedStringToArray(keyProperty);
      return this;
    }

    public Builder keyColumn(String keyColumn) {
    
    
      mappedStatement.keyColumns = delimitedStringToArray(keyColumn);
      return this;
    }

    public Builder databaseId(String databaseId) {
    
    
      mappedStatement.databaseId = databaseId;
      return this;
    }

    public Builder lang(LanguageDriver driver) {
    
    
      mappedStatement.lang = driver;
      return this;
    }

    public Builder resultSets(String resultSet) {
    
    
      mappedStatement.resultSets = delimitedStringToArray(resultSet);
      return this;
    }

    /**
     * Resul sets.
     *
     * @param resultSet
     *          the result set
     * @return the builder
     * @deprecated Use {@link #resultSets}
     */
    @Deprecated
    public Builder resulSets(String resultSet) {
    
    
      mappedStatement.resultSets = delimitedStringToArray(resultSet);
      return this;
    }

    public MappedStatement build() {
    
    
      assert mappedStatement.configuration != null;
      assert mappedStatement.id != null;
      assert mappedStatement.sqlSource != null;
      assert mappedStatement.lang != null;
      mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
      return mappedStatement;
    }
  }

  public KeyGenerator getKeyGenerator() {
    
    
    return keyGenerator;
  }

  public SqlCommandType getSqlCommandType() {
    
    
    return sqlCommandType;
  }

  public String getResource() {
    
    
    return resource;
  }

  public Configuration getConfiguration() {
    
    
    return configuration;
  }

  public String getId() {
    
    
    return id;
  }

  public boolean hasNestedResultMaps() {
    
    
    return hasNestedResultMaps;
  }

  public Integer getFetchSize() {
    
    
    return fetchSize;
  }

  public Integer getTimeout() {
    
    
    return timeout;
  }

  public StatementType getStatementType() {
    
    
    return statementType;
  }

  public ResultSetType getResultSetType() {
    
    
    return resultSetType;
  }

  public SqlSource getSqlSource() {
    
    
    return sqlSource;
  }

  public ParameterMap getParameterMap() {
    
    
    return parameterMap;
  }

  public List<ResultMap> getResultMaps() {
    
    
    return resultMaps;
  }

  public Cache getCache() {
    
    
    return cache;
  }

  public boolean isFlushCacheRequired() {
    
    
    return flushCacheRequired;
  }

  public boolean isUseCache() {
    
    
    return useCache;
  }

  public boolean isResultOrdered() {
    
    
    return resultOrdered;
  }

  public String getDatabaseId() {
    
    
    return databaseId;
  }

  public String[] getKeyProperties() {
    
    
    return keyProperties;
  }

  public String[] getKeyColumns() {
    
    
    return keyColumns;
  }

  public Log getStatementLog() {
    
    
    return statementLog;
  }

  public LanguageDriver getLang() {
    
    
    return lang;
  }

  public String[] getResultSets() {
    
    
    return resultSets;
  }

  /**
   * Gets the resul sets.
   *
   * @return the resul sets
   * @deprecated Use {@link #getResultSets()}
   */
  @Deprecated
  public String[] getResulSets() {
    
    
    return resultSets;
  }

  public BoundSql getBoundSql(Object parameterObject) {
    
    
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
    
    
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
    
    
      String rmId = pm.getResultMapId();
      if (rmId != null) {
    
    
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
    
    
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

  private static String[] delimitedStringToArray(String in) {
    
    
    if (in == null || in.trim().length() == 0) {
    
    
      return null;
    } else {
    
    
      return in.split(",");
    }
  }

}

These classes are also used under the org.apache.ibatis.mapping package建造者模式. If you are interested, you can read the source code yourself
Insert image description here

2.4 Agent mode

JDK dynamic proxy used by Mybatis underlying package.
Generally speaking, there are three steps to define a JDK dynamic proxy, as follows

  • Define proxy interface
  • Define proxy interface implementation class
  • Define dynamic proxy call handler

Let's go to the source code to see how the Mybatis framework implements dynamic proxy.

MapperProxyFactory, as the name suggests, it must be used to create dynamic proxy instance objects.

/**
 * @author Lasse Voss
 *
 * @description: 创建Mapper层接口代理类的工厂类
 */
public class MapperProxyFactory<T> {
    
    

  /** mapper接口类的全限定路径名 */
  private final Class<T> mapperInterface;
  /** mapper接口类的中的抽象方法名称和对应的动态代理类中的MapperMethodInvoker对象的映射关系集合 */
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

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

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

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    
    
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    
    
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

The core code is just this line:

Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);

As can be seen from the above, the implementation of dynamic proxy is achieved with the help of the Proxy class provided by JDK.

Here we see that when creating a dynamic proxy, an anonymous inner class of the InvocationHandler interface or an implementation class of the InvocationHandler interface should be passed in. The management directly passes a MapperProxy object. It can be seen that the MapperProxy class must implement the InvocationHandler interface. Go to the code Verify it inside:

/*
 *    Copyright 2009-2023 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.util.MapUtil;

/**
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {
    
    

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  // 代理的mapper层接口中的哪个方法,例如: public abstract int com.kkarma.mapper.StudentMapper.insertStudent(com.kkarma.pojo.Student)
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  // 代理的mapper层接口是哪个, 例如: interface com.kkarma.mapper.StudentMapper
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    
    
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    
    
    Method privateLookupIn;
    try {
    
    
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
    
    
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
    
    
      // JDK 1.8
      try {
    
    
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
    
    
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
    
    
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      /**
       *  这里给大家解释一下这个invoke判断里面加这个判断的原因:
       *      大家都知道Object对象默认已经实现了很多方法, 我们的Mapper接口在进行定义的时候, 可能定义了静态方法、 默认方法以及抽象方法
       *      因此在创建了动态代理对象的时候, 这个动态代理类肯定也包含了很多的方法, 从Object类继承的方法, 从接口继承的默认方法,
       *      以及从接口继承抽象方法需要实现取执行SQL语句的方法
       *
       *      这个if分值判断的只要目的在将无需走SQL执行流程的方法如(toString/equals/hashCode)等先过滤掉
       *      然后再抽象方法及默认方法中通过一个接口MapperMethodInvoker再进行一次判断,找到所有需要执行SQL的方法通过PlainMethodInvoker的invoke
       *      方法取执行SQL语句获取结果,能够加快获取 mapperMethod 的效率
       */
      if (Object.class.equals(method.getDeclaringClass())) {
    
    
        return method.invoke(this, args);
      } else {
    
    
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
    
    
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    
    
    try {
    
    
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
    
    
        if (m.isDefault()) {
    
    
          try {
    
    
            if (privateLookupInMethod == null) {
    
    
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
    
    
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
    
    
            throw new RuntimeException(e);
          }
        } else {
    
    
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
    
    
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    
    
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    
    
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    
    
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    
    
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
    
    
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    
    
      return mapperMethod.execute(sqlSession, args);
    }
  }

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    
    
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
    
    
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    
    
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }
}

2.5 Iterator pattern

The iterator pattern mainly includes the following roles:

  • Abstract collection (Aggregate) role: used to store and manage element objects, define the functions of storing, adding, and deleting collection elements, and declares a createIterator() method for creating iterator objects.
  • Concrete Aggregate role: implements an abstract collection class and returns an instance of a concrete iterator.
  • Abstract iterator (Iterator) role: defines the interface for accessing and traversing aggregate elements, usually including hasNext(), next() and other methods.
    • hasNext(): This method is used to determine whether there is a next element in the collection
    • next(): This method is used to move the cursor back one element
    • currentItem(): This method is used to return the element pointed by the current cursor
  • Concrete iterator (Concretelterator) role: implements the methods defined in the abstract iterator interface, completes the traversal of the collection object, and records the current position of the traversal.
    Insert image description here

Let’s take a look at the application of iterator pattern in Mybatis.

/**
 * @author Clinton Begin
 * 属性分词器
 *
 * @description: 使用了迭代器模式
 */
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
    
    
  private String name;
  private final String indexedName;
  private String index;
  private final String children;

  public PropertyTokenizer(String fullname) {
    
    
    int delim = fullname.indexOf('.');
    if (delim > -1) {
    
    
      name = fullname.substring(0, delim);
      children = fullname.substring(delim + 1);
    } else {
    
    
      name = fullname;
      children = null;
    }
    indexedName = name;
    delim = name.indexOf('[');
    if (delim > -1) {
    
    
      index = name.substring(delim + 1, name.length() - 1);
      name = name.substring(0, delim);
    }
  }

  public String getName() {
    
    
    return name;
  }

  public String getIndex() {
    
    
    return index;
  }

  public String getIndexedName() {
    
    
    return indexedName;
  }

  public String getChildren() {
    
    
    return children;
  }

  @Override
  public boolean hasNext() {
    
    
    return children != null;
  }

  @Override
  public PropertyTokenizer next() {
    
    
    return new PropertyTokenizer(children);
  }

  @Override
  public void remove() {
    
    
    throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
  }
}

2.6 Adapter mode

Convert a system's interface into another form, so that the interface that cannot be called directly becomes callable.

适配器模式涉及3个角色:
1.源(Source):需要被适配的对象或类型,相当于插头。
2.适配器(Adaptor):连接目标和源的中间对象,相当于插头转换器。
3.目标(Target):期待得到的目标,相当于插座。
适配器模式包括3种形式:类适配器模式、对象适配器模式、接口适配器模式(又称缺省适配器模式)

The adapter mode is used in the log module of Mybatis. When using the log module internally, Mybatis uses its internal interface, but the external interfaces of commonly used log frameworks are different. In order to reuse and integrate these third-party log components, Mybatis provides a variety of Adapters in its log module to adapt the external interfaces of these third-party log components into , so that Myabtis can call third-party logs through the Log interface. Regarding the integration examples and principle analysis of the log framework, I have written related articles before [ Mybatis Framework Source Code Notes (6) Principles of Integrated Log Framework in Mybatis Analysis] If you are interested, you can check it out by yourself. Source: Log interfaceorg.apache.ibatis.logging.Log
org.apache.ibatis.logging.Log

Insert image description here

package org.apache.ibatis.logging;

/**
 * @author Clinton Begin
 */
public interface Log {
    
    

  boolean isDebugEnabled();

  boolean isTraceEnabled();

  void error(String s, Throwable e);

  void error(String s);

  void debug(String s);

  void trace(String s);

  void warn(String s);

}

Adapter: Slf4jImpl

public class Slf4jImpl implements Log {
    
    

  private Log log;

  public Slf4jImpl(String clazz) {
    
    
    Logger logger = LoggerFactory.getLogger(clazz);

    if (logger instanceof LocationAwareLogger) {
    
    
      try {
    
    
        // check for slf4j >= 1.6 method signature
        logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
        log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
        return;
      } catch (SecurityException | NoSuchMethodException e) {
    
    
        // fail-back to Slf4jLoggerImpl
      }
    }

    // Logger is not LocationAwareLogger or slf4j version < 1.6
    log = new Slf4jLoggerImpl(logger);
  }

  @Override
  public boolean isDebugEnabled() {
    
    
    return log.isDebugEnabled();
  }

  @Override
  public boolean isTraceEnabled() {
    
    
    return log.isTraceEnabled();
  }

  @Override
  public void error(String s, Throwable e) {
    
    
    log.error(s, e);
  }

  @Override
  public void error(String s) {
    
    
    log.error(s);
  }

  @Override
  public void debug(String s) {
    
    
    log.debug(s);
  }

  @Override
  public void trace(String s) {
    
    
    log.trace(s);
  }

  @Override
  public void warn(String s) {
    
    
    log.warn(s);
  }
}

Target: Logger interface


package org.slf4j;

public interface Logger {
    
    


    final public String ROOT_LOGGER_NAME = "ROOT";

    public String getName();

    public boolean isTraceEnabled();

    public void trace(String msg);

    public void trace(String format, Object arg);
    
    public void trace(String format, Object arg1, Object arg2);

    public void trace(String format, Object... arguments);

    public void trace(String msg, Throwable t);

    public boolean isTraceEnabled(Marker marker);

    public void trace(Marker marker, String msg);

    public void trace(Marker marker, String format, Object arg);

    public void trace(Marker marker, String format, Object arg1, Object arg2);

    public void trace(Marker marker, String format, Object... argArray);

    public void trace(Marker marker, String msg, Throwable t);

    public boolean isDebugEnabled();

    public void debug(String msg);

    public void debug(String format, Object arg);

    public void debug(String format, Object arg1, Object arg2);

    public void debug(String format, Object... arguments);

    public void debug(String msg, Throwable t);

    public boolean isDebugEnabled(Marker marker);

    public void debug(Marker marker, String msg);

    public void debug(Marker marker, String format, Object arg);

    public void debug(Marker marker, String format, Object arg1, Object arg2);
    
    public void debug(Marker marker, String format, Object... arguments);
    
    public void debug(Marker marker, String msg, Throwable t);

    public boolean isInfoEnabled();

    public void info(String msg);

    public void info(String format, Object arg);

    public void info(String format, Object arg1, Object arg2);
    
    public void info(String format, Object... arguments);

    public void info(String msg, Throwable t);
    
    public boolean isInfoEnabled(Marker marker);

    public void info(Marker marker, String msg);

    public void info(Marker marker, String format, Object arg);

    public void info(Marker marker, String format, Object arg1, Object arg2);

    public void info(Marker marker, String format, Object... arguments);

    public void info(Marker marker, String msg, Throwable t);
    
    public boolean isWarnEnabled();

    public void warn(String msg);

    public void warn(String format, Object arg);

    public void warn(String format, Object... arguments);
    
    public void warn(String format, Object arg1, Object arg2);

    public void warn(String msg, Throwable t);
    
    public boolean isWarnEnabled(Marker marker);
    
    public void warn(Marker marker, String msg);

    public void warn(Marker marker, String format, Object arg);
    
    public void warn(Marker marker, String format, Object arg1, Object arg2);

    public void warn(Marker marker, String format, Object... arguments);

    public void warn(Marker marker, String msg, Throwable t);
    
    public boolean isErrorEnabled();

    public void error(String msg);

    public void error(String format, Object arg);

    public void error(String format, Object arg1, Object arg2);
    
    public void error(String format, Object... arguments);

    public void error(String msg, Throwable t);

    public boolean isErrorEnabled(Marker marker);

    public void error(Marker marker, String msg);

    public void error(Marker marker, String format, Object arg);

    public void error(Marker marker, String format, Object arg1, Object arg2);

    public void error(Marker marker, String format, Object... arguments);

    public void error(Marker marker, String msg, Throwable t);
}

Target Logger implementation class: Slf4jLoggerImpl

class Slf4jLoggerImpl implements Log {
    
    

  private final Logger log;

  public Slf4jLoggerImpl(Logger logger) {
    
    
    log = logger;
  }

  @Override
  public boolean isDebugEnabled() {
    
    
    return log.isDebugEnabled();
  }

  @Override
  public boolean isTraceEnabled() {
    
    
    return log.isTraceEnabled();
  }

  @Override
  public void error(String s, Throwable e) {
    
    
    log.error(s, e);
  }

  @Override
  public void error(String s) {
    
    
    log.error(s);
  }

  @Override
  public void debug(String s) {
    
    
    log.debug(s);
  }

  @Override
  public void trace(String s) {
    
    
    log.trace(s);
  }

  @Override
  public void warn(String s) {
    
    
    log.warn(s);
  }

}

2.7 Decorator Pattern

I wrote this beforeMybatis framework source code notes (5) Analysis of the principles of the Mybatis framework caching mechanismIt has been discussed in detail and will not be carried out here. To go into details, Mybatis uses the decorator pattern when implementing the cache architecture, and achieves powerful cache functions through packaging and combination.

Cache interface


public interface Cache {
    
    

  String getId();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  int getSize();

  default ReadWriteLock getReadWriteLock() {
    
    
    return null;
  }
}

Insert image description here
Insert image description here

2.8 Combination mode

The combination mode actually organizes a set of objects (folders and files) into a tree structure to represent a 'part-whole' hierarchical structure (nested structure of directories and subdirectories). The combination mode allows the client to Unify the processing logic (recursive traversal) of single objects (files) and combined objects (folders).
Insert image description here

/**
 * SQL Node 接口,每个 XML Node 会解析成对应的 SQL Node 对象
 * @author Clinton Begin
 */
public interface SqlNode {
    
    

    /**
     * 应用当前 SQL Node 节点
     *
     * @param context 上下文
     * @return 当前 SQL Node 节点是否应用成功。
     */
    boolean apply(DynamicContext context);
}

Insert image description here

Let’s take a look at its implementation classes. Just look for any implementation class. They are basically the same.
Insert image description here

public class TrimSqlNode implements SqlNode {
    
    

  private final SqlNode contents;
  private final String prefix;
  private final String suffix;
  private final List<String> prefixesToOverride;
  private final List<String> suffixesToOverride;
  private final Configuration configuration;

  public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
    
    
    this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
  }

  protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
    
    
    this.contents = contents;
    this.prefix = prefix;
    this.prefixesToOverride = prefixesToOverride;
    this.suffix = suffix;
    this.suffixesToOverride = suffixesToOverride;
    this.configuration = configuration;
  }

  @Override
  public boolean apply(DynamicContext context) {
    
    
    FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
    boolean result = contents.apply(filteredDynamicContext);
    filteredDynamicContext.applyAll();
    return result;
  }

  private static List<String> parseOverrides(String overrides) {
    
    
    if (overrides != null) {
    
    
      final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
      final List<String> list = new ArrayList<>(parser.countTokens());
      while (parser.hasMoreTokens()) {
    
    
        list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
      }
      return list;
    }
    return Collections.emptyList();
  }

  private class FilteredDynamicContext extends DynamicContext {
    
    
    private DynamicContext delegate;
    private boolean prefixApplied;
    private boolean suffixApplied;
    private StringBuilder sqlBuffer;

    public FilteredDynamicContext(DynamicContext delegate) {
    
    
      super(configuration, null);
      this.delegate = delegate;
      this.prefixApplied = false;
      this.suffixApplied = false;
      this.sqlBuffer = new StringBuilder();
    }

    public void applyAll() {
    
    
      sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
      String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
      if (trimmedUppercaseSql.length() > 0) {
    
    
        applyPrefix(sqlBuffer, trimmedUppercaseSql);
        applySuffix(sqlBuffer, trimmedUppercaseSql);
      }
      delegate.appendSql(sqlBuffer.toString());
    }

    @Override
    public Map<String, Object> getBindings() {
    
    
      return delegate.getBindings();
    }

    @Override
    public void bind(String name, Object value) {
    
    
      delegate.bind(name, value);
    }

    @Override
    public int getUniqueNumber() {
    
    
      return delegate.getUniqueNumber();
    }

    @Override
    public void appendSql(String sql) {
    
    
      sqlBuffer.append(sql);
    }

    @Override
    public String getSql() {
    
    
      return delegate.getSql();
    }

    private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
    
    
      if (!prefixApplied) {
    
    
        prefixApplied = true;
        if (prefixesToOverride != null) {
    
    
          for (String toRemove : prefixesToOverride) {
    
    
            if (trimmedUppercaseSql.startsWith(toRemove)) {
    
    
              sql.delete(0, toRemove.trim().length());
              break;
            }
          }
        }
        if (prefix != null) {
    
    
          sql.insert(0, " ");
          sql.insert(0, prefix);
        }
      }
    }

    private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
    
    
      if (!suffixApplied) {
    
    
        suffixApplied = true;
        if (suffixesToOverride != null) {
    
    
          for (String toRemove : suffixesToOverride) {
    
    
            if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
    
    
              int start = sql.length() - toRemove.trim().length();
              int end = sql.length();
              sql.delete(start, end);
              break;
            }
          }
        }
        if (suffix != null) {
    
    
          sql.append(" ");
          sql.append(suffix);
        }
      }
    }
  }
}

2.9 Template Method Pattern

The template class defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Allows subclasses to redefine certain specific steps of an algorithm without changing the structure of the algorithm.
Let’s take a look at the application of the last method pattern in the Mybatis framework.
Here we use the template method in the BaseExecutor class to illustrate. The BaseExecutor method defines the SQL The query and modification template methods are called in other current classes, but are not implemented in BaseExecutor.
Insert image description here
Insert image description here
These methods are all implemented through subclasses.
Insert image description here
Implement the template method in the subclass

Insert image description hereThese subclasses are also related to our SQL statement types. It may be a simple query, it may be a batch processing operation, it may be a reusable operation. Here we can use the template method combined with the strategy pattern to implement different processing logic corresponding to different business request types to achieve application expansion. Design patterns require constant coding and memorization in actual practice to consolidate and deepen understanding. You cannot design for the sake of design, the appropriate one is the best. Only by practicing more and summarizing can you gradually master it.

Guess you like

Origin blog.csdn.net/qq_41865652/article/details/129954174