Author: Brother Xiaofu
<br/>Blog: https://bugstack.cn
Precipitate, share, grow, and let yourself and others gain something! 😄
1. Foreword: Small Town Volume Code Home
There are always many R&D partners who ask Brother Fu, "Why do you learn design patterns, read the source code of the framework, and supplement technical knowledge, just for an ordinary business project, isn't it also writing CRUD every day to build an airplane?"
You're right, but you write CRUD every day, do you find it annoying? Are you worried? Are you worried that you will not get technical growth, and that you will not be able to use these CRUD projects to participate in the future? When you report, get promoted, defend, or even be forced to interview, you don’t have any dry goods in your hands.
So you/I, as a small-town coder, must of course expand their knowledge reserves, otherwise 架构,架构思维不懂
, 设计,设计模式不会
, 源码、源码学习不深
, and finally use a bunch of Does CRUD write a resume?
2. Source code: learn design patterns
In the implementation of Mybatis's more than 20,000 lines of framework source code, a large number of design patterns are used to decouple the design of complex scenarios in the engineering architecture. These are the ingenious use of design patterns that are the essence of the entire framework. An important reason to like volume source code . After Xiao Fu's arrangement, there are the following 10 design patterns, as shown in the figure
To be reasonable, if you just memorize these 10 design patterns and wait for the next interview to talk about it, although it can be a little helpful, this way of learning really narrows the road. Just like every time you say a design pattern, can you think of the source code implementation in which process this design pattern is reflected in the framework of Mybatis? Can this idea of source code implementation be used in your business process development? Don't always say that your process is simple, you can't use design patterns! Is it difficult to not take the exam because of the wealth and wealth of the second generation? 🤔
Okay, let’s not talk nonsense. Next, Brother Fu will use the study of “Handwriting Mybatis: Progressive Source Code Practice” to list these 10 design patterns for you, and where are they reflected in the Mybatis framework!
- This article corresponds to the source code repository : https://gitcode.net/KnowledgePlanet/doc/-/wikis/home
3. Type: Creation Mode
1. Factory pattern
See the source code for details : cn.bugstack.mybatis.session.SqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
}
See the source code for details : cn.bugstack.mybatis.session.defaults.DefaultSqlSessionFactory
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = environment.getTransactionFactory();
tx = transactionFactory.newTransaction(configuration.getEnvironment().getDataSource(), TransactionIsolationLevel.READ_COMMITTED, false);
// 创建执行器
final Executor executor = configuration.newExecutor(tx);
// 创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor);
} catch (Exception e) {
try {
assert tx != null;
tx.close();
} catch (SQLException ignore) {
}
throw new RuntimeException("Error opening session. Cause: " + e);
}
}
}
- Factory Pattern : Simple Factory is a creational design pattern that provides a method for creating objects in the parent class, allowing subclasses to determine the type of instance objects.
- Scenario introduction :
SqlSessionFactory
is the factory for obtaining the session. Every time we use Mybatis to operate the database, a new session will be opened. In the implementation of the session factory, it is responsible for obtaining the configuration information of the data source environment, constructing the transaction factory, creating an executor for operating SQL, and finally returning the session implementation class. - Similar designs :
SqlSessionFactory
,ObjectFactory
,MapperProxyFactory
,DataSourceFactory
2. Singleton pattern
See the source code : cn.bugstack.mybatis.session.Configuration
public class Configuration {
// 缓存机制,默认不配置的情况是 SESSION
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
// 映射注册机
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
// 映射的语句,存在Map里
protected final Map<String, MappedStatement> mappedStatements = new HashMap<>();
// 缓存,存在Map里
protected final Map<String, Cache> caches = new HashMap<>();
// 结果映射,存在Map里
protected final Map<String, ResultMap> resultMaps = new HashMap<>();
protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>();
// 插件拦截器链
protected final InterceptorChain interceptorChain = new InterceptorChain();
// 类型别名注册机
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
// 类型处理器注册机
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
// 对象工厂和对象包装器工厂
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected final Set<String> loadedResources = new HashSet<>();
//...
}
- Singleton pattern : is a creational pattern that allows you to guarantee that a class has only one instance and provide a global node to access that instance.
- Scenario introduction : Configuration is a single instance like dog skin plaster, which runs through the life cycle of the entire session, so the configuration objects; mapping, caching, input parameters, output parameters, interceptors, registration machines, object factories, etc., are all in the Configuration configuration item initialization. And with the SqlSessionFactoryBuilder construction phase to complete the instantiation operation.
- Similar scenes :
ErrorContext
,LogFactory
,Configuration
3. Builder Mode
See the source code for details : cn.bugstack.mybatis.mapping.ResultMap#Builder
public class ResultMap {
private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private Set<String> mappedColumns;
private ResultMap() {
}
public static class Builder {
private ResultMap resultMap = new ResultMap();
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
resultMap.id = id;
resultMap.type = type;
resultMap.resultMappings = resultMappings;
}
public ResultMap build() {
resultMap.mappedColumns = new HashSet<>();
// step-13 新增加,添加 mappedColumns 字段
for (ResultMapping resultMapping : resultMap.resultMappings) {
final String column = resultMapping.getColumn();
if (column != null) {
resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
}
}
return resultMap;
}
}
// ... get
}
- Builder pattern : Use multiple simple objects to build a complex object step by step. This type of design pattern is a creational pattern, which provides an optimal way to create objects.
- Scenario introduction : Regarding the use of the builder mode in the Mybatis framework, it was really a screen wiper, and you missed a hand. XxxxBuilder is everywhere, all about the parsing of XML files to the encapsulation of various objects, all use the builder and the builder assistant to complete the object encapsulation. Its core purpose is not to set too many properties of objects and write them into other business processes, but to provide the best boundary isolation in the way of builders.
- 同类场景:
SqlSessionFactoryBuilder
、XMLConfigBuilder
、XMLMapperBuilder
、XMLStatementBuilder
、CacheBuilder
4. Type: Structural Pattern
1. Adapter Mode
See the source code : cn.bugstack.mybatis.logging.Log
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);
}
See the source code for details : cn.bugstack.mybatis.logging.slf4j.Slf4jImpl
public class Slf4jImpl implements Log {
private Log log;
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException e) {
// fail-back to Slf4jLoggerImpl
} catch (NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
log = new Slf4jLoggerImpl(logger);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
log.error(s, e);
}
@Override
public void error(String s) {
log.error(s);
}
@Override
public void debug(String s) {
log.debug(s);
}
@Override
public void trace(String s) {
log.trace(s);
}
@Override
public void warn(String s) {
log.warn(s);
}
}
- Adapter pattern : is a structural design pattern that enables objects with incompatible interfaces to cooperate with each other.
- Scenario introduction : It is precisely because there are too many log frameworks, including: Log4j, Log4j2, Slf4J, etc., and the use interfaces of these log frameworks are different. In order to unify the interfaces of these logging tools, Mybatis defines a unified set of The log interface for all other log tool interfaces is adapted accordingly.
- Similar scenarios : mainly focus on the adaptation of the log, Log and the corresponding implementation class, and use in the LogFactory factory method.
2. Proxy mode
See the source code for details : cn.bugstack.mybatis.binding.MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
// ...
}
- Proxy pattern : is a structural pattern that allows you to provide a substitute for an object or its placeholder. The proxy controls access to the original object and allows some processing before submitting the request to the object.
- Scenario introduction : Without bragging, without the proxy mode, there will be no various frameworks. Just like the MapperProxy mapper proxy implementation class in Mybatis, its function is to help us complete the method operation of the specific implementation class of the DAO interface. The CRUD method called by any of your configured DAO interfaces will be taken over by MapperProxy. A series of operations are called to the method executor, and the final database execution result is returned.
- Similar scenarios :
DriverProxy
,Plugin
,Invoker
,MapperProxy
3. Combination mode
See the source code for details : cn.bugstack.mybatis.scripting.xmltags.SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
See the source code for details : cn.bugstack.mybatis.scripting.xmltags.IfSqlNode
public class IfSqlNode implements SqlNode{
private ExpressionEvaluator evaluator;
private String test;
private SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 如果满足条件,则apply,并返回true
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
See the source code for details : cn.bugstack.mybatis.scripting.xmltags.XMLScriptBuilder
public class XMLScriptBuilder extends BaseBuilder {
private void initNodeHandlerMap() {
// 9种,实现其中2种 trim/where/set/foreach/if/choose/when/otherwise/bind
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("if", new IfHandler());
}
List<SqlNode> parseDynamicTags(Element element) {
List<SqlNode> contents = new ArrayList<>();
List<Node> children = element.content();
for (Node child : children) {
if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
} else if (child.getNodeType() == Node.ELEMENT_NODE) {
String nodeName = child.getName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new RuntimeException("Unknown element " + nodeName + " in SQL statement.");
}
handler.handleNode(element.element(child.getName()), contents);
isDynamic = true;
}
}
return contents;
}
// ...
}
For configuration details : resources/mapper/Activity_Mapper.xml
<select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap" flushCache="false" useCache="true">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
<if test="null != activityId">
activity_id = #{activityId}
</if>
</trim>
</select>
- Composition Pattern : is a structural design pattern that you can use to combine objects into a tree-like structure and use them as if they were separate objects.
- Scenario introduction : In Mybatis XML dynamic SQL configuration, a total of 9 tags (trim/where/set/foreach/if/choose/when/otherwise/bind) are provided, allowing users to combine various scenarios SQL statement. The implementation of the SqlNode interface is the rule node in each combination structure, and the use of a rule tree combination mode is completed through the assembly of the rule node. For specific use of source code, you can read "Handwritten Mybatis: Progressive Source Code Practice"
- Similar scenarios : It is mainly reflected in the analysis of various SQL tags, mainly implementing various subclasses of the SqlNode interface.
4. Decorator pattern
See the source code for details : cn.bugstack.mybatis.session.Configuration
public Executor newExecutor(Transaction transaction) {
Executor executor = new SimpleExecutor(this, transaction);
// 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰者模式
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
return executor;
}
- Decorator pattern : is a structural design pattern that allows you to bind new behavior to the original object by placing the object in a special wrapper object that contains the behavior.
- Scenario introduction : All SQL operations of Mybatis are completed by calling SimpleExecutor through the SqlSession session, and the operations of the first-level cache are also processed in the simple executor. Then the second-level cache here is based on the first-level cache refresh operation, so in terms of implementation, by creating a cache executor and wrapping the processing logic of the simple executor, the second-level cache operation is realized. So what is used here is the decorator pattern, also known as the Russian nesting doll pattern.
- Similar scenarios : mainly in the implementation of the Cache interface and the CachingExecutor in advance.
5. Type: Behavioral Model
1. Template mode
See the source code for details : cn.bugstack.mybatis.executor.BaseExecutor
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
if (closed) {
throw new RuntimeException("Executor was closed.");
}
// 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 根据cacheKey从localCache中查询数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list == null) {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
See the source code for details : cn.bugstack.mybatis.executor.SimpleExecutor
protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 新建一个 StatementHandler
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 准备语句
stmt = prepareStatement(handler);
// StatementHandler.update
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
- Template Pattern : is a behavioral design pattern that defines the framework of an algorithm in a superclass, allowing subclasses to override specific steps of the algorithm without modifying the structure.
- Scenario introduction : As long as there is a series of processes that can be defined by standard, most of the steps in the process are general logic, and only a small part needs to be implemented by subclasses, then the template pattern is usually used to define this standard process. Just like the BaseExecutor of Mybatis is an abstract class used to define the template mode, in this class, a set of standard processes are defined for query and modification operations.
- Similar scenes :
BaseExecutor
,SimpleExecutor
,BaseTypeHandler
2. Strategy Mode
See the source code for details : cn.bugstack.mybatis.type.TypeHandler
public interface TypeHandler<T> {
/**
* 设置参数
*/
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
/**
* 获取结果
*/
T getResult(ResultSet rs, String columnName) throws SQLException;
/**
* 取得结果
*/
T getResult(ResultSet rs, int columnIndex) throws SQLException;
}
See the source code : cn.bugstack.mybatis.type.LongTypeHandler
public class LongTypeHandler extends BaseTypeHandler<Long> {
@Override
protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {
ps.setLong(i, parameter);
}
@Override
protected Long getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getLong(columnName);
}
@Override
public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getLong(columnIndex);
}
}
- Strategy Pattern : It is a behavioral design pattern that defines a series of algorithms and puts each algorithm into a separate class, so that the objects of the algorithm can be replaced with each other.
- Scenario introduction : When Mybatis processes the results returned after JDBC execution, it needs to obtain corresponding values according to different types, so that a large number of if judgments can be avoided. Therefore, based on the TypeHandler interface, each parameter type has its own strategy implementation.
- Similar scenarios :
PooledDataSource\UnpooledDataSource
,BatchExecutor\ResuseExecutor\SimpleExector\CachingExecutor
,LongTypeHandler\StringTypeHandler\DateTypeHandler
3. Iterator pattern
See the source code for details : cn.bugstack.mybatis.reflection.property.PropertyTokenizer
public class PropertyTokenizer implements Iterable<PropertyTokenizer>, Iterator<PropertyTokenizer> {
public PropertyTokenizer(String fullname) {
// 班级[0].学生.成绩
// 找这个点 .
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);
}
}
// ...
}
- Iterator pattern : is a behavioral design pattern that allows you to iterate over all elements in a collection without exposing the underlying representation of the collection.
- Scenario introduction : PropertyTokenizer is an iterative operation for parsing object relationships under the MetaObject reflection toolkit of the Mybatis framework. This class is used very frequently in the Mybatis framework, including parsing the data source configuration information and filling it into the data source class, as well as parameter parsing and object setting.
- Similar scene :
PropertyTokenizer
6. Summary: The experience of "The King of Volumes"
It may take 1-2 months for a systematic dismantling and progressive learning of a source code, which takes more experience than Shuangwen and tired of taking the test. But you will always build a complete system of technical architecture about this kind of knowledge in your mind after you finish learning in a large chunk of time. No matter where you enter from, you can know the direction of each branch process, which is also your Deep learning on the road to becoming a technologist.
If you also want to have such a hearty study, don't miss the information "Handwritten Mybatis: Progressive Source Code Practice" written by Brother Fu for you. The directory is shown in the figure, with a total of 20 chapters.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。