数据库-MySQL-Mybatis源码解析-设计模式角度

news2024/11/27 0:14:13

文章目录

  • 前言
  • 一、工厂模式
  • 二、单例模式
  • 三、建造者模式
  • 四、模板模式
  • 五、代理模式
  • 六、装饰器模式
  • 七、总结


前言

Mybatis是一个比较主流的ORM框架,所以在日常工作中接触得很多。能写出这种框架的作者肯定有其独特之处。阅读优秀框架的源码,如果能看懂些巧妙构思,受益匪浅。

所谓万事开头难,看源码也要找到切入的点。设计模式无疑是源码分析一个很好的切入点。

一、工厂模式

工厂模式属于创建型模式。工厂模式的作用是把创建对象的逻辑封装起来,提供一个接口给外部创建对象,降低类与类之间的耦合。

在Mybatis中,用到工厂模式主要在DataSourceFactory。这是一个负责创建DataSource数据源的工厂。DataSourceFactory是一个接口,有不同的子类实现,根据不同的配置,生成不同的DataSourceFactory实现类。类图如下:
Mybatis-DataSourceFactory

接着我们看一下DataSourceFactory的源码:

public interface DataSourceFactory {
  
  void setProperties(Properties props);

  DataSource getDataSource();

}

DataSourceFactory接口定义了两个抽象方法,怎么工作的呢,其实是跟dataSource标签的属性type有关。

<environment id="development">
    <transactionManager type="JDBC"/>
    <!-- type属性是关键属性 -->
    <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>

Mybatis内置的type有三种配置,分别是UNPOOLED,POOLED,JNDI。

UNPOOLED,这个数据源的实现只是每次被请求时打开和关闭连接。

POOLED,这种数据源的使用“池“的思想,避免了创建新的连接实例时所必需的初始化和认证时间。

JNDI,这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

接着看XMLConfigBuilder的dataSourceElement()方法。

private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
        //读取配置文件中dataSource标签的属性type
        String type = context.getStringAttribute("type");
        Properties props = context.getChildrenAsProperties();
        //根据type属性的值,返回不同的子类,使用接口DataSourceFactory接收,体现了面向接口编程的思想
        DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
        //设置属性值,比如数据库的url,username,password等等
        factory.setProperties(props);
        //返回factory
        return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

获得DataSourceFactory之后,就通过getDataSource()方法获取数据源,完事了。

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            environment = context.getStringAttribute("default");
        }
        //这里for循环主要是配置文件可以配置多个数据源
        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调动getDataSource()方法,创建dataSource对象
                DataSource dataSource = dsFactory.getDataSource();
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                    .transactionFactory(txFactory)
                    .dataSource(dataSource);
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

Mybatis使用工厂模式来创建DataSourceFactory,可以做到通过配置去使用不同的DataSourceFactory创建DataSource,非常灵活。在Mybatis中使用到工厂模式还有很多地方,比如SqlSessionFactory,这里就不再展开了,有兴趣的可以自己探索一下。

二、单例模式

单例模式属于创建型设计模式,这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。保证在应用中只有单一对象被创建

Mybatis中用到单例模式的地方有很多,这里举个例子是ErrorContext类。这是一个用于记录该线程的执行环境错误信息,所以是在线程范围内的单例。

public class ErrorContext {
    //使用ThreadLocal保存每个线程中的单例对象
    private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
    //私有化构造器
    private ErrorContext() {
    }
    //向外提供唯一的接口获取单例对象
    public static ErrorContext instance() {
        //从LOCAL中取出context对象
        ErrorContext context = LOCAL.get();
        if (context == null) {
            //如果为null,new一个
            context = new ErrorContext();
            //放入到LOCAL中保存
            LOCAL.set(context);
        }
        //如果不为null,直接返回
        return context;
    }
}

三、建造者模式

建造者模式也是属于创建型模式,主要是在创建一个复杂对象时使用,通过一步一步构造最终的对象,将一个复杂对象的构建与它的表示分离。

在Mybatis中,使用到建造者模式的地方体现在ParameterMapping类,这是用于参数映射的一个类。

public class ParameterMapping {
  private Configuration configuration;
  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
  
  //私有化构造器
  private ParameterMapping() {
  }
  
  //通过内部类Builder创建对象
  public static class Builder {
      //初始化ParameterMapping实例
      private ParameterMapping parameterMapping = new ParameterMapping();
      //通过构造器初始化ParameterMapping的一些成员变量
      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;
      }
      //设置parameterMapping的mode
      public Builder mode(ParameterMode mode) {
          parameterMapping.mode = mode;
          return this;
      }
      //设置parameterMapping的javaType
      public Builder javaType(Class<?> javaType) {
          parameterMapping.javaType = javaType;
          return this;
      }
      //设置parameterMapping的jdbcType
      public Builder jdbcType(JdbcType jdbcType) {
          parameterMapping.jdbcType = jdbcType;
          return this;
      }
      //设置parameterMapping的numericScale
      public Builder numericScale(Integer numericScale) {
          parameterMapping.numericScale = numericScale;
          return this;
      }
      //设置parameterMapping的resultMapId
      public Builder resultMapId(String resultMapId) {
          parameterMapping.resultMapId = resultMapId;
          return this;
      }
      //设置parameterMapping的typeHandler
      public Builder typeHandler(TypeHandler<?> typeHandler) {
          parameterMapping.typeHandler = typeHandler;
          return this;
      }
      //设置parameterMapping的jdbcTypeName
      public Builder jdbcTypeName(String jdbcTypeName) {
          parameterMapping.jdbcTypeName = jdbcTypeName;
          return this;
      }
      //设置parameterMapping的expression
      public Builder expression(String expression) {
          parameterMapping.expression = expression;
          return this;
      }
      //通过build()方法创建对象,返回
      public ParameterMapping build() {
          resolveTypeHandler();
          validate();
          return parameterMapping;
      }
  	}
}

在SqlSourceBuilder类的buildParameterMapping()方法中可以看到建造者模式的实战应用:

//根据参数content,构建parameterMapping实例
private ParameterMapping buildParameterMapping(String content) {
    //属性值的Map集合
    Map<String, String> propertiesMap = parseParameterMapping(content);
    String property = propertiesMap.get("property");
    Class<?> propertyType;
    //省略...
    //创建一个ParameterMapping.Builder对象
    ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
	//省略...
    //这里遍历Map集合,把属性值设置到ParameterMapping对象中,并创建
    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 {
            //抛出异常
        }
    }
    //省略...
    //创建ParameterMapping对象,并返回
    return builder.build();
}

四、模板模式

模板模式是一种行为型模式,一般用在一些比较通用的方法中,定义一个抽象类,编写一个算法的骨架,将一些步骤延迟到子类。就像是请假条一样,开头和结尾都是写好的模板,中间的请假的原因(内容)由请假人(子类)去补充完整,这样可以提高代码的复用。

在Mybatis中,模板模式体现在Executor和BaseExecutor这两个类中。首先看张类图:
Mybatis-Executor
Executor是一个接口,从命名上可以看出是用来执行SQL语句的对象。下面有一个BaseExecutor的抽象类,这就是用来定义模板方法的。再下面有三个实现类,
SimpleExecutor(简单执行器),ReuseExecutor(重用执行器),BatchExecutor(批量执行器)。实现类就是用来填充模板中间的内容的。

执行器在执行JDBC操作的前后往往有很多需要处理的工作都是相同的,比如查询的时候使用缓存,更新时需要清除缓存等等,所以就很适合使用模板模式。

接着我们看BaseExecutor抽象类的源码,一看就明白了,其实就定义了一个骨架。

public abstract class BaseExecutor implements Executor {
    //查询操作
    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);
        //上面的代码是固定的
        try {
            //这段代码不同的子类有不同的实现,所以是调用抽象方法doQuery()
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        //下面的代码也是固定的
        } finally {
            //清除缓存
            localCache.removeObject(key);
        }
        //添加到缓存中
        localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        //返回结果
        return list;
    }
    
    //抽象方法,由子类去实现
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
        throws SQLException;
}

那么就有疑问了,一开始如果都不设置的话,默认使用哪个子类的实现。很简单,直接跟着源码去顺藤摸瓜,我们就看到了。

public class Configuration {
    //默认是SIMPLE,也就是SimpleExecutor
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
}

其实模板模式很简单就能“认出来”,我的理解就是,抽象类里定义具(具体的方法),具体方法再调抽(抽象方法)。那十有八九就是模板模式了。

五、代理模式

代理模式属于结构型模式,代理模式的定义说的很抽象,为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

其实代理模式可以简单理解为中介的作用,比如一手房东只关心收租,其他的水电费结算,带人看房这些杂七杂八的东西他不想关心,就交给中介(二手房东),租客要租房就给钱中介,一手房东收钱就找中介,这个中介就是所谓的代理者。

回到Mybatis框架中,SqlSession类就用到代理模式,SqlSession是操作数据库一个会话对象,我们用户一般通过SqlSession做增删改查,但是如果每次做增删改都开启事务,关闭事务,显然是很麻烦,所以就可以交给代理类来完成这个工作,如果没有开启事务,由代理类自动开启事务

Mybatis在这里是使用JDK动态代理,所以SqlSession是一个接口。

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

    void rollback();
    //省略...
}

我们知道动态代理要有一个实现InvocationHandler接口的类,这个类在SqlSessionManager里,是一个内部类,叫做SqlSessionInterceptor,在这个类里做相关的处理。

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取SqlSession对象,localSqlSession是一个ThreadLocal,所以是每个线程有自己的sqlSession
        final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
        //如果不为null
        if (sqlSession != null) {
            try {
                //执行方法,返回结果  
                return method.invoke(sqlSession, args);
            } catch (Throwable t) {
                throw ExceptionUtil.unwrapThrowable(t);
            }
            //如果sqlsession为null
        } else {
            //打开session  
            final SqlSession autoSqlSession = openSession();
            try {
                //执行Sqlsession的方法,获得结果  
                final Object result = method.invoke(autoSqlSession, args);
                //commit提交事务
                autoSqlSession.commit();
                //返回结果
                return result;
            } catch (Throwable t) {
                //rollback回滚事务
                autoSqlSession.rollback();
                //抛出异常
                throw ExceptionUtil.unwrapThrowable(t);
            } finally {
                //关闭sqlsession
                autoSqlSession.close();
            }
        }
    }
}

然后通过构造器去初始化,提供静态方法返回这个SqlSessionManager实例,请看源码。

//实现了SqlSessionFactory,SqlSession接口
public class SqlSessionManager implements SqlSessionFactory, SqlSession {

    private final SqlSessionFactory sqlSessionFactory;
    //代理类sqlSessionProxy
    private final SqlSession sqlSessionProxy;

    private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
        //初始化代理类
        this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
            SqlSessionFactory.class.getClassLoader(),
            new Class[]{SqlSession.class},
            new SqlSessionInterceptor());
    }
    //提供一个静态方法给外部获取SqlSessionManager类,可以用SqlSession接收
    public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionManager(sqlSessionFactory);
    }
    
    //重写selectOne方法,使用代理类去执行
    @Override
    public <T> T selectOne(String statement) {
        return sqlSessionProxy.<T> selectOne(statement);
    }
    
    //重写insert方法,使用代理类去执行
    @Override
    public int insert(String statement) {
        return sqlSessionProxy.insert(statement);
    }
    //省略...
}

这就是Mybatis使用代理模式的一个例子,其实也不是很复杂,还是能看懂的。

但是上面这种方式一般很少用,我们一般都是使用Mapper接口的方式,其实Mapper接口的方式也是使用了代理模式,接下来再继续看。直接看MapperProxy类。

//实现了InvocationHandler接口
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;
    }
	//代理类做的什么事情,看这个方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//省略...
        //获取mapperMethod对象
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        //执行方法。根据Mapper.xml配置文件的配置进行执行,返回结果
        return mapperMethod.execute(sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        //从methodCache取出mapperMethod
        MapperMethod mapperMethod = methodCache.get(method);
        //为null
        if (mapperMethod == null) {
            //new一个。这里已经把Mapper.xml的一些配置都封装好了
            mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
            //然后put进methodCache
            methodCache.put(method, mapperMethod);
        }
        //返回mapperMethod
        return mapperMethod;
    }
}

mapperMethod的execute()方法的作用就是根据Mapper接口的方法名找到Mapper.xml文件的sql的Id,然后执行相应的操作,返回结果。内部的代码比较长,但是思路就是这样,这里就不展开了。

所以我们平时用的时候,TbCommodityInfoMapper接口假设是这样定义了一个list()方法。

public interface TbCommodityInfoMapper {
    //查询TbCommodityInfo列表
    List<TbCommodityInfo> list();
}

那个在TbCommodityInfoMapper.xml就要对应有一个id为list的配置。

<!-- 命名空间也要和接口的全限定名一致 -->
<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <!-- 必须要有一个对应的属性id为list的sql配置 -->
    <select id="list" resultType="tbCommodityInfo">
        select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
    </select>
</mapper>

然后在MapperProxyFactory类,使用工厂模式提供获取代理类。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
	//通过构造器初始化mapperInterface
    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);
    }
	
    //通过这个方法,获取Mapper接口的代理类
    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

}

那个这个getMapper的方法就在SqlSession的子类中被调用。

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

最终用户就是这样使用,这里是纯Mybatis,没有集成Spring的写法。

public class Test {
    public static void main(String[] args) {
        //获取SqlSession对象
        SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
        //再获取Mapper接口的代理类
        TbCommodityInfoMapper tbCommodityInfoMapper = sqlSession.getMapper(TbCommodityInfoMapper.class);
        //通过代理类去执行相应的方法
        List<TbCommodityInfo> commodityInfoList = tbCommodityInfoMapper.list();
    }
}

所以代理模式可以说是Mybatis核心的设计模式,用的是非常巧妙。

六、装饰器模式

装饰器模式属于结构型模式,使用装饰类包装对象,动态地给对象添加一些额外的职责。那么在Mybatis中,哪个地方会使用到装饰器模式呢?

没错了,就是缓存。Mybatis有一级缓存和二级缓存的功能,一级缓存默认是打开的,范围在SqlSession中生效,二级缓存需要手动配置打开,范围在全局Configuration,在每个namespace中配置。二级缓存的类型有以下几种,请看配置。

<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
    <!--
        eviction:代表的是缓存回收策略,目前MyBatis提供以下策略。
        (1) LRU,最近最少使用的,一处最长时间不用的对象
        (2) FIFO,先进先出,按对象进入缓存的顺序来移除他们
        (3) SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象
        (4) WEAK,弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。这里采用的是LRU,移除最长时间不用的对形象
        flushInterval:刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果你不配置它,那么当SQL被执行的时候才会去刷新缓存。
        size:引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大。设置过大会导致内存溢出。这里配置的是1024个对象
        readOnly:只读,意味着缓存数据只能读取而不能修改,这样设置的好处是我们可以快速读取缓存,缺点是我们没有办法修改缓存
    -->
    <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>

所以这个场景已经很清晰了,也就是在一级缓存的基础上,再添加二级缓存,也就符合装饰器模式的那句话,动态给对象添加职责功能。怎么做呢,我们不妨先看Cache类的类图。

先看Cache接口,其实就定义了一些接口方法。

public interface Cache {

    String getId();

    void putObject(Object key, Object value);

    Object getObject(Object key);

    Object removeObject(Object key);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}

然后再看PerpetualCache类,这是Cache接口最基本的实现,二级缓存要扩展就在这个类上面再去包装来实现扩展。其实就是一个HashMap,再简单包装一下。

public class PerpetualCache implements Cache {

    private final String id;
	//成员变量cache,创建一个HashMap
    private Map<Object, Object> cache = new HashMap<Object, Object>();

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

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

    @Override
    public int getSize() {
        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() {
        cache.clear();
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return null;
    }
	//省略...
}

那么我们再看二级缓存的一个代表性的类LruCache。

public class LruCache implements Cache {
	//一级缓存,保存在这个成员变量中
    private final Cache delegate;
    //实际上这是一个LinkedHashMap,利用LinkedHashMap的LRU算法实现缓存的LRU
    private Map<Object, Object> keyMap;
    private Object eldestKey;
	//构造器缓存对象,初始化
    public LruCache(Cache delegate) {
        this.delegate = delegate;
        setSize(1024);
    }

	//初始化keyMap,重写removeEldestEntry方法,实现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;
                //每次put进来时,eldestKey都是最老的key
                if (tooBig) {
                    eldestKey = eldest.getKey();
                }
                return tooBig;
            }
        };
    }

    @Override
    public void putObject(Object key, Object value) {
        //保存进缓存
        delegate.putObject(key, value);
        //这里就是删除掉不常用的值
        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) {
            //删除掉Map中最老的key
            delegate.removeObject(eldestKey);
            eldestKey = null;
        }
    }

}

关键在于成员变量delegate,这在其他的二级缓存装饰类中都定义了。这个是为了保存PerpetualCache这个基础缓存类的。所以这也就是说二级缓存是在PerpetualCache为基础扩展的。再继续看就更加明白了。

直接看创建SqlSessionFactory的builder方法,一直追踪下去,就可以找到MapperBuilderAssistant类的useNewCache方法。

public class MapperBuilderAssistant extends BaseBuilder {
	//当前Mapper.xml的命名空间
    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)
            //设置基础缓存
            .implementation(valueOrDefault(typeClass, PerpetualCache.class))
            //添加缓存装饰类
            .addDecorator(valueOrDefault(evictionClass, LruCache.class))
            .clearInterval(flushInterval)
            .size(size)
            .readWrite(readWrite)
            .blocking(blocking)
            .properties(props)
            //创建缓存
            .build();
        //把缓存添加到configuration,所以二级缓存是configuration范围的
        configuration.addCache(cache);
        currentCache = cache;
        return cache;
    }
}

再看CacheBuilder的build方法,更加清晰了。

public class CacheBuilder {
    
    private Class<? extends Cache> implementation;
    private final List<Class<? extends Cache>> decorators;
    
    public Cache build() {
        setDefaultImplementations();
        Cache cache = newBaseCacheInstance(implementation, id);
        setCacheProperties(cache);
        // issue #352, do not apply decorators to custom caches
        if (PerpetualCache.class.equals(cache.getClass())) {
            //遍历装饰器类
            for (Class<? extends Cache> decorator : decorators) {
                //在cache上添加二级缓存
                cache = newCacheDecoratorInstance(decorator, cache);
                setCacheProperties(cache);
            }
            cache = setStandardDecorators(cache);
        } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
            cache = new LoggingCache(cache);
        }
        //返回缓存
        return cache;
    }

    private void setDefaultImplementations() {
        if (implementation == null) {
            //如果为空,初始化为PerpetualCache
            implementation = PerpetualCache.class;
            //如果装饰器类为空,默认用LruCache
            if (decorators.isEmpty()) {
                decorators.add(LruCache.class);
            }
        }
    }
}

所以我们大概可以想到二级缓存就像千层饼一样,一层一层地包装起来。最后debug模式验证一下。

在执行查询的时候你就会看到真的是千层饼一样,一层一层的。看CachingExecutor类的query方法。

最后在调用Cache的putObject方法时就会一层一层从外到内地调用,实现为对象动态扩展功能的装饰器模式。

装饰器模式在Mybatis中的应用就讲到这里了。

七、总结

这篇文章就介绍了Mybatis中用到的6种设计模式,分别是工厂模式,单例模式,模板模式,建造者模式,代理模式,还有装饰器模式。实际上Mybatis除了讲的这些之外,还有很多没有提到的,比如组合模式,适配器模式等等。

很多框架是运用了大量的设计模式,如果对设计模式没有一定的认识,很容易看不懂,看懵。所以对于还没有设计模式基础的同学,建议先看设计模式,然后再去学习源码,这样才能循序渐进地提升自身实力。


参考文章
从设计模式的角度剖析Mybatis源码


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【K8S问题系列 |18 】如何解决 imagePullSecrets配置正确,但docker pull仍然失败问题

如果 imagePullSecrets 配置正确&#xff0c;但在执行 docker pull 命令时仍然失败&#xff0c;可能存在以下几种原因。以下是详细的排查步骤和解决方案。 1. 检查 Docker 登录凭证 确保你使用的是与 imagePullSecrets 中相同的凭证进行 Docker 登录&#xff1a; 1.1 直接登录…

[工具分享] 根据Excel数据根据Word文档模板,批量创建生成Word文档并重命名,方便快速查找打印

前几天交楼的小姐姐要多份Word文档合同打印给客户&#xff0c;那么100份就需要修改100次 上面好多都是模板的制式文件&#xff0c;里面的部分数据都是要根据实际值来变动的&#xff0c; 那么有没有快速的方法来操作呢&#xff0c;还是只能一个个手动的改&#xff0c;又容易出…

嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点

目录 一、static 1、static 修饰局部变量 2、 static 修饰全局变量 3、static 修饰函数 4、static 修饰类成员 5、小结 二、const 1、const 修饰普通变量 2、const 修饰指针 3、const 修饰函数参数 4. const 修饰函数返回值 5. const 修饰类成员 6. const 与 #defi…

云计算-华为HCIA-学习笔记

笔者今年7月底考取了华为云计算方向的HCIE认证&#xff0c;回顾从IA到IE的学习和项目实战&#xff0c;想整合和分享自己的学习历程&#xff0c;欢迎志同道合的朋友们一起讨论&#xff01; 第三章&#xff1a;常见设备 交换机 二层交换机和三层交换机&#xff0c;所谓二层交换机…

基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功

基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器&#xff0c;2FSK 的两个频率为:fI15KHz&#xff0c;f23KHz&#xff0c;波特率为 1500 bps,比特0映射为f 载波&#xff0c;比特1映射为 载波。 1&#xff09…

网络安全与加密

1.Base64简单说明描述&#xff1a;Base64可以成为密码学的基石&#xff0c;非常重要。特点&#xff1a;可以将任意的二进制数据进行Base64编码结果&#xff1a;所有的数据都能被编码为并只用65个字符就能表示的文本文件。65字符&#xff1a;A~Z a~z 0~9 / 对文件进行base64编码…

Python绘制太极八卦

文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…

C 语言面向对象

面向对象的基本特性&#xff1a;封装&#xff0c;继承&#xff0c;多态 1.0 面向过程概念 当我们在编写程序时&#xff0c;通常采用以下步骤&#xff1a; 1. 将问题的解法分解成若干步骤 2. 使用函数分别实现这些步骤 3. 依次调用这些函数 这种编程风格的被称作 面向过程…

滑动窗口最大值(java)

题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7]…

拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流

在前端开发的世界里&#xff0c;我们总是在不断追寻更高效、更简洁的方式来构建令人惊艳的用户界面。而今天&#xff0c;我要向大家隆重介绍一款具有创新性的工具 ——NoCss.js&#xff0c;它将彻底颠覆你对传统前端开发的认知&#xff0c;引领我们进入一个全新的无 CSS 编程时…

配置Springboot+vue项目在ubuntu20.04

一、jdk1.8环境配置 (1) 安装jdk8&#xff1a; sudo apt-get install openjdk-8-jdk (2) 检查jdk是否安装成功&#xff1a; java -version(3) 设置JAVA_HOME&#xff1a; echo export JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64 >> ~/.bashrc echo export PATH$J…

Spring框架特性及包下载(Java EE 学习笔记04)

1 Spring 5的新特性 Spring 5是Spring当前最新的版本&#xff0c;与历史版本对比&#xff0c;Spring 5对Spring核心框架进行了修订和更新&#xff0c;增加了很多新特性&#xff0c;如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上&#xff0c;所以Spri…

软考教材重点内容 信息安全工程师 第 5 章 物理与环境安全技术

5.1.1 物理安全概念 传统上的物理安全也称为实体安全&#xff0c;是指包括环境、设备和记录介质在内的所有支持网络信息系统运行的硬件的总体安全&#xff0c;是网络信息系统安全、可靠、不间断运行的基本保证&#xff0c;并且确保在信息进行加工处理、服务、决策支持的过程中&…

「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用

1. 谷歌浏览器安装及使用流程 1.1 准备篡改猴扩展程序包。 因为谷歌浏览器的扩展商城打不开&#xff0c;所以需要准备一个篡改猴压缩包。 其他浏览器只需打开扩展商城搜索篡改猴即可。 没有压缩包的可以进我主页下载。 也可直接点击下载&#xff1a;Chrome浏览器篡改猴(油猴…

【案例学习】如何使用Minitab实现包装过程的自动化和改进

Masimo 是一家全球性的医疗技术公司&#xff0c;致力于开发和生产各种行业领先的监控技术&#xff0c;包括创新的测量、传感器和患者监护仪。在 Masimo Hospital Automation 平台的助力下&#xff0c;Masimo 的连接、自动化、远程医疗和远程监控解决方案正在改善医院内外的护理…

Git旧文件覆盖引发思考

一天&#xff0c;我的同事过来找到我&#xff0c;和我讲&#xff1a;张叫兽&#xff0c;大事不好&#xff0c;我的文件被人覆盖了。git是真的不好用啊 git不好用&#xff1f;文件被覆盖&#xff1b;瞬间我似乎知道了什么&#xff0c;让我想到了某位男明星的语法&#xff1a;他…

CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes

CSP/信奥赛C语法基础刷题训练&#xff08;23&#xff09;&#xff1a;洛谷P1217&#xff1a;[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 …

嵌入式系统与OpenCV

目录 一、OpenCV 简介 二、嵌入式 OpenCV 的安装方法 1. Ubuntu 系统下的安装 2. 嵌入式 ARM 系统中的安装 3. Windows10 和树莓派系统下的安装 三、嵌入式 OpenCV 的性能优化 1. 介绍嵌入式平台上对 OpenCV 进行优化的必要性。 2. 利用嵌入式开发工具&#xff0c;如优…

SAP BC 记录一次因为HANA服务器内存满的问题

用户操作 DB02 进入hana数据库服务器 free -g 内存用完了 如下图 解决方案&#xff1a;增加内存 操作 关应用服务->关闭数据库服务->关闭hana服务器->加内存->起hana服务器->起hana服务->启动应用服务。

ArcGIS应用指南:ArcGIS制作局部放大地图

在地理信息系统&#xff08;GIS&#xff09;中&#xff0c;制作详细且美观的地图是一项重要的技能。地图制作不仅仅是简单地将地理数据可视化&#xff0c;还需要考虑地图的可读性和美观性。局部放大图是一种常见的地图设计技巧&#xff0c;用于展示特定区域的详细信息&#xff…