preface

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

Mybatis encounters at least the use of the following design patterns

1. Builder mode

Examples include SqlSessionFactoryBuilder, XMLConfigBuilder, XMLMapperBuilder, XMLStatementBuilder, and CacheBuilder.

2. Factory mode

For example, SqlSessionFactory, ObjectFactory, and MapperProxyFactory.

3. Singletons

Examples are ErrorContext and LogFactory.

4. Proxy mode

Mybatis implements core, such as MapperProxy, ConnectionLogger, using JDK dynamic proxy; There is also the executor. Loader package that uses cglib or Javassist for lazy loading.

5. Combination mode

Examples include SqlNode and subclasses ChooseSqlNode.

6. Template method pattern

Like BaseExecutor and SimpleExecutor, and BaseTypeHandler and all the subclasses like IntegerTypeHandler.

7. Adapter mode

For example, Log Mybatis interface and its JDBC, log4J and other Log framework adaptation implementation.

8. Decorator mode

For example, the implementation of decorators in the cache.decorators subpackage of the Cache package.

9. Iterator pattern

For example, the iterator pattern PropertyTokenizer.

Next, we will interpret each pattern one by one, first introduce the knowledge of the pattern itself, and then explain how to apply the pattern in Mybatis.

1. 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 Mybatis initialization, SqlSessionFactoryBuilder calls XMLConfigBuilder to read all myBatismapconfig. XML and all mapper. XML files. Construct the Configuration object, which is the core object that Mybatis runs on, and then construct an SqlSessionFactory object using this Configuration object as a parameter.

XMLConfigBuilder calls XMLMapperBuilder to read the *Mapper file when building the Configuration object. XMLMapperBuilder uses XMLStatementBuilder to read and build all the SQL statements.

A similar feature of this process is that these Builders read files or configurations, and then do a lot of XpathParser parsing, configuration or syntax parsing, reflection generating objects, caching results, and so on, much more work than a single constructor can do. Therefore, the Builder model is adopted to solve the problem.

SqlSessionFactoryBuilder ¶ For example, SqlSessionFactoryBuilder contains the following methods:

This is to build the SqlSessionFactory factory object with different input parameters.

Ii. Factory mode

In Mybatis, SqlSessionFactory uses the factory pattern, which is not so complex logic, but a simple factory pattern.

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

SqlSession can be considered a core interface for Mybatis to work with. This interface is used to execute SQL statements, obtain Mappers, and manage transactions. Similar to the Connection object used to connect to MySQL.

As you can see, the openSession method of the Factory is overloaded with multiple inputs for autoCommit, Executor, Transaction, and other parameters 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. This method reads the environment configuration from configuration and initializes TransactionFactory to obtain a Transaction object. An Executor object is obtained through Transaction, and an SqlSession is constructed using three parameters: configuration, Executor, and autoCommit.

You can see here that the execution of the SqlSession is actually delegated to the corresponding Executor.

For LogFactory, its 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

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

Three, singleton pattern

Singleton Pattern: A Singleton Pattern ensures that there is only one instance of a class that is instantiated and provided to the entire system by itself. This class is called a Singleton class and provides global access to methods.

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

In Mybatis, ErrorContext is used in two places, ErrorContext and LogFactory, where ErrorContext is used in each thread scope singleton, used to record the execution environment error information of that thread. The LogFactory is the LogFactory provided to the entire Mybatis to get the log objects 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 decoration, has a static local instance variable and a method that gets the instance variable. In the method that gets the instance, it determines whether it is null, creates it if it is, and then returns the constructed object.

The interesting thing here is that the static instance variable of LOCAL is decorated with ThreadLocal, that is, it belongs to each thread’s own data, whereas in the instance() method, an instance of this thread is first obtained, and if not, an ErrorContext unique to that thread is created.

Fourth, agent mode

Agent mode can be considered as the core mode used by Mybatis, because of this mode, we only need to write mapper. Java interface, do not need to implement, Mybatis background to help us complete the specific SQL execution.

Proxy Pattern: Provides a Proxy for an object and controls the reference to the original object. Proxy mode is called Proxy or Surrogate, and is an object structure mode.

The proxy mode contains the following roles:

1.Subject:

Abstract theme role

2.Proxy:

Proxy subject role

3.RealSubject:

Real Theme characters

There are two steps, the first is to create a Proxy in advance, the second is to automatically request the Proxy when using, and then the Proxy to perform the specific transaction; When we use the Configuration getMapper method, we call the mapperRegistry. GetMapper method, 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, we first get a MapperProxy object by using the T newInstance(SqlSession SqlSession) method, then call T newInstance(MapperProxymapperProxy) to generate the proxy object and return it.

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

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

In this way, we only need to write the Mapper. Java interface class. When we actually execute a Mapper interface, it will be forwarded to the MapperProxy.invoke method. Execute >prepareStatement to complete the execution and return the SQL.

Five, combination mode

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

The combination pattern has consistency for single objects (leaf objects) and composite objects (composite objects). It organizes objects into a tree structure, which can be used to describe the relationship between whole and parts. It also obfuscates the concept of simple elements (leaf objects) and complex elements (container objects), allowing the client to treat complex elements as if they were simple elements, thus allowing the client to decouple from the internal structure of complex elements.

One thing to note in using the composite pattern is that it is critical that leaf objects and composite objects implement the same interface. This is why the composite pattern allows for consistent processing of leaf nodes and object nodes.

Mybatis supports powerful 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

In this case, dynamic elements such as trim and if are used to generate SQL under different 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 combined pattern tree:

The simplicity of the composite mode is that all child nodes are of the same class and can be recursively executed downward. For example, in the case of TextSqlNode, because it is the lowest leaf node, the corresponding content is appended directly into 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, we need to make a judgment first. If the judgment is passed, we will still call the SqlNode of the child element, that is, contents. Apply method, to achieve recursive resolution.

	@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 pattern

The template method pattern is one of the most common among all 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 method that represents these specific logical steps is called the primitive method; The method that sums up these basic methods is called a template method, hence the name of the design pattern.

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

In Mybatis, SQL execution of sqlSession is delegated to Executor implementations, which contain the following structure:

BaseExecutor takes the template method pattern, implements most of the SQL execution logic, and 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 several subclasses that implement different strategies:

1. Simple SimpleExecutor

Each time an UPDATE or SELECT is performed, a Statement object is opened and the Statement object is closed as soon as it is used up. (This can be a Statement or PrepareStatement object)

2. Reuse ReuseExecutor

If the Statement object exists, use it. If it does not exist, create it. When the Statement object is used up, do not close it. (This can be a Statement or PrepareStatement object)

3. The batch BatchExecutor

Execute update (no SELECT, JDBC batch does not support SELECT), add all SQL to the batch (addBatch()), and wait for uniform execution (executeBatch()), which caches multiple Statement objects, Each Statement object is waiting for executeBatch() to be executed one by one after addBatch() has finished. BatchExecutor maintains multiple buckets, each containing its own SQL, like apples in apple blue and tomatoes in tomato blue, which are dumped into the warehouse. (This can be a Statement or PrepareStatement object)

For example, we implement the update method in SimpleExecutor 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: Converts one interface to another interface that the client wants. Adapter Pattern enables classes that are incompatible with the interface to work together, nicknamed wrappers. The adapter pattern can be used as either a class structural pattern or an object structural pattern.

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

For example, the Log4jImpl implementation holds an instance of org.apache.log4j.Logger and 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 adds additional Responsibility to an object. Decorator patterns are more flexible than subclassing implementations in terms of adding functionality to an object. The alias can also be called wrappers, the same as the alias for the adapter pattern, but they are applicable in different situations. The decorative pattern, also known as the “painter pattern” depending on the translation, 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, The PerpetualCache is then easily controlled with a series of decorators such as the cache policy. 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 matched by the cache is displayed

3. The LruCache:

Least recently used algorithm, cache reclaim strategy

4. ScheduledCache:

Schedule the cache, responsible for periodic cache clearing

5. SerializedCache:

Cache serialization and deserialization storage

6. SoftCache:

Cache management strategy based on soft reference

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, TransactionalCache: TransactionalCache

Like most persistence frameworks, myBatis caches are divided into level-1 and level-2 caches

1. Level 1 cache

Also known as the local cache, PerpetualCache is a PerpetualCache type stored in the BaseExecutor (BaseExecutor), which is in the SqlSession (DefaultSqlSession). Therefore, the lifecycle of the level 1 cache is the same as that of the SqlSession.

2. Level-2 cache

Also known as custom Cache, any class that implements the Cache interface can be used as a secondary Cache, so third-party caches such as Encache can be configured. The level-2 cache is uniquely identified by the Namespace namespace and is stored in the Configuration core Configuration object.

3. Level-2 cache objects

The default type is PerpetualCache. If the configured cache is the default type, MyBatis will automatically append a series of decorators based on the configuration.

Cache objects are referenced in the following order:

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

The iterator pattern

The Iterator mode is also known as the Cursor mode. GOF defines it as providing a way to access the elements of a container object without exposing the inner details of the object.

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

For example, Mybatis PropertyTokenizer is a heavyweight class in the Property package that is frequently referenced by other classes in the Reflection package. 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 then provides an iterator method to iterate over the parsed substring. This is a very common method class.

conclusion

I sort out a: Mybatis brain map, Mybatis related data documents, Spring family bucket series, Java systematic data, (including Java core knowledge points, interview topics and 20 years of the latest Internet real questions, ebooks, etc.) friends in need can follow the public number [program Yuanxiaowan] can get.