12
头图

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

Mybatis 框架源码10种设计模式

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!

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);
        }
    }

}

Mybatis 工厂模式

  • 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<>();
 
    //...
}

Mybatis 单例模式

  • 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
}

Mybatis 建造者模式

  • 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.
  • 同类场景SqlSessionFactoryBuilderXMLConfigBuilderXMLMapperBuilderXMLStatementBuilderCacheBuilder

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);
  }

}

Mybatis 适配器模式

  • 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);
        }
    }
    
    // ...

}

Mybatis 代理模式

  • 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>

Mybatis 组合模式

  • 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;
}

Mybatis 装饰器模式

  • 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);
    }
}

Mybatis 模板模式

  • 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);
    }

}

Mybatis 策略模式

  • 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);
        }
    }

        // ...

}

Mybatis 迭代器模式

  • 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.


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。