statement是什么意思_为什么建议框架源码学习从Mybatis开始?能说这么清楚的,少见了...

00398d715e774cd8ea8e156c984949f5.png

↓原文出处:掘金,原文作者:享学源码↓

目录

一、容器Configuration

二、动态SQL模板

  1. MappedStatement(映射器)
  2. 解析过程

三、SqlSession

  1. 基本介绍
  2. 分类
  3. Executor

四、Mapper(殊途同归)

  1. 存在的意义
  2. 工作原理

五、缓存

  1. 一级缓存
  2. 二级缓存

2.1基本信息

2.2如何工作

六、插件

七、结果映射

八、总结

看过Mybatis后,我觉得Mybatis虽然小,但是五脏俱全,而且设计精湛。

这个黑盒背后是怎样一个设计,下面讲讲我的理解

正文

一、容器Configuration

Configuration 像是Mybatis的总管,Mybatis的所有配置信息都存放在这里,此外,它还提供了设置这些配置信息的方法。Configuration可以从配置文件里获取属性值,也可以通过程序直接设置。

用一句话概述Configuration,它类似Spring中的容器概念,而且是中央容器级别,存储的Mybatis运行所需要的大部分东西。

二、动态SQL模板

使用mybatis,我们大部分时间都在干嘛?在XML写SQL模板,或者在接口里写SQL模板

<?xml  version="1.0" encoding="UTF-8" ?>mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">         select * from user where id= #{id}   

或者

@Mapperpublic interface UserMapper {    @Insert("insert into user( name, age) " +            "values(#{user.name}, #{user.age})")    void save(@Param("user") User user);    @Select("select * from user where id=#{id}")    User getById(@Param("id")String id);}

这对于Mybatis框架内部意味着什么?

1、MappedStatement(映射器)

  • 就像使用Spring,我们写的Controller类对于Spring 框架来说是在定义BeanDefinition一样。
  • 当我们在XML配置,在接口里配置SQL模板,都是在定义Mybatis的域值MappedStatement

一个SQL模板对应MappedStatement

mybatis 在启动时,就是把你定义的SQL模板,解析为统一的MappedStatement对象,放入到容器Configuration中。每个MappedStatement对象有一个ID属性。这个id同我们平时mysql库里的id差不多意思,都是唯一定位一条SQL模板,这个id 的命名规则:命名空间+方法名

Spring的BeanDefinition,Mybatis的MappedStatement

2、解析过程

同Spring一样,我们可以在xml定义Bean,也可以java类里配置。涉及到两种加载方式。

这里简单提一下两种方法解析的入口:

1.xml方式的解析

提供了XMLConfigBuilder组件,解析XML文件,这个过程既是Configuration容器创建的过程,也是MappedStatement解析过程。

XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);Configuration config = parser.parse()复制代码

2.与Spring使用时:会注册一个MapperFactoryBean,在MapperFactoryBean在实例化,执行到afterPropertiesSet()时,触发MappedStatement的解析

80a6e239531f4412bf3c2b6976d9294c.png

最终会调用Mybatis提供的一MapperAnnotationBuilder 组件,从其名字也可以看出,这个是处理注解形式的MappedStatement。

殊途同归形容这两种方式很形象,感兴趣的可以看看源码。

三、SqlSession

1.基本介绍

有了SQL模板,传入参数,从数据库获取数据,这就是SqlSession干的工作。

SqlSession代表了我们通过Mybatis与数据库进行的一次会话。使用Mybatis,我们就是使用SqlSession与数据库交互的。

我们把SQL模板的id,即MappedStatement 的id 与 参数告诉SqlSession,SqlSession会根据模板id找到对应MappedStatement ,然后与数据交互,返回交互结果

User user = sqlSession.selectOne("com.wqd.dao.UserMapper.selectUser", 1);

2.分类

  • DefaultSqlSession:最基础的sqlsession实现,所有的执行最终都会落在这个DefaultSqlSession上,线程不安全
  • SqlSessionManager : 线程安全的Sqlsession,通过ThreadLocal实现线程安全。

3.Executor

Sqlsession有点像门面模式,SqlSession是一个门面接口,其内部工作是委托Executor完成的。

public class DefaultSqlSession implements SqlSession {  private Configuration configuration;  private Executor executor;//就是他 }

我们调用SqlSession的方法,都是由Executor完成的。

public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {    try {      MappedStatement ms = configuration.getMappedStatement(statement);      ----交给Executor      executor.query(ms, wrapCollection(parameter), rowBounds, handler);    } catch (Exception e) {      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);    } finally {      ErrorContext.instance().reset();    }  }

四、Mapper(殊途同归)

1.存在的意义

UserMapper userMapper = sqlsession.getMapper(UserMapper.class);User user = userMapper.getById("51");

Mapper的意义在于,让使用者可以像调用方法一样执行SQL。区别于,需要显示传入SQL模板的id,执行SQL的方式。

User user = sqlSession.selectOne("com.wqd.dao.UserMapper.getById", 1);

2.工作原理

代理!!!代理!!! 代理!!!Mapper通过代理机制,实现了这个过程。

1、MapperProxyFactory: 为我们的Mapper接口创建代理。

public T newInstance(SqlSession sqlSession) {    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);    return newInstance(mapperProxy);}protected T newInstance(MapperProxy mapperProxy) {    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

MapperProxyFactory通过JDK动态代理技术,在内存中帮我们创建一个代理类出来。(虽然你看不到,但他确实存在)

2、MapperProxy:就是上面创建代理时的增强

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    try {      if (Object.class.equals(method.getDeclaringClass())) {        return method.invoke(this, args);      } else if (isDefaultMethod(method)) {        return invokeDefaultMethod(proxy, method, args);      }    } catch (Throwable t) {      throw ExceptionUtil.unwrapThrowable(t);    }   --------------------------    final MapperMethod mapperMethod = cachedMapperMethod(method);    return mapperMethod.execute(sqlSession, args);}

针对非默认,非Object方法(也就是我们的业务方法),会封装成一个MapperMethod, 调用的是MapperMethod.execute

3、MapperMethod一个业务方法在执行时,会被封装成MapperMethod, MapperMethod 执行时,又会去调用了Sqlsession

public Object execute(SqlSession sqlSession, Object[] args) {    Object result;    switch (command.getType()) {      case SELECT:        ...        result = sqlSession.selectOne(command.getName(), param);        ...        break;      ....}

绕了一周,终究回到了最基本的调用方式上。

result = sqlSession.selectOne(command.getName(), param);User user = sqlSession.selectOne("com.wqd.dao.UserMapper.getById", 1);

总结下:

  • 最基本用法=sqlsession.selectOne(statement.id,参数)
  • Mapper=User代理类getById--->MapperProxy.invoke方法--->MapperMethod.execute()--->sqlsession.selectOne(statement.id,参数)

显然这一绕,方便了开发人员,但是对于系统来说带来的是多余开销。

五、缓存

Mybatis 还加入了缓存的设计。

分为一级缓存和二级缓存:

1.一级缓存

先看长什么样子?原来就是HashMap的封装

public class PerpetualCache implements Cache {  private String id;  private Map cache = new HashMap();  public PerpetualCache(String id) {    this.id = id;  }}

在什么位置?作为BaseExecutor的一个属性存在。

public abstract class BaseExecutor implements Executor { protected BaseExecutor(Configuration configuration, Transaction transaction) {    this.localCache = new PerpetualCache("LocalCache"); }}

Executor上面说过,Sqlsession的能力其实是委托Executor完成的.Executor作为Sqlsession的一个属性存在。

所以:MyBatis一级缓存的生命周期和SqlSession一致

2.二级缓存

2.1基本信息

二级缓存在设计上相对于一级缓存就比较复杂了。

以xml配置为例,二级缓存需要配置开启,并配置到需要用到的namespace中。

    

同一个namespace下的所有MappedStatement共用同一个二级缓存。二级缓存的生命周期跟随整个应用的生命周期,同时二级缓存也实现了同namespace下SqlSession数据的共享。

二级缓存配置开启后,其数据结构默认也是PerpetualCache。这个和一级缓存的一样。

但是在构建二级缓存时,mybatis使用了一个典型的设计模式装饰模式,对PerpetualCache进行了一层层的增强,使得二级缓存成为一个被层层装饰过的PerpetualCache,每装饰一层,就有不同的能力,这样一来,二级缓存就比一级缓存丰富多了。

装饰类有:

  • LoggingCache:日志功能,装饰类,用于记录缓存的命中率,如果开启了DEBUG模式,则会输出命中率日志
  • LruCache:采用了Lru算法的Cache实现,移除最近最少使用的Key/Value
  • ScheduledCache:使其具有定时清除能力
  • BlockingCache: 使其具有阻塞能力
层层装饰private Cache setStandardDecorators(Cache cache) {    try {      MetaObject metaCache = SystemMetaObject.forObject(cache);      if (size != null && metaCache.hasSetter("size")) {        metaCache.setValue("size", size);      }      if (clearInterval != null) {        cache = new ScheduledCache(cache);        ((ScheduledCache) cache).setClearInterval(clearInterval);      }      if (readWrite) {        cache = new SerializedCache(cache);      }      cache = new LoggingCache(cache);      cache = new SynchronizedCache(cache);      if (blocking) {        cache = new BlockingCache(cache);      }      return cache;    } catch (Exception e) {      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);    }  }

2.2如何工作

二级缓存的工作原理,还是用到装饰模式,不过这次装饰的Executor。使用CachingExecutor去装饰执行SQL的Executor

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    executorType = executorType == null ? defaultExecutorType : executorType;    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;    Executor executor;    if (ExecutorType.BATCH == executorType) {      executor = new BatchExecutor(this, transaction);    } else if (ExecutorType.REUSE == executorType) {      executor = new ReuseExecutor(this, transaction);    } else {      executor = new SimpleExecutor(this, transaction);    }    if (cacheEnabled) {      executor = new CachingExecutor(executor);//装饰    }    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }

当执行查询时,先从二级缓存中查询,二级缓存没有时才去走Executor的查询

private Executor delegate;private TransactionalCacheManager tcm = new TransactionalCacheManager();public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)      throws SQLException {        Cache cache = ms.getCache();        ....        List list = (List) tcm.getObject(cache, key);        if (list == null) {          list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);          tcm.putObject(cache, key, list); // issue #578 and #116        }        return list;      }    }    return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);  }

其中TransactionalCacheManager 属性为二级缓存提供了事务能力。

public void commit(boolean required) throws SQLException {    delegate.commit(required);    tcm.commit();也就是事务提交时才会将数据放入到二级缓存中去}

总结下二级缓存

  • 二级缓存是层层装饰
  • 二级缓存工作原理是装饰普通执行器
  • 装饰执行器使用TransactionalCacheManager为二级缓存提供事务能力

六、插件

一句话总结mybaits插件:代理,代理,代理,还是代理。

Mybatis的插件原理也是动态代理技术。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {    ..      executor = new SimpleExecutor(this, transaction);    ....    if (cacheEnabled) {      executor = new CachingExecutor(executor);    }    插件的入口    executor = (Executor) interceptorChain.pluginAll(executor);    return executor;  }InterceptorChain public Object pluginAll(Object target) {    for (Interceptor interceptor : interceptors) {      target = interceptor.plugin(target);    }    return target;  }

以分页插件为例,创建完Executor后,会执行插件的plugn方法,插件的plugn会调用Plugin.wrap方法,在此方法中我们看到了我们属性的JDK动态代理技术。创建Executor的代理类,以Plugin为增强。

QueryInterceptorpublic Object plugin(Object target) {        return Plugin.wrap(target, this);}public class Plugin implements InvocationHandler {public static Object wrap(Object target, Interceptor interceptor) {    Map, Set> signatureMap = getSignatureMap(interceptor);    Class> type = target.getClass();    Class>[] interfaces = getAllInterfaces(type, signatureMap);    if (interfaces.length > 0) {      return Proxy.newProxyInstance(          type.getClassLoader(),          interfaces,          new Plugin(target, interceptor, signatureMap));    }    return target;  }}

最终的执行链:Executor代理类方法--》Plugin.invoke方法--》插件.intercept方法--》Executor类方法

七、结果映射

介于结果映射比较复杂,再开一篇来细节吧

八、总结

  • mybatis可以说将装饰器模式,动态代理用到了极致。非常值得我们学习。
  • 框架留给应用者的应该是框架运行的基本单位,也就是域值的概念,应用者只需要定义原料,然后就是黑盒运行。

例如:

  • Spring的BeanDefinition
  • Mybatis的MappedStatement

Mybatis是一个非常值得阅读的框架,相比于Spring的重,将Mybatis作为第一个源码学习的框架,非常非常的合适。

0fdd5cff323079c9c65b366e3daf51af.png
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值