preface

Although we all know that there are 26 design patterns, but most of them stay at the conceptual level and rarely encountered in real development. Mybatis source code uses a large number of design patterns. Reading the source code and observing the application of design patterns in it, we can have a deeper understanding of design patterns.

Mybatis has encountered at least the use of the following design patterns:

1. Builder mode

For example, SqlSessionFactoryBuilder, XMLConfigBuilder, XMLMapperBuilder, XMLStatementBuilder, CacheBuilder;

2. Factory mode

For example SqlSessionFactory, ObjectFactory, MapperProxyFactory;

3. Singleton mode

Examples are ErrorContext and LogFactory;

4. Proxy mode

Mybatis implementation of the core, such as MapperProxy, ConnectionLogger, JDK dynamic proxy; The executor. Loader package uses Cglib or Javassist for lazy loading;

5. Combination mode

Such as SqlNode and each subclass ChooseSqlNode;

6. Template method mode

Such as BaseExecutor and SimpleExecutor, BaseTypeHandler and all the subclasses such as IntegerTypeHandler;

7. Adapter mode

For example, the Mybatis interface of Log and its adaptation to JDBC, LOG4j and other logging frameworks

8. Decorator mode

For example, implementations of individual decorators in the cache.decorators subpackage of the Cache package;

Iterator pattern

For example, the iterator pattern PropertyTokenizer;

Next, we will interpret each mode one by one, first introducing the knowledge of the mode itself, and then interpreting how the mode is applied in Mybatis.

I. Builder mode

The Builder pattern is defined as “separating the construction of a complex object from its representation so that the same construction process can create different representations.” , it belongs to create the class mode, generally speaking, if an object construction is complicated, is beyond the scope of the constructor can contain, you can use the factory pattern and the Builder pattern, relative to the factory pattern will produce a complete product, the Builder is applied to build more complex object, even will only build a part of the product.

During the initialization of the Mybatis environment, SqlSessionFactoryBuilder calls XMLConfigBuilder to read all mybatismapconfig. XML and all * mapper. XML files. Build the core object of Mybatis to run the Configuration object, and then use this Configuration object as a parameter to build an SqlSessionFactory object.

When XMLConfigBuilder builds the Configuration object, XMLMapperBuilder is also called to read the *Mapper file. The XMLMapperBuilder uses the XMLStatementBuilder to read and build all SQL statements.

A similar feature in this process is that these Builders read files or configurations and then do a lot of XpathParser parsing, configuration or syntax parsing, reflecting generated objects, caching results, and so on, much more than a constructor can do. Therefore, a large number of Builder model to solve.

For concrete classes of Builder, methods usually start with build*. For example, SqlSessionFactoryBuilder contains the following methods:

The factory object SqlSessionFactory is built based on different input parameters.

Ii. Factory model

In Mybatis, for example SqlSessionFactory uses factory mode, which is a simple factory mode with less complicated logic.

Simple Factory Pattern: Also known as Static Factory Method Pattern, it belongs to the class creation Pattern. In the simple factory pattern, you can return instances of different classes depending on the parameters. The simple factory pattern specifically defines a class that is responsible for creating instances of other classes, which usually have a common parent class.

SqlSession can be thought of as an interface at the heart of how Mybatis works, where SQL statements can be executed, Mappers can be retrieved, and transactions can be managed. A Connection object similar to a MySQL Connection object.

As you can see, the Factory openSession method is overloaded to support the input of parameters such as autoCommit, Executor, Transaction, etc., 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(); }}Copy the code

This is a low-level method called by openSession that reads the corresponding environment configuration from Configuration and then initializes the TransactionFactory to get a Transaction object, Then Transaction is used to obtain an Executor object, and SqlSession is constructed using the configuration, Executor, and autoCommit parameters.

SqlSession execution is delegated to the corresponding Executor.

For LogFactory, the implementation code is:

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

A special feature here is that the type of the Log variable is Constructor<? Extends Log>, which means that the factory produces not just one product, but a series of products with a common Log interface, such as Log4jImpl, Slf4jImpl, and many more specific logs.

Singleton mode

Singleton Pattern: The Singleton Pattern ensures that a class has only one instance and that it instantiates and provides that instance to the entire system. This class is called a Singleton class, which provides globally accessible methods.

The singleton pattern has three main points: first, there can be only one instance of a class; Second, it must create the instance itself; Third, it must provide this instance to the entire system itself. The singleton pattern is an object creation pattern. Singleton is also called singleton or singleton.

Mybatis uses singleton mode in two places, ErrorContext and LogFactory. ErrorContext is a singleton within the scope of each thread, which is used to record the error information of the execution environment of the thread. The LogFactory is the LogFactory provided to the entire Mybatis to get the log object configured for the project.

ErrorContext singleton implementation code:

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;
	}
Copy the code

The constructor is a private modifier that takes a static local instance variable and a method to get it. In the instance method, it determines whether it is null and if so, it creates it and then returns the constructed object.

The interesting thing here is that the static instance variable of LOCAL uses ThreadLocal, which means it belongs to each thread’s data, whereas in instance() we get the instance of this thread first and create a thread-specific ErrorContext if it doesn’t.

4. Agency mode

The proxy mode 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, and Mybatis background helps us to complete the execution of specific SQL.

Proxy Pattern: Provides an agent for an object, and the Proxy object controls the reference to the original object. Surrogate, known in English as Proxy or Surrogate, is an object-structured pattern.

The proxy mode contains the following roles:

Subject: Abstracts the topic role

Proxy: indicates the role of the Proxy topic

RealSubject: RealSubject roles

There are two steps. The first step is to create a Proxy in advance. The second step is to automatically request the Proxy when using it, and then the Proxy performs specific transactions. When we use the getMapper method of Configuration, the mapperregistry.getmapper method is called, And this method will be called mapperProxyFactory. NewInstance (sqlSession) to generate a specific agent:

/** * @author Lasse Voss */ 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); }}Copy the code

In this case, T newInstance(SqlSession SqlSession) is used to obtain a MapperProxy object, and then T newInstance(MapperProxymapperProxy) is called to generate the proxy object and return it.

If you look at the code for MapperProxy, 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);
	}
Copy the code

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

In this way, we simply write the Mapper.java interface class, which is passed to the MapperProxy.invoke method when a Mapper interface is actually executed. This method calls a series of methods, such as SQLsession. cud>executor.execute>prepareStatement, to execute and return SQL.

5. Combination mode

The composition pattern combines multiple objects into a tree structure to represent a whole-part hierarchy.

The composite pattern is consistent with both a single object (leaf object) and a composite object (composite object). It organizes objects into a tree structure that can be used to describe the relationship between whole and part. It also blurs the concepts of simple elements (leaf objects) and complex elements (container objects), allowing clients to treat complex elements as if they were simple, thus decoupling the internal structure of complex elements.

One thing to note when using composite patterns is the most critical aspect of composite patterns: leaf objects and composite objects implement the same interface. This is why the composite pattern is able to treat leaf nodes and object nodes identically.

Mybatis supports 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>Copy the code

Dynamic elements such as trim and if are used to generate SQL in different situations based on conditions;

In DynamicSqlSource. GetBoundSql method, called the rootSqlNode. Apply (context) method, the apply method are all dynamic node implementation of the interface:

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

For all nodes implementing the SqlSource interface, that is, the nodes of the entire composite pattern tree:

The simplicity of the composite pattern is that all child nodes are of the same class and can be executed recursively. For example, for TextSqlNode, which is the lowest leaf node, append the corresponding content directly to the SQL statement:

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

But for IfSqlNode, it needs to make a judgment first. If the judgment passes, it will still call SqlNode of the child element, namely contents. Apply method, to achieve recursive parsing.

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

Vi. Template method mode

The template method pattern is one of the most common patterns and is a basic technique for inheritance-based code reuse.

The template method pattern requires collaboration between designers who develop abstract classes and concrete subclasses. One designer is responsible for the outline and skeleton of an algorithm, while others are responsible for the logical steps of the algorithm. The primitive methods that represent these specific logical steps are called primitive methods. The method that combines these basic methods is called template method, from which the design pattern gets its name.

The template class defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Allows subclasses to redefine specific steps of an algorithm without changing its structure.

In Mybatis, SQL session execution is delegated to Executor. Executor contains the following structure:

BaseExecutor, for example, uses the template method pattern, which implements most of the SQL execution logic and then leaves 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;
Copy the code

The template method class has concrete implementations of several subclasses that use different strategies:

1. SimpleExecutor:

Each time you perform an update or SELECT operation, you open a Statement object and close it immediately. (Can be a Statement or a PrepareStatement object)

2. Reuse ReuseExecutor:

If the Statement object exists, use it. If the Statement object does not exist, create it. After the Statement object is used, place it in Map<String, Statement> for next use. (Can be a Statement or a PrepareStatement object)

Batch BatchExecutor:

Update (no SELECT, JDBC batch does not support SELECT), add all SQL to the batch (addBatch()), and wait for execution (executeBatch()), which caches multiple Statement objects. Each Statement object is executed one by one after addBatch() is complete. BatchExecutor maintains several buckets, each with its own SQL, like apple blue with many apples, tomato blue with many tomatoes, and finally dumped into the warehouse. (Can be a Statement or a PrepareStatement object)

For example, the update method in SimpleExecutor looks like this:

@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); }}Copy the code

7. Adapter mode

Adapter Pattern: Transforms one interface into another interface that the customer wants. The Adapter Pattern enables classes with incompatible interfaces to work together. Its alias is wrappers. The adapter pattern can be used as either a class schema or an object schema.

In Mybatsi’s logging package, there is a Log interface:

/**
 * @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);

}
Copy the code

This interface defines the logging method directly used by Mybatis, and who implements the Log interface? Mybatis provides a variety of logging framework implementations, all of which match the interface method defined by the Log interface, and finally implements all external logging framework to Mybatis Log package adaptation:

For example, the Log4jImpl implementation holds an instance of org.apache.log4j.logger, which delegates all logging methods to that 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); }}Copy the code

Eight, decorator mode

Decorator Pattern: Dynamically assigning additional responsibilities to an object is more flexible in terms of adding functionality to the object than generating subclass implementations. It can also be called a Wrapper, the same alias as the adapter pattern, but they are suitable for different situations. Depending on the translation, decoration pattern is also called “painter pattern”, which is an object structure pattern.

In mybatis, Cache Cache function by root interface (org. Apache. Ibatis. Cache. The Cache) definition. The system USES the decorator design pattern, the basic function of data storage and the cache by PerpetualCache (org. Apache. Ibatis. Cache. Impl. PerpetualCache) permanent cache implementation, It then provides convenient control over the PerpetualCache through a series of decorators, such as cache strategy. The diagram below:

Standard for decorative PerpetualCache decorator a total of eight (all in org. Apache. Ibatis. Cache. Decorators package) :

1. FifoCache:

Fifo algorithm, cache reclamation strategy

2. LoggingCache:

The log information about cache hits is displayed

3. The LruCache:

Least recently used algorithm, cache reclamation policy

4. ScheduledCache:

Scheduling cache, responsible for periodically clearing the cache

5. SerializedCache:

Cache serialization and deserialization storage

## 36.SoftCache: Cache management strategy based on soft reference implementation

7. SynchronizedCache:

Synchronous cache decorator to prevent concurrent access by multiple threads

8. WeakCache:

Cache management strategy based on weak reference implementation

In addition, there is a special decorator called TransactionalCache: a TransactionalCache

Like most persistence frameworks, myBatis cache is divided into level 1 cache and level 2 cache

A level 1 cache, also called a local cache, is a PerpetualCache of type PerpetualCache, stored in a BaseExecutor, which in turn resides in SqlSession DefaultSqlSession, So the life cycle of level 1 cache is the same as SqlSession. Level-2 Cache, also known as custom Cache, can be configured for third-party caches such as Encache. The level 2 cache is uniquely identified by the Namespace and stored in the Configuration core Configuration object. The default type of the second-level cache object is PerpetualCache, and if the configured cache is PerpetualCache, MyBatis automatically appends a series of decorators based on the configuration.

Cache objects are referenced in the following order:

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

Iterator pattern

Iterator pattern, also known as Cursor pattern. GOF is defined as providing a way to access elements of a container object without exposing the inner details of the object.

A Java Iterator is an interface to the Iterator pattern. Implementing this interface is equivalent to applying the Iterator pattern:

For example, Mybatis PropertyTokenizer is a heavyweight property class that is frequently referenced by other classes in reflection. This class implements the Iterator interface. The hasNext function 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."); } }Copy the code

As you can see, this class passes a string to the constructor and provides an iterator method to iterate over the parsed substring. It is a very common method class.

The last

My side organized a: Mybatis related information, Mybatis brain map, Spring family barrel series, Java systematic information, (including Java core knowledge points, interview topics and 20 years of the latest Internet real questions, e-books, etc.) need friends can pay attention to the public number 【 procedure yuan small 】 can be obtained.