Mybatis design pattern

  1. Builder mode, such as SqlSessionFactoryBuilder, XMLConfigBuilder, XMLMapperBuilder, XMLStatementBuilder and CacheBuilder;
  2. Factory mode, such as SqlSessionFactory, ObjectFactory, MapperProxyFactory;
  3. Singleton mode, such as ErrorContext and LogFactory;
  4. Proxy mode, the core of Mybatis implementation, such as MapperProxy and ConnectionLogger, using jdk dynamic proxy; In addition, the executor.loader package uses cglib or javassist to achieve the effect of delayed loading;
  5. Combination mode, such as SqlNode and each subclass ChooseSqlNode;
  6. Template method patterns, such as BaseExecutor and simpleexector, BaseTypeHandler and all subclasses, such as IntegerTypeHandler;
  7. Adapter mode, such as the Mybatis interface of Log and its adaptation to various Log frameworks such as jdbc and log4j;
  8. Decorator mode, such as the implementation of each decorator in the cache.decorators sub package in the Cache package;
  9. Iterator mode, such as iterator mode PropertyTokenizer;

Next, interpret the patterns one by one, first introduce the knowledge of the pattern itself, and then explain how the pattern is applied in Mybatis.

1. Builder mode

Builder mode It is defined as "separating the construction of a complex object from its representation, so that different representations can be created in the same construction process". It belongs to the creation class mode. Generally speaking, if the construction of an object is complex and beyond the scope of the constructor, factory mode and Builder mode can be used, Compared with the factory pattern, which will produce a complete product, Builder is applied to the construction of more complex objects, or even only a part of the product.

image

During the initialization of Mybatis environment, SqlSessionFactoryBuilder will call XMLConfigBuilder to read all MybatisMapConfig.xml and all * Mapper.xml files, build the core object Configuration object running Mybatis, and then use the Configuration object as a parameter to build a SqlSessionFactory object.

When building the Configuration object, XMLConfigBuilder will also call XMLMapperBuilder to read the * Mapper file, and XMLMapperBuilder will use XMLStatementBuilder to read and build all SQL statements.

In this process, there is a similar feature, that is, these builders will read files or configurations, and then do a lot of steps such as XPath parser parsing, configuration or syntax parsing, reflecting generated objects, storing results in cache, etc. so much work can not be included in a constructor, so a lot of Builder modes are used to solve it.

For the specific classes of builder, most methods start with build *, such as SqlSessionFactoryBuilder, which includes the following methods:

image

That is, the factory object SqlSessionFactory is built according to different input parameters.

2. Factory mode

stay Mybatis For example, SqlSessionFactory uses factory mode, which is a simple factory mode without so complex logic.

Simple factory pattern: also known as Static Factory method

Method) mode, which belongs to class creation mode. In the simple factory mode, different instances can be returned according to different parameters. The simple factory pattern specifically defines a class to be responsible for creating instances of other classes. The created instances usually have a common parent class.

image.png

SqlSession can be regarded as the core interface of Mybatis. Through this interface, you can execute SQL statements, obtain Mappers and manage transactions. A Connection object similar to MySQL.

image

It can be seen that the openSession method of the Factory is overloaded with many, and supports the input of parameters such as autoCommit, Executor and Transaction to build the core SqlSession object.

In the default factory implementation of DefaultSqlSessionFactory, there is a method to see how the factory produces a product:

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
   boolean autoCommit) {
  Transaction tx = null;
  try {
   final Environment environment = configuration.getEnvironment();
   final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
   tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
   final Executor executor = configuration.newExecutor(tx, execType);
   return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
   closeTransaction(tx); // may have fetched a connection so lets call
         // close()
   throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
 }

This is an underlying method called by openSession. This method first reads the corresponding environment configuration from configuration, then initializes TransactionFactory to obtain a Transaction object, then obtains an Executor object through Transaction, and finally constructs SqlSession through configuration, Executor and whether autoCommit.

You can also see the clue here. The execution of SqlSession is actually entrusted to the corresponding Executor.

For LogFactory, its implementation code:

public final class LogFactory {
 private static Constructor<? extends Log> logConstructor;
 private LogFactory() {
  // disable construction
 }
 public static Log getLog(Class<?> aClass) {
  return getLog(aClass.getName());
 }

A special thing here is that the type of Log variable is constructor <? extends

Log >, that is, the factory produces not only a product, but a series of products with log public interface, such as Log4jImpl, Slf4jImpl and many other specific logs.

3. Singleton mode

[Singleton mode

Pattern)](https://links.jianshu.com/go?to=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzk0NzAzNTM0Mg%3D%3D%26mid%3D2247484020%26idx%3D2%26sn%3Db46ca04124c7498be9c5ee85017947b6%26chksm%3Dc37c4fd5f40bc6c36776d1b0b7e5ae259d3830779b00dc062951fddaf269eb1f1ae3e1eeabd8%26token%3D1276697545%26lang%3Dzh_CN%23rd ): the singleton pattern ensures that a class has only one instance, and it instantiates itself and provides this instance to the whole system. This class is called a singleton class, which provides global access methods.

There are three key points of singleton mode: first, a class can only have one instance; second, it must create this instance by itself; third, it must provide this instance to the whole system by itself. Singleton mode is an object creation mode. Singleton mode is also known as singleton mode or singleton mode.

image.png

There are two singleton modes used in Mybatis, ErrorContext and LogFactory. ErrorContext is a singleton used in each thread to record the execution environment error information of the thread, while LogFactory is a log factory provided to the whole Mybatis to obtain the log objects configured for the project.

Single instance implementation code of ErrorContext:

public class ErrorContext {
 private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
 private ErrorContext() {
 }
 public static ErrorContext instance() {
  ErrorContext context = LOCAL.get();
  if (context == null) {
   context = new ErrorContext();
   LOCAL.set(context);
  }
  return context;
 }

The constructor is a private modifier. It has a static local instance variable and a method to obtain the instance variable. In the method to obtain the instance, first judge whether it is empty. If so, create it first, and then return the constructed object.

But here's an interesting thing: the static instance variable of LOCAL is decorated with ThreadLocal, that is, it belongs to the respective data of each thread. In the instance() method, first obtain the instance of this thread, and if not, create the ErrorContext unique to this thread.

4. Agent mode

proxy pattern It can be considered as the mode used by the core of mybatis. Because of this mode, we only need to write Mapper.java interface without implementation. Mybatis background helps us complete the specific SQL execution.

Proxy pattern: provides a proxy for an object, and the proxy object controls the reference to the original object

It is called Proxy or Surrogate, which is an object structured pattern.

The agent mode includes the following roles:

  • Subject: abstract topic role
  • Proxy: proxy subject role
  • RealSubject: real theme role

image.png

image.png

There are two steps. The first is to create a Proxy in advance. The second is to automatically request a Proxy when using it, and then the Proxy will execute specific transactions;

When we use the getMapper method of Configuration, mapperRegistry.getMapper method will be called, and this method will call mapperProxyFactory.newInstance(sqlSession) to generate a specific proxy:

public class MapperProxyFactory<T> {
 private final Class<T> mapperInterface;
 private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
 public MapperProxyFactory(Class<T> mapperInterface) {
  this.mapperInterface = mapperInterface;
 }
 public Class<T> getMapperInterface() {
  return mapperInterface;
 }
 public Map<Method, MapperMethod> 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<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
 }
}

Here, we first get a MapperProxy object through T newInstance(SqlSession sqlSession) method, and then call T.

newInstance(MapperProxymapperProxy) generates a proxy object and returns.

If you view the MapperProxy code, you can see the following:

public class MapperProxy<T> implements InvocationHandler, Serializable {
 @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);
 }

Typically, the MapperProxy class implements the InvocationHandler interface and the invoke method of the interface.

In this way, we only need to write Mapper.java interface class. When a Mapper interface is really executed, it will be forwarded to MapperProxy.invoke method, which will call a series of subsequent methods such as sqlsession. Cud > executor. Execute > preparestatement to complete the execution and return of SQL.

5. Combination mode

Combination mode Combine multiple objects to form a tree structure to represent the "whole"-

The structural hierarchy of "part".

Composite pattern is consistent with single object (leaf object) and composite object (composite object). It organizes objects into tree structure and can be used to describe the relationship between whole and part. At the same time, it also blurs the concepts of simple elements (leaf objects) and complex elements (container objects), so that customers can deal with complex elements like simple elements, so that the client program can be decoupled from the internal structure of complex elements.

In the use of composite mode, one thing to note is also the key point of composite mode: leaf objects and composite objects implement the same interface. This is why the combination mode can handle leaf nodes and object nodes consistently.

image

Mybatis supports the powerful functions of dynamic SQL, such as the following SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name != ''">
            name = #{name}
        </if>
        <if test="age != null and age != ''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday != ''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

Dynamic elements such as trim and if are used to generate SQL under different conditions;

In the DynamicSqlSource.getBoundSql method, the rootSqlNode.apply(context) method is called. The apply method is the interface implemented by all dynamic nodes:

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

All nodes that implement the SqlSource interface are the nodes of the entire composite pattern tree:

image

The simplicity of the combination mode is that all child nodes are nodes of the same type and can be executed recursively downward. For example, for TextSqlNode, because it is the lowest leaf node, it directly append s the corresponding content to the SQL statement:

 @Override
 public boolean apply(DynamicContext context) {
  GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
  context.appendSql(parser.parse(text));
  return true;
 }

However, for IfSqlNode, you need to make a judgment first. If the judgment passes, you will still call the SqlNode of the child element, that is, the contents.apply method, to realize recursive parsing.

 @Override
 public boolean apply(DynamicContext context) {
  if (evaluator.evaluateBoolean(test, context.getBindings())) {
   contents.apply(context);
   return true;
  }
  return false;
 }

6. Template method pattern

Template method pattern Is one of the most common patterns in all patterns, and is the basic technology of code reuse based on inheritance.

The template method pattern requires collaboration between designers who develop abstract classes and concrete subclasses. One designer is responsible for giving the outline and skeleton of an algorithm, while others are responsible for giving the logical steps of the algorithm. The method representing these specific logical steps is called primitive

method); The method that summarizes these basic methods is called template method, from which the name of this design pattern comes.

The template class defines the skeleton of the algorithm in an operation, and delays some steps to subclasses. The subclass can redefine some specific steps of an algorithm without changing the structure of the algorithm.

image

In Mybatis, the SQL execution of sqlSession is delegated to the Executor. The Executor contains the following structure:

image

The BaseExecutor adopts the template method mode, which implements most of the SQL execution logic, and then gives the following methods to subclass Customization:

 protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
 protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
   ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

The template method class has specific implementations of several subclasses and uses different strategies:

  • Simple executor: each time update or select is executed, a Statement object will be opened, and the Statement object will be closed immediately after use. (can be a Statement or PrepareStatement object)
  • Reuse reuseexecution: execute update or select, find the Statement object with sql as the key, use it if it exists, and create it if it does not exist. When it is used up, the Statement object is not closed, but placed in map < string, Statement > for next use. (can be a Statement or PrepareStatement object)
  • Batch BatchExecutor: executes update (no select, JDBC batch does not support select), adds all SQL to batch processing (addBatch()) and waits for unified execution (executeBatch()), which caches multiple Statement objects. Each Statement object waits for executeBatch() batch processing one by one after addBatch() is completed; The batch executor maintains multiple buckets. Each bucket contains a lot of its own SQL, just like the Apple Blue contains a lot of apples and the tomato blue contains a lot of tomatoes. Finally, it is poured into the warehouse. (can be a Statement or PrepareStatement object)

For example, the update method is implemented in simpleexecution as follows:

 @Override
 public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
  Statement stmt = null;
  try {
   Configuration configuration = ms.getConfiguration();
   StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
     null);
   stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.update(stmt);
  } finally {
   closeStatement(stmt);
  }
 }

7. Adapter mode

[Adapter mode

Pattern)](https://links.jianshu.com/go?to=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzk0NzAzNTM0Mg%3D%3D%26mid%3D2247484020%26idx%3D2%26sn%3Db46ca04124c7498be9c5ee85017947b6%26chksm%3Dc37c4fd5f40bc6c36776d1b0b7e5ae259d3830779b00dc062951fddaf269eb1f1ae3e1eeabd8%26token%3D1276697545%26lang%3Dzh_CN%23rd)

: convert an interface to another interface that the customer wants. The adapter mode enables those classes that are incompatible with the interface to work together. Its alias is wrapper. The adapter mode can be used as either class structured mode or object structured mode.

image.png

In the logging package of Mybatsi, there is a Log interface:

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

This interface defines the logging methods directly used by mybatis, and who will implement the Log interface? Mybatis provides the implementation of a variety of logging frameworks, which match the interface methods defined by this Log interface, and finally implements all external logging frameworks to Mybatis Adaptation of log package:

image

For example, for the implementation of Log4jImpl, the implementation holds an instance of org.apache.log4j.Logger, and then all log methods are delegated to the instance.

public class Log4jImpl implements Log {
 private static final String FQCN = Log4jImpl.class.getName();
 private Logger log;
 public Log4jImpl(String clazz) {
  log = Logger.getLogger(clazz);
 }
 @Override
 public boolean isDebugEnabled() {
  return log.isDebugEnabled();
 }
 @Override
 public boolean isTraceEnabled() {
  return log.isTraceEnabled();
 }
 @Override
 public void error(String s, Throwable e) {
  log.log(FQCN, Level.ERROR, s, e);
 }
 @Override
 public void error(String s) {
  log.log(FQCN, Level.ERROR, s, null);
 }
 @Override
 public void debug(String s) {
  log.log(FQCN, Level.DEBUG, s, null);
 }
 @Override
 public void trace(String s) {
  log.log(FQCN, Level.TRACE, s, null);
 }
 @Override
 public void warn(String s) {
  log.log(FQCN, Level.WARN, s, null);
 }
}

8. Decorator mode

[Decorator

Pattern)](https://links.jianshu.com/go?to=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzk0NzAzNTM0Mg%3D%3D%26mid%3D2247484020%26idx%3D2%26sn%3Db46ca04124c7498be9c5ee85017947b6%26chksm%3Dc37c4fd5f40bc6c36776d1b0b7e5ae259d3830779b00dc062951fddaf269eb1f1ae3e1eeabd8%26token%3D1276697545%26lang%3Dzh_CN%23rd)

: dynamically add some additional responsibilities to an object. In terms of adding object functions, decoration mode is more flexible than generating subclass implementation. Its alias can also be called wrapper, which is the same as that of adapter mode, but they are applicable to different occasions. According to different translations, decoration mode is also called "painter mode" , it is an object structured pattern.

image.png

In mybatis, the function of cache is defined by the root interface cache (org.apache.ibatis.cache.Cache). The whole system adopts decorator design mode. The basic functions of data storage and cache are realized by perpetual cache (org. Apache. Ibatis. Cache. Impl. Perpetual cache), and then a series of decorators are used to control the perpetual cache of perpetual cache, such as cache strategy. As shown below:

image.png

There are 8 standard decorators for decorating the perpetual cache (all in the org.apache.ibatis.cache.decorators package):

  1. FifoCache: FIFO algorithm, cache recycling strategy
  2. LoggingCache: outputs the log information of cache hits
  3. LruCache: least recently used algorithm, cache recycling strategy
  4. ScheduledCache: scheduling cache, which is responsible for clearing the cache regularly
  5. SerializedCache: cache serialized and deserialized storage
  6. SoftCache: cache management strategy based on soft reference
  7. Synchronized cache: a synchronized cache decorator used to prevent concurrent access by multiple threads
  8. WeakCache: cache management strategy based on weak reference

In addition, there is a special decorator transactional cache: transactional cache

Like most persistence layer frameworks, mybatis cache is also divided into level 1 cache and level 2 cache

  • The L1 cache, also known as local cache, is a persistent cache of PerpetualCache type, which is stored in the executor (BaseExecutor), and the executor is in the SqlSession (DefaultSqlSession). Therefore, the lifecycle of the L1 cache is the same as that of the SqlSession.
  • L2 Cache, also known as user-defined Cache, can be used as L2 Cache for all classes that implement Cache interface, so third-party Cache such as encache can be configured. The L2 Cache takes the namespace namespace as its unique identifier and is saved in the Configuration core Configuration object.

The default type of L2 cache object is perpetual cache. If the configured cache is the default type, mybatis will automatically append a series of decorators according to the configuration.

The reference order between Cache objects is:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9. Iterator mode

Iterator mode , also known as Cursor mode. The definition given by GOF is to provide a method to access each element in a container object without exposing the internal details of the object.

image

The Iterator of Java is the interface of Iterator mode. As long as the interface is implemented, the Iterator mode is applied:

image

such as Mybatis The PropertyTokenizer of is a heavyweight class in the property package, which will be frequently referenced by other classes in the reflection package. This class implements the Iterator interface. The function hasNext in the Iterator interface is often used.

public class PropertyTokenizer implements Iterator<PropertyTokenizer> {
 private String name;
 private String indexedName;
 private String index;
 private 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.");
 }
}

Added by maseeha on Tue, 23 Nov 2021 06:43:17 +0200