Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Deep Dive: Design Patterns Within Mybatis Source Code

Tech May 8 5

Understanding design patterns theoretically often differs from observing their practical application in large-scale frameworks. While there are 23 standard patterns, developers frequently encounter them more clearly within mature source codebases like Mybatis. Analyzing these internals reveals how complex systems manage object creation, SQL execution, and configuration parsing.

Mybatis employs the following design patterns to handle its core functionalities:

  1. Builder Pattern
  2. Factory Pattern
  3. Singleton Pattern
  4. Proxy Pattern
  5. Composite Pattern
  6. Template Method Pattern
  7. Adapter Pattern
  8. Decorator Pattern
  9. Iterator Pattern

Below is an analysis of each pattern as applied to the framework.

1. Builder Pattern

The Builder pattern separates the construction of a complex object from its representation. This allows different representations to be created by using the same construction process. It is typically used when object initialization requires multiple steps or parameters that exceed constructor capabilities.

In Mybatis, SqlSessionFactoryBuilder orchestrates the environment setup. It invokes internal builders such as XMLConfigBuilder, which loads configuration files (mybatis-config.xml) and mapper XMLs. The XMLMapperBuilder then processes individual Mapper files, delegating to XMLStatementBuilder for SQL statement construction. Since reading configurations, parsing XML via XPath, generating reflection objects, and caching results involve numerous steps, the Builder approach prevents the explosion of constructors in Configuration. Typically, classes ending with Builder use methods prefixed with build to return sepcific products like SqlSessionFactory.

// Example conceptual structure of building logic
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(Configuration config) {
        // Logic to assemble the session factory based on parsed config
        return new DefaultSqlSessionFactory(config);
    }
}

2. Factory Pattern

Mybatis relies heavily on factories to create consistent instances. A simple factory creates products without requiring direct instantiation by the client.

The SqlSessionFactory interface exposes several overloaded openSession methods. These allow clients to define transaction isolation levels, execution types, and auto-commit settings without knowing the concrete implementation details. Inside DefaultSqlSessionFactory, the private method handling this logic fetches the environment configuration, instantiates a transaction via TransactionFactory, and requests a new Executor instance before constructing the final DefaultSqlSession.

private SqlSession openSessionInternal(final ExecutorType mode, final boolean flagAutoCommit) {
	Transaction txnInstance = null;
	try {
		final Environment envInfo = configuration.getEnvironment();
		final TransactionFactory txfactory = getTransactionFactoryFromEnvironment(envInfo);
		txnInstance = txfactory.newTransaction(envInfo.getDataSource(), null, flagAutoCommit);
		final Executor execService = configuration.newExecutor(txnInstance, mode);
		return new DefaultSqlSession(configuration, execService, flagAutoCommit);
	} catch (Exception e) {
		closeTransaction(txnInstance);
		throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
	} finally {
		ErrorContext.instance().reset();
	}
}

Additionally, LogFactory produces various logger implementations (e.g., Log4jImpl, Slf4jImpl) adhering to a common Log interface, demonstrating a factory producing polymorphic products.

3. Singleton Pattern

The Singleton pattern guarantees a single instance of a class per application scope. In Mybatis, two primary components use this mechanism: ErrorContext and LogFactory.

LogFactory acts as a global registry. However, ErrorContext demonstrates a thread-local singleton variation. Each thread maintains its own error context to prevent interference between concurrent operations. The instance retrieval checks if a thread-bound value exists; if not, it initializes one.

public class ErrorContext {
	private static final ThreadLocal LOCAL_STORAGE = new ThreadLocal();

	private ErrorContext() {}

	public static ErrorContext getInstance() {
		ErrorContext ctx = LOCAL_STORAGE.get();
		if (ctx == null) {
			ctx = new ErrorContext();
			LOCAL_STORAGE.set(ctx);
		}
		return ctx;
	}
}

4. Proxy Pattern

Dynamic proxies form the core of Mybatis's mapping capability. This allows users to write interface-only Mapper classes (UserMapper.java) while the framework executes the actual SQL queries behind the scenes.

When MapperRegistry.getMapper() is called, MapperProxyFactory generates a proxy. Internally, Java's Proxy.newProxyInstance creates an implementation of the InvocationHandler interface. The invoke method intercepts every method call made against the mapper interface. It maps the invoked method to a corresponding MappedStatement and delegates execution to the underlying executor.

public class MapperProxy implements InvocationHandler {
	@Override
	public Object executeCall(Object proxyObj, Method method, Object[] args) throws Throwable {
		try {
			if (Object.class.equals(method.getDeclaringClass())) {
				return method.invoke(this, args);
			}
			// Determine default method or standard mapping
			final MapperMethod mapperOperation = cachedMapperMethod(method);
			return mapperOperation.execute(sessionInstance, args);
		} catch (Throwable t) {
			throw ExceptionUtil.unwrapThrowable(t);
		}
	}
}

5. Composite Pattern

This pattern treats individual objects and compositions of objects uniformly. In Mybatis, Dynamic SQL uses a tree structure of SqlNode objects.

Nodes can be simple leaf elements (like text segments) or complex containers (like IfSqlNode). All nodes implement a common apply interface that operates on a DynamicContext. When processing dynamic SQL, the root node recursively applies to all children. If conditions are met (e.g., inside an <if> tag), child nodes are processed; otherwise, they are skipped.

public interface SqlComponent {
	boolean apply(DynamicContext dynCtx);
}

// Leaf Node
public boolean apply(DynamicContext ctx) {
	context.appendSql(parser.parse(text));
	return true;
}

// Container Node
public boolean apply(DynamicContext ctx) {
	if (evaluator.evaluateBoolean(testExpr, ctx.getBindings())) {
		contents.apply(ctx); // Recursion
		return true;
	}
	return false;
}

6. Template Method Pattern

This pattern defines the skeleton of an algorithm while deferring specific steps to subclasses. It promotes code reuse.

The BaseExecutor class handles generic lifecycle management of database interactions but leaves specific SQL fetching and updating to subclasses. Implementations include:

  • SimpleExecutor: Creates a new Statement for every query and closes it immediately.
  • ReuseExecutor: Caches Statement objects keyed by SQL to reuse them.
  • BatchExecutor: Accumulates SQL commands into batches for bulk processing.
protected abstract int performUpdate(MappedStatement ms, Object param) throws SQLException;

// SimpleExecutor Implementation
@Override
public int performUpdate(MappedStatement ms, Object param) throws SQLException {
	Statement dbStmt = null;
	try {
		Configuration cfg = ms.getConfiguration();
		StatementHandler handler = cfg.newStatementHandler(this, ms, param, RowBounds.DEFAULT, null, null);
		dbStmt = prepareStatement(handler, ms.getStatementLog());
		return handler.update(dbStmt);
	} finally {
		closeStatement(dbStmt);
	}
}

7. Adapter Pattern

Adapters convert interfaces of incompatible classes into ones clients expect. Mybatis decouples itself from logging libraries using a Log interface.

Regardless of whether a project uses Log4j, JDK Logger, or SLF4J, the LogFactory wraps the specific library's API behind the unified Log interface. For instance, Log4jImpl internally holds a reference to org.apache.log4j.Logger and translates calls to debug(String) or error(String, Throwable) into the respective log4j methods.

public class Log4jLogger implements Log {
	private static final String FQCN = Log4jLogger.class.getName();
	private Logger innerLog;

	public Log4jLogger(String className) {
		innerLog = Logger.getLogger(className);
	}

	@Override
	public void warn(String message) {
		logMessage(FQCN, Level.WARN, message);
	}

	@Override
	public void error(String msg) {
		logMessage(FQCN, Level.ERROR, msg);
	}
	// ... other mappings
}

8. Decorator Pattern

Decorators attach additional responsibilities to objects dynamically. This is more flexible than inheritance for adding features like synchronization or serialization to caching logic.

Mybatis cache hierarchy starts with PerpetualCache (the base storage). Various decorators wrap this base to add behaviors:

  • SynchronizedCache: Ensures thread safety.
  • LruCache: Evicts least recently used entries.
  • LoggingCache: Logs hit/miss rates.
  • SerializedCache: Handles object serialization.

The chain order determines precedence (e.g., Synchronized -> Logging -> Lru -> Perpetual).

9. Iterator Pattern

Iterators provide sequential access to container elements without exposing internal data structures. Mybatis utilizes this during property resolution.

The PropertyTokenizer class implements the Iterator interface to traverse nested properties specified in dot notation (e.g., address.city). It parses the string, extracting the current property name and advancing to the remaining path in subsequent calls to next(). This enables flexible handling of deeply nested objects during result mapping.

public class PropertyPath extends Iterator<PropertyPath> {
	private String propName;
	private String subPath;

	public PropertyPath(String fullName) {
		int delim = fullName.indexOf('.');
		if (delim > -1) {
			propName = fullName.substring(0, delim);
			subPath = fullName.substring(delim + 1);
		} else {
			propName = fullName;
			subPath = null;
		}
	}

	public boolean hasNext() {
		return subPath != null;
	}

	public PropertyPath next() {
		return new PropertyPath(subPath);
	}
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.