mybatis中的主要設計模式應用

mybatis中包含很多中設計模式,主要包括:建造者模式、工廠模式、單例模式、模板方法模式、組合模式、代理模式、適配器模式、裝飾器模式、迭代器模式。

Builder模式

對象的構建過程過於複雜,可以使用建造者模式,將對象的創建過程和表示過程相分離。

  • MapperAnnotationBuilder
    Mapper的註解構建者。當在配置文件中直接定義下面的語句時,就需要通過這個構建器進行註解解析,生成最終的結構化映射對象。
    <mapper class="com.im.sky.mybatis.GenericDao"/>
    
    關鍵方法如下:
     public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
      loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
      }
    
  • XMLStatementBuilder
public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  String databaseId = context.getStringAttribute("databaseId");

  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }

  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

  String nodeName = context.getNode().getNodeName();
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);

  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  String resultSets = context.getStringAttribute("resultSets");
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
  • XMLMapperBuilder

用於解析單個Mapper文件,關鍵方法如下:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}
  • XMLConfigBuilder
    用於解析mybatis的配置文件,關鍵代碼如下:
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

上面幾個構建者的調用順序主要是,先通過XMLConfigBuilder構建器解析mybatis的配置文件,然後解析到mappers屬性時,會調用XMLMapperBuilder或者MapperAnnotationBuilder進行基於xml文件的配置解析或者基於註解的配置解析。然後解析到單條配置信息時,會調用XMLStatementBuilder進行配置的解析。

工廠模式

三個主要的工廠類:MapperProxyFactory、ObjectFactory、SqlSessionFactory。

  • MapperProxyFactory
@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);
  }
  • ObjectFactory

普通對象的創建類,這是一個接口,看看其提供的默認實現DefaultObjectFactory

// 內部就是通過發射實例化對象的
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
    Class<?> classToCreate = resolveInterface(type);
    // we know types are assignable
    return (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
  }
  • SqlSessionFactory

提供了openSession很多的重載方法,用於獲取一個SqlSession對象,可以認爲一個SqlSession關聯一個數據庫連接,進行數據庫操作是通過Executor來進行的,內部關聯一個Transaction,控制Connection的生命週期。

@Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

單例模式

主要的類包括:ErrorContext(其實也不是單例,只是將實例化交於內部方法實現了)、LogFactory。

  • ErrorContext
    跟蹤調用的上下文
// 私有
private ErrorContext() {
}

//內部調用生成
public static ErrorContext instance() {
  ErrorContext context = LOCAL.get();
  if (context == null) {
    context = new ErrorContext();
    LOCAL.set(context);
  }
  return context;
}

public ErrorContext store() {
  ErrorContext newContext = new ErrorContext();
  newContext.stored = this;
  LOCAL.set(newContext);
  return LOCAL.get();
}
  • LoggerFactory
private LogFactory() {
  // disable construction
}

public static Log getLog(Class<?> aClass) {
  return getLog(aClass.getName());
}

public static Log getLog(String logger) {
  try {
    return logConstructor.newInstance(logger);
  } catch (Throwable t) {
    throw new LogException("Error creating logger for logger " + logger + ".  Cause: " + t, t);
  }
}

代理模式

主要的類包括:MapperProxy、ConnectionLogger、CglibProxyFactory、JavassistProxyFactory等。

上面幾個類使用JDK、CGLIB、Javassist代理。具體代碼可以自己研究。

// MapperProxy的部分代碼,實現了InvocationHandler接口。
@Override
  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);
  }

組合模式

主要的類包括:SqlNode和其各個子類等。

// 接口
public interface SqlNode {
  boolean apply(DynamicContext context);
}

// SqlNode的一個子類
public class MixedSqlNode implements SqlNode {
  private final List<SqlNode> contents;

  public MixedSqlNode(List<SqlNode> contents) {
    this.contents = contents;
  }

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }
}

模板方法模式

例如BaseTypeHandler類,提供了一個基本的實現和模板,部分方法需要在子類中實現。

@Override
  public T getResult(ResultSet rs, String columnName) throws SQLException {
    try {
      // getNullableResult方法需要在子類中實現。
      return getNullableResult(rs, columnName);
    } catch (Exception e) {
      throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);
    }
  }

適配器模式

在mybatis中,對Log的實現是適配器模式的典型應用,將不同的日誌實現基本通過適配器模式全部集成了過來。

mybatis自己定義了一個Log接口

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);
}

其中一個對Slf4j2的適配實現如下:

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);
  }

}

裝飾器模式

cache.decorators包下的Cache接口的實現類是典型的裝飾器的實現方式,通過組合的方式,給Cache進行一定的細節處理,比如配置出入隊策略、加鎖、同步等小功能。

例如BlockingCache:

// 部分代碼如下,藉助ReentrantLock實現加鎖功能
public class BlockingCache implements Cache {

  private long timeout;
  private final Cache delegate;
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }
}

迭代器模式

PropertyTokenizer這個類是迭代器模式的一個實現。

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.");
  }
}

總結:

mybatis中使用的設計模式很多,不管是使用哪個設計模式,我們都需要思考使用這種設計模式有什麼好處,如果不使用這種設計模式應該怎麼寫,會不會導致代碼很臃腫。

1、什麼時候使用?

  • 建造者模式
    如果對象的構建過程很複雜,並且易變,不如採用建造者模式。lombok的jar包中就提供了一個自動生成建造者對象的註解。

  • 工廠模式
    工廠模式易於管理對象實例,甚至有時可以將工廠想象爲一個容器。

  • 單例模式
    一個實例在多線程中不會遇到線程安全問題,可以考慮使用單例,像工具類等都是單例,spring的bean默認也是單例。

  • 模板方法模式
    如果一個行爲可以分爲多個步驟,這些步驟的順序是固定的,只是有的步驟可以定製化,則可以採用模板方法模式。

  • 組合模式
    如果感覺有必要將一個對象和一個對象的組合看做是實現相同的接口,可以考慮採用這種模式,組合模式對象感覺有點像樹結構,將葉子節點和非葉子節點同等看待。

  • 代理模式
    主要是對對象進行控制,代理層稍微做了比較多的東西,目前除了框架中看到外,平常業務中基本用不到代理模式。

  • 適配器模式
    這個模式使用的多,功能已經有了,但是向外暴露的接口和已實現功能的接口不一致,這時適配器就派上用場了。

  • 裝飾器模式
    給對象添加些小功能,和代理模式結構很類似。

  • 迭代器模式
    有必要遍歷對象時,可以考慮迭代器模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章