There are 9 design patterns used by Mybatis. How many do you know

Posted by chrishide87 on Fri, 31 Dec 2021 21:00:18 +0100

A large number of design patterns are used in the Mybatis source code. Reading the source code and observing the application of design patterns can have a deeper understanding of design patterns.

Mybatis has encountered at least the following design patterns:

  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; And the executor The loader package uses cglib or javassist to delay 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 Cache in Cache package The implementation of each decorator in the decorators sub 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

The definition of builder pattern is to "separate the construction of a complex object from its representation, so that the same construction process can create different representations.", It belongs to the creation class mode. Generally speaking, if the construction of an object is more complex and beyond the scope of the constructor, you can use the factory mode and builder mode. Compared with the factory mode, it will produce a complete product. Builder is applied to the construction of more complex objects, or even only a part of the product.

During the initialization of Mybatis environment, SqlSessionFactoryBuilder will call XMLConfigBuilder to read all mybatismap config XML and all * mapper XML file, build the core object Configuration object of 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:

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

2. Factory mode

In Mybatis, for example, SqlSessionFactory uses factory mode, which is not so complex logic, but a simple factory mode.

Simple factory pattern: also known as static factory method pattern, it belongs to class creation pattern. java training 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.

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.

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, which means that the factory produces not only a product, but a series of products with Log public interface, such as Log4jImpl, Slf4jImpl and many specific logs.

3. Singleton mode

Singleton pattern: the singleton pattern ensures that a class has only one instance, and 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 main 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 example to the whole system by itself. Singleton mode is an object creation mode. Singleton mode is also known as singleton mode or singleton mode.

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

The proxy pattern can be regarded as the pattern used by the core of Mybatis. Because of this pattern, we only need to write mapper The java interface does not need to be implemented. The Mybatis background helps us complete the specific SQL execution.

Proxy pattern: provide a proxy for an object, and the proxy object controls the reference to the original object. Proxy mode is called proxy or Surrogate in English. It is an object structure mode.

The agent mode includes the following roles:

  • Subject: abstract topic role

  • Proxy: proxy subject role

  • RealSubject: real theme role

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;

Mapperregistry is called when we use the getMapper method of Configuration getMapper method, which in turn calls mapperproxyfactory Newinstance (sqlsession) to generate a specific agent:

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 the T newInstance(SqlSession sqlSession) method, then call T newInstance(MapperProxymapperProxy) to generate the proxy object and then return it.

When 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 actually executed, it will be forwarded to mapperproxy Invoke method, which will call the subsequent sqlsession cud>executor. Execute > preparestatement and other methods to complete the execution and return of SQL.

5. Combination mode

The combination mode combines multiple objects to form a tree structure to represent the structural hierarchy of "whole part".

The composite mode is consistent for single objects (leaf objects) and composite objects (composite objects), java programming training It organizes objects into a 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.

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 dynamicsqlsource In the getboundsql method, rootsqlnode is called apply (context) method, which 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:

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 is passed, the SqlNode of the child element, i.e. contents. Will still be called 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. It 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 the 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.

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

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 reuse executor: execute update or select, use sql as the key to find the Statement object, use it if it exists, and create it if it does not exist. After using it, the Statement object is not closed, but placed in the Map 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 pattern: convert an interface into another interface that the customer wants. The adapter pattern enables those classes whose interfaces are incompatible to work together. Its alias is wrapper. The adapter pattern can be either a class structured pattern or an object structured pattern.

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 Log method directly used by mybatis. Who will implement the Log interface? Mybatis provides a variety of Log framework implementations, which match the interface methods defined by the Log interface, and finally realize the adaptation of all external Log frameworks to mybatis Log packages:

For example, for the implementation of Log4jImpl, the implementation holds org apache. log4j. An instance of logger, and then all logging methods are delegated to this 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: dynamically add some additional responsibilities to an object. In terms of adding object functions, decorator pattern is more flexible than generating subclass implementation. Its aliases can also be called wrappers, which are the same as those of the adapter pattern, but they are suitable for different situations. According to different translations, decoration mode is also called "painter mode", which is an object structure mode.

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, and the basic functions of data storage and cache are defined by PerpetualCache (org.apache.ibatis.cache.impl.PerpetualCache) permanent cache is implemented, and then a series of decorators are used to control the permanent cache of PerpetualCache for convenience, such as cache policy. As shown in the following figure:

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

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

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

For example, Mybatis's PropertyTokenizer is a heavyweight class in the property package, which is 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.");
 }
}

You can see that this class passes in a string to the constructor, and then provides the iterator method to traverse the parsed substring. It is a very common method class.

Topics: Java Back-end