Mind mapping

Star: github.com/yehongzhi/l…

An overview of the

Mybatis is a relatively mainstream ORM framework, so I have a lot of contact with it in daily work. I like to look at the source code of a good framework, because the author who can write such a framework must be unique. If you can understand some clever ideas of the source code, it must be a great benefit.

All things are difficult at the beginning, look at the source code to find the point to cut. Design patterns are certainly a good place to start source code analysis, so without further ado, let’s get started.

The factory pattern

The factory pattern is a creation pattern. The factory pattern encapsulates the logic for creating objects, provides an interface for creating objects externally, and reduces the coupling between classes.

In Mybatis, the factory mode is mainly used in the DataSourceFactory. This is the factory responsible for creating the DataSource DataSource. DataSourceFactory is an interface with different subclass implementations that generate different DataSourceFactory implementation classes for different configurations. The class diagram is as follows:

DataSourceFactory = DataSourceFactory

public interface DataSourceFactory {
  
  void setProperties(Properties props);

  DataSource getDataSource(a);

}
Copy the code

The DataSourceFactory interface defines two abstract methods related to the type attribute of the dataSource tag.

<environment id="development">
    <transactionManager type="JDBC"/>
    <! -- Type attribute is the key attribute -->
    <dataSource type="POOLED">
        <property name="driver" value="${dataSource.driver}"/>
        <property name="url" value="${dataSource.url}"/>
        <property name="username" value="${dataSource.username}"/>
        <property name="password" value="${dataSource.password}"/>
    </dataSource>
</environment>
Copy the code

Mybatis has three types of built-in type configuration, respectively UNPOOLED, POOLED, JNDI.

UNPOOLED, the implementation of this data source simply opens and closes the connection each time it is requested.

POOLED, the idea of using “pools” for data sources, avoids the initialization and authentication time required to create new connection instances.

JNDI, a data source implemented for use in a container such as an EJB or application server, can centrally or externally configure the data source and then place a reference to the JNDI context.

Then look at the dataSourceElement() method of XMLConfigBuilder.

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if(context ! =null) {
        // Read the type attribute of the dataSource tag in the configuration file
        String type = context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        // According to the value of the type attribute, return different subclasses, using the interface DataSourceFactory receive, reflect the idea of interface oriented programming
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        // Set property values, such as database URL, username, password, etc
        factory.setProperties(props);
        / / back to the factory
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
Copy the code

Once you get the DataSourceFactory, you get the data source using the getDataSource() method, and you’re done.

private void environmentsElement(XNode context) throws Exception {
    if(context ! =null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        // The for loop is a configuration file that can be configured with multiple data sources
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                //dsFactory calls the getDataSource() method to create the dataSource object
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = newEnvironment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); }}}}Copy the code

Mybatis uses the factory model to create datasourceFactories. You can configure different datasourceFactories to create datasourceFactories, which is very flexible. There are many other places to use factory mode in Mybatis, such as SqlSessionFactory, which is not expanded here, you can explore it yourself.

The singleton pattern

The singleton pattern is the creative design pattern. This class provides a way to access its unique objects directly, without instantiating the objects of that class. Ensure that only a single object is created in the application.

The singleton pattern is used in many places in Mybatis. An example is the ErrorContext class. This is an execution environment error message that logs the thread, so it is a thread-wide singleton.

public class ErrorContext {
    // Use ThreadLocal to hold singleton objects in each thread
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    // Privatize constructor
    private ErrorContext(a) {}// Provide a unique interface to get a singleton
    public static ErrorContext instance(a) {
        // Fetch the context object from LOCAL
        ErrorContext context = LOCAL.get();
        if (context == null) {
            // If it is null, new one
            context = new ErrorContext();
            // Add it to LOCAL
            LOCAL.set(context);
        }
        // If not null, return directly
        returncontext; }}Copy the code

Builder model

The Builder pattern is also a creation pattern and is mainly used when creating a complex object, separating the construction of a complex object from its representation by constructing the final object step by step.

The place where the builder pattern is used in Mybatis is in the ParameterMapping class, which is a class for ParameterMapping.

public class ParameterMapping {
  private Configuration configuration;
  private String property;
  private ParameterMode mode;
  privateClass<? > javaType = Object.class;private JdbcType jdbcType;
  private Integer numericScale;
  privateTypeHandler<? > typeHandler;private String resultMapId;
  private String jdbcTypeName;
  private String expression;
  
  // Privatize constructor
  private ParameterMapping(a) {}// Create an object from the inner class Builder
  public static class Builder {
      Initializes the ParameterMapping instance
      private ParameterMapping parameterMapping = new ParameterMapping();
      // Initialize some of the member variables of ParameterMapping through the constructor
      public Builder(Configuration configuration, String property, TypeHandler
        typeHandler) {
          parameterMapping.configuration = configuration;
          parameterMapping.property = property;
          parameterMapping.typeHandler = typeHandler;
          parameterMapping.mode = ParameterMode.IN;
      }

      public Builder(Configuration configuration, String property, Class
        javaType) {
          parameterMapping.configuration = configuration;
          parameterMapping.property = property;
          parameterMapping.javaType = javaType;
          parameterMapping.mode = ParameterMode.IN;
      }
      // Set the mode for parameterMapping
      public Builder mode(ParameterMode mode) {
          parameterMapping.mode = mode;
          return this;
      }
      // Set the javaType of parameterMapping
      public Builder javaType(Class
        javaType) {
          parameterMapping.javaType = javaType;
          return this;
      }
      // Set the jdbcType for parameterMapping
      public Builder jdbcType(JdbcType jdbcType) {
          parameterMapping.jdbcType = jdbcType;
          return this;
      }
      // Set the numericScale of parameterMapping
      public Builder numericScale(Integer numericScale) {
          parameterMapping.numericScale = numericScale;
          return this;
      }
      // Set the resultMapId of parameterMapping
      public Builder resultMapId(String resultMapId) {
          parameterMapping.resultMapId = resultMapId;
          return this;
      }
      // Sets typeHandler for parameterMapping
      public Builder typeHandler(TypeHandler
        typeHandler) {
          parameterMapping.typeHandler = typeHandler;
          return this;
      }
      // Set jdbcTypeName for parameterMapping
      public Builder jdbcTypeName(String jdbcTypeName) {
          parameterMapping.jdbcTypeName = jdbcTypeName;
          return this;
      }
      // Set the expression of parameterMapping
      public Builder expression(String expression) {
          parameterMapping.expression = expression;
          return this;
      }
      // Create the object with the build() method, return
      public ParameterMapping build(a) {
          resolveTypeHandler();
          validate();
          returnparameterMapping; }}}Copy the code

We can see the Builder pattern in action in the buildParameterMapping() method of the SqlSourceBuilder class:

// Build a parameterMapping instance based on the content parameter
private ParameterMapping buildParameterMapping(String content) {
    // Map collection of attribute values
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property"); Class<? > propertyType;/ / to omit...
    Create a parameterMapping.Builder object
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
	/ / to omit...
    // This iterates through the Map collection, sets the property values to ParameterMapping, and creates a ParameterMapping object
    for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
            javaType = resolveClass(value);
            builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
            builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
            builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
            builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
            builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
            typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
            builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
            // Do Nothing
        } else if ("expression".equals(name)) {
            Expression based parameters are not supported yet
        } else {
            // Throw an exception}}/ / to omit...
    // Create a ParameterMapping object and return
    return builder.build();
}
Copy the code

Template pattern

The template pattern is a behavioral pattern used in more general ways, defining an abstract class, writing the skeleton of an algorithm, and deferring some steps to subclasses. Just like a leave of absence note, the beginning and end are written templates, and the reason for leave (content) in the middle is completed by the person (subclass) to improve code reuse.

In Mybatis, the template pattern is embodied in the Executor and BaseExecutor classes. Let’s start with a class diagram:

Executor is an interface that, by its name, is an object used to execute SQL statements. There is an abstract BaseExecutor class below that defines the template methods. Below that, there are three implementation classes, SimpleExecutor(SimpleExecutor), ReuseExecutor(ReuseExecutor), and BatchExecutor(BatchExecutor). The implementation class is used to fill in the middle of the template.

The executors often perform many of the same tasks before and after performing JDBC operations, such as using caches for queries, clearing caches for updates, and so on, making the template pattern a good fit.

Then we look at the source code for the BaseExecutor abstract class and see that it defines a skeleton.

public abstract class BaseExecutor implements Executor {
    // Query operation
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        // This code is fixed
        try {
            // Different subclasses of this code have different implementations, so it calls the abstract method doQuery().
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        // The following code is also fixed
        } finally {
            // Clear the cache
            localCache.removeObject(key);
        }
        // Add to the cache
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        // Return the result
        return list;
    }
    
    // Abstract methods implemented by subclasses
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
        throws SQLException;
}
Copy the code

Then there is the question of which subclass implementation is used by default if none is set in the first place. Very simple, directly follow the source code to follow the trail, we see.

public class Configuration {
    // The default is SIMPLE, which is SimpleExecutor
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
}
Copy the code

In fact, the template pattern is very simple to “recognize”, my understanding is that the abstract class definition (concrete methods), concrete methods to extract (abstract methods). That’s probably a template pattern.

The proxy pattern

The proxy pattern is a structural pattern, defined abstractly to provide a proxy for other objects to control access to that object. In some cases, an object is inappropriate or cannot directly reference another object, and a proxy object can act as an intermediary between the client and the target object.

Actually proxy mode can be as simple as intermediary role, such as the landlord only concerned about the rent, other utilities and settlement, take people to see these miscellaneous collection of things he doesn’t want to care about, hand over to the mediation (secondary) east, tenant to rent it to Qian Zhongjie, the landlord money hand seek mediation, the mediation is the so-called agents.

SqlSession is a session object to operate the database. Our users generally use SqlSession to add, delete, change and check, but if the transaction is opened and closed every time to add, delete and change, it is obviously very troublesome. So the proxy class can do the job, and if the transaction is not started, the proxy class automatically starts the transaction.

Mybatis uses JDK dynamic proxy here, so SqlSession is an interface.

public interface SqlSession extends Closeable {
    
    <T> T selectOne(String statement);

    <E> List<E> selectList(String statement);

    <E> List<E> selectList(String statement, Object parameter);

    <K, V> Map<K, V> selectMap(String statement, String mapKey);

    <T> Cursor<T> selectCursor(String statement);

    void select(String statement, Object parameter, ResultHandler handler);

    int insert(String statement);

    int update(String statement);

    int delete(String statement);

    void commit(a);

    void rollback(a);
    / / to omit...
}
Copy the code

We know that a dynamic proxy has a class that implements the InvocationHandler interface. This class, in SqlSessionManager, is an internal class called SqlSessionInterceptor, which does the relevant processing in this class.

private class SqlSessionInterceptor implements InvocationHandler {
    public SqlSessionInterceptor(a) {
        // Prevent Synthetic Access
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        LocalSqlSession is a ThreadLocal, so each thread has its own SqlSession
        final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
        // If not null
        if(sqlSession ! =null) {
            try {
                // Execute the method and return the result
                return method.invoke(sqlSession, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
            // If SQLSession is null
        } else {
            / / open the session
            final SqlSession autoSqlSession = openSession();
            try {
                // Execute the Sqlsession method to get the result
                final Object result = method.invoke(autoSqlSession, args);
                //commit Commits the transaction
                autoSqlSession.commit();
                // Return the result
                return result;
            } catch (Throwable t) {
                Rollback Rollback the transaction
                autoSqlSession.rollback();
                // Throw an exception
                throw ExceptionUtil.unwrapThrowable(t);
            } finally {
                / / close the sqlsessionautoSqlSession.close(); }}}}Copy the code

The constructor is then initialized to provide static methods to return the SqlSessionManager instance, see the source code.

// Implement SqlSessionFactory, SqlSession interface
public class SqlSessionManager implements SqlSessionFactory.SqlSession {

    private final SqlSessionFactory sqlSessionFactory;
    // sqlSessionProxy
    private final SqlSession sqlSessionProxy;

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        // Initialize the proxy class
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[]{SqlSession.class},
            new SqlSessionInterceptor());
    }
    // Provide a static method for external access to the SqlSessionManager class, which can be received using SqlSession
    public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionManager(sqlSessionFactory);
    }
    
    // Override the selectOne method to use the proxy class to execute it
    @Override
    public <T> T selectOne(String statement) {
        return sqlSessionProxy.<T> selectOne(statement);
    }
    
    // Override the insert method to use the proxy class to execute it
    @Override
    public int insert(String statement) {
        return sqlSessionProxy.insert(statement);
    }
    / / to omit...
}
Copy the code

This is an example of Mybatis using proxy mode, it is not very complicated, or can understand.

But the above method is rarely used, we generally use Mapper interface, in fact, Mapper interface is also used in proxy mode, and we will continue to see. Look directly at the MapperProxy class.

// The InvocationHandler interface is implemented
public class MapperProxy<T> implements InvocationHandler.Serializable {

    private static final long serialVersionUID = -6424540398559729838L;
    private final 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;
    }
	// What does the proxy class do, see this method
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		/ / to omit...
        // Get the mapperMethod object
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        // Execute method. Execute according to the configuration of the mapper. XML configuration file and return the result
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        // Get mapperMethod from methodCache
        MapperMethod mapperMethod = methodCache.get(method);
        / / null
        if (mapperMethod == null) {
            / / the new one. Some of the configuration of mapper.xml has been encapsulated here
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            // Then put into methodCache
            methodCache.put(method, mapperMethod);
        }
        / / return mapperMethod
        returnmapperMethod; }}Copy the code

The execute() method of mapperMethod is used to find the SQL Id of the mapper. XML file based on the method name of the Mapper interface, perform the corresponding operation, and return the result. The internal code is a little bit longer, but that’s the idea, so I won’t expand it here.

So the TbCommodityInfoMapper interface assumes that we define a list() method like this.

public interface TbCommodityInfoMapper {
    // Query the TbCommodityInfo list
    List<TbCommodityInfo> list(a);
}
Copy the code

In the TbCommodityInfoMapper. XML is due to an id for the list of configuration.

<! The namespace must also match the fully qualified name of the interface.
<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <! Select * from SQL where id = 'list';
    <select id="list" resultType="tbCommodityInfo">
        select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
    </select>
</mapper>
Copy the code

Then in the MapperProxyFactory class, the factory pattern is used to provide the fetch proxy class.

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
	// Initialize mapperInterface with the constructor
    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @SuppressWarnings("unchecked")
    protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
    }
	
    // Get the proxy class of the Mapper interface using this method
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        returnnewInstance(mapperProxy); }}Copy the code

So this getMapper method is called in a subclass of SqlSession.

@Override
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}
Copy the code

This is how the end user uses it. Here is pure Mybatis with no integrated Spring writing.

public class Test {
    public static void main(String[] args) {
        // Get the SqlSession object
        SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
        // Get the proxy class of the Mapper interface
        TbCommodityInfoMapper tbCommodityInfoMapper = sqlSession.getMapper(TbCommodityInfoMapper.class);
        // Use the proxy class to execute the corresponding methodList<TbCommodityInfo> commodityInfoList = tbCommodityInfoMapper.list(); }}Copy the code

Therefore, the proxy mode can be said to be the core design mode of Mybatis, which is very clever.

Decorator mode

The decorator pattern is a structural pattern that wraps objects with decorator classes, dynamically adding some extra responsibility to them. Where will decorator mode be used in Mybatis?

Yes, cache. Mybatis has level-1 cache and Level-2 cache functions. Level-1 cache is enabled by default and ranges take effect in SqlSession. Level-2 cache needs to be manually enabled and ranges are configured in global Configuration and in each namespace. For details about the types of level-2 cache, see Configuration.

<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <! MyBatis is designed to exclude cache reclamation policies from any design design. (1) LRU, the least recently used object, and the most recently used object. (2) FIFO, which removes objects in the order in which they enter the cache. (3) SOFT, which removes objects based on garbage collector status and SOFT reference rules. More aggressively remove objects based on garbage collector state and weak reference rules. FlushInterval: specifies the flushInterval in milliseconds. If you do not flushInterval, the cache will be flushed only when the SQL is executed. Size: indicates the number of references. It is a positive integer. It indicates the maximum number of objects that can be stored in the cache. Too many Settings will cause memory overflow. ReadOnly: 1024 objects readOnly: 1024 objects readOnly: 1024 objects readOnly: 1024 objects readOnly: 1024 objects readOnly: 1024 objects readOnly: 1024 objects
    <cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
    
    <select id="list" resultMap="base_column">
        select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
    </select>
</mapper>
Copy the code

So the scenario is pretty clear, which is to add a second level cache on top of the first level cache, which is consistent with the decorator pattern, dynamically adding responsibilities to objects. To do this, let’s first look at the class diagram of the Cache class.

Consider the Cache interface, which defines some interface methods.

public interface Cache {

    String getId(a);

    void putObject(Object key, Object value);

    Object getObject(Object key);

    Object removeObject(Object key);

    void clear(a);

    int getSize(a);

    ReadWriteLock getReadWriteLock(a);
}
Copy the code

And then the PerpetualCache class, which is the basic implementation of the Cache interface, that’s where the level two Cache is going to be extended and then wrapped up to do that. It’s just a HashMap, wrapped up a little bit.

public class PerpetualCache implements Cache {

    private final String id;
	// Member variable cache, create a HashMap
    private Map<Object, Object> cache = new HashMap<Object, Object>();

    public PerpetualCache(String id) {
        this.id = id;
    }

    @Override
    public String getId(a) {
        return id;
    }

    @Override
    public int getSize(a) {
        return cache.size();
    }

    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear(a) {
        cache.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock(a) {
        return null;
    }
	/ / to omit...
}
Copy the code

So let’s look at LruCache, a representative class for level 2 caching.

public class LruCache implements Cache {
	// Level 1 cache, stored in this member variable
    private final Cache delegate;
    // This is actually a LinkedHashMap, using the LinkedHashMap LRU algorithm to implement the cache LRU
    private Map<Object, Object> keyMap;
    private Object eldestKey;
	// The constructor caches the object and initializes it
    public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
    }

	// Initialize keyMap and override removeEldestEntry to implement LUR
    public void setSize(final int size) {
        keyMap = new LinkedHashMap<Object, Object>(size, .75F.true) {
            private static final long serialVersionUID = 4267176411845948333L;

            @Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
                boolean tooBig = size() > size;
                // eldestKey is the oldest key when put in
                if (tooBig) {
                    eldestKey = eldest.getKey();
                }
                returntooBig; }}; }@Override
    public void putObject(Object key, Object value) {
        // Save to cache
        delegate.putObject(key, value);
        // Delete values that are not commonly used
        cycleKeyList(key);
    }

    @Override
    public Object getObject(Object key) {
        keyMap.get(key); //touch
        return delegate.getObject(key);
    }
    
    private void cycleKeyList(Object key) {
        keyMap.put(key, key);
        if(eldestKey ! =null) {
            // Delete the oldest key in the Map
            delegate.removeObject(eldestKey);
            eldestKey = null; }}}Copy the code

The key is the member variable delegate, which is defined in the other tier 2 cache decorator classes. This is to keep PerpetualCache, the base cache class. So this means that level 2 caching is PerpetualCache based on PerpetualCache. It becomes even clearer as we move on.

Look directly at the Builder method that creates the SqlSessionFactory and track it down to the useNewCache method of the MapperBuilderAssistant class.

public class MapperBuilderAssistant extends BaseBuilder {
	// The current mapper.xml namespace
    private String currentNamespace;

    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties props) {
        Cache cache = new CacheBuilder(currentNamespace)
            // Set the base cache
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            // Add the cache decorator class
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            // Create a cache
            .build();
        // Add the cache to configuration, so the level 2 cache is in the configuration range
        configuration.addCache(cache);
        currentCache = cache;
        returncache; }}Copy the code

Looking at the Build method of the CacheBuilder, it becomes even clearer.

public class CacheBuilder {
    
    private Class<? extends Cache> implementation;
    private final List<Class<? extends Cache>> decorators;
    
    public Cache build(a) {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        if (PerpetualCache.class.equals(cache.getClass())) {
            // Walk through the decorator class
            for (Class<? extends Cache> decorator : decorators) {
                // Add level 2 cache to cache
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            cache = setStandardDecorators(cache);
        } else if(! LoggingCache.class.isAssignableFrom(cache.getClass())) { cache =new LoggingCache(cache);
        }
        // return cache
        return cache;
    }

    private void setDefaultImplementations(a) {
        if (implementation == null) {
            // If empty, initialize to PerpetualCache
            implementation = PerpetualCache.class;
            If the decorator class is empty, LruCache is used by default
            if(decorators.isEmpty()) { decorators.add(LruCache.class); }}}}Copy the code

So we can kind of imagine that level 2 caching is like a thousand layer cake, wrapped layer by layer. Finally, debug mode validates.

When you execute the query, you will see that it is really a thousand layer cake, layer by layer. Look at the Query method of the CachingExecutor class.

Finally, when the Cache putObject method is called, it is called layer by layer from the outside to the inside, implementing the decorator pattern of dynamically extending the function of the object.

Decorator mode in the application of Mybatis here, what do not understand, you can search the public number Java technology enthusiasts, plus my wechat questions.

conclusion

This article introduces 6 design patterns used in Mybatis, which are factory pattern, singleton pattern, template pattern, Builder pattern, agent pattern, and decorator pattern. In fact, Mybatis in addition to what I said, there are many I did not mention, such as combination mode, adapter mode and so on, interested in their own to study it.

Because now a lot of interviews are always asked to have seen the source code of what framework, in fact, it is good to see the source code, but can not blindly start, because a lot of framework is the use of a large number of design patterns, if there is no certain understanding of the design pattern, it is easy to read not understand, see mengyi. So there is no design mode based students, it is recommended to look at the design mode first, and then to learn the source code, so as to gradually improve their own strength.

That’s all for this article. Thank you for reading.

Please give me a thumbs-up if you think it is useful. Your thumbs-up is the biggest motivation for my creation

I’m a programmer who tries to be remembered. See you next time!!

Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!