MyBaits源码解析主要知识汇总

Configuration组成

configuration的属性主要分为两大部分

  1. 从mybatis-config.xml中读取的配置
  2. 从mapper配置文件或Mapper注解读取的配置

主要对象

//与XML配置文件对应的对象
protected Environment environment;
protected boolean cacheEnabled = true;  //是否开启一级缓存
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;  //默认Type
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); //Mapper仓库
protected final InterceptorChain interceptorChain = new InterceptorChain(); //插件链条
//根据NameSpace+方法名作为ID,存储Mapper信息(sql语句,StatementType,ResultSetType,resultMap)
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//存储所有Mapper配置文件中的ResultMap节点
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");  
//?
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");  

其中类MappedStatement存储有如下信息:
sqlSource,ResultSetType,SqlCommandType,resultMaps,StatementType(STATEMENT, PREPARED, CALLABLE)

Configuration加载过程:

  1. XMLConfigBuilder解析mybatis-config.xml的配置到Configuration中
  2. 针对Configuration节点下的每个Mapper节点,循环调用XMLMapperBuilder解析Mapper到mappedStatements 属性
  3. 加载其他配置项:插件/拦截器、对象工厂、setting项。

备注:
一级缓存配置方法:

<setting name="localCacheScope" value="SESSION|STATEMENT"/>  

MappedStatement说明

根据namespace+方法名存储Mapper中每个方法对应的所有配置, 主要属性如下:

//节点中的id:amespace+方法名 
private String id;  
//直接从节点属性中取  
private Integer fetchSize;  
//直接从节点属性中取  
private Integer timeout;  
private StatementType statementType;  
private ResultSetType resultSetType;  
//对应一条SQL语句  
private SqlSource sqlSource;  

//每条语句都对就一个缓存,如果有的话。  
private Cache cache;  
//这个已经过时了  
private ParameterMap parameterMap;  
private List<ResultMap> resultMaps;  

//SQL的类型,select/update/insert/detete  
private SqlCommandType sqlCommandType;  
private KeyGenerator keyGenerator;  
private String[] keyProperties;  
private String[] keyColumns;  

//是否有内映射  
private boolean hasNestedResultMaps;  
private String databaseId;  

private String[] resultSets;  
SqlSource接口

SqlSource表示从mapper.xml或注解中读取的sql内容,继承类包括:DynamicSqlSource,StaticSqlSource,ProviderSqlSource,RawSqlSource, 主要功能用来生成Sql语句(或动态语句), 不细讲.


SqlSession:

sqlSession主要方法:

private final Configuration configuration;
private final Executor executor;

<T> T selectOne(String statement, Object parameter);
<T> T getMapper(Class<T> type);

//主要操作全部用Executor及Configuration来实现的.
 public Connection getConnection() {
    return executor.getTransaction().getConnection();
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { 

    //从Configuration获取一个MappedStatement配置  
    MappedStatement ms = configuration.getMappedStatement(statement);  
    //直接调用executor.query()方法  
    List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);  
    return result;  
}  

在DefaultSqlSession中有一个Executor对象,对象数据的操作都是由这个Executor来完成。
Executor持有一个Transaction对象
所以sqlsession的处理逻辑可以如下理解:
=> DefaultSqlSession将select/update/insert/delete/commit/rollback/close交由Executor处理
=> Executor又将commit/rollback/close方法交由Transaction处理
=> DefaultSqlSession/Executor/Transaction对象都在DefaultSqlSessionFactory.openSessionFromDataSource方法中创建

备注:

  1. 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心.同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在, 没有任何理由对它进行清除或重建。在应用运行期间不要重复创建多次,建议使用单例模式
  2. SqlSession则需要每个线程持有不同的对象,也就是说它不是线程安全的。
  3. Executor中有三个对象来帮助他完成MappedStatement的执行工作。
    StatementHandler负责从连接中获取一个Statement对象
    ParameterHandler负责对Statement设置参数
    ResultHandler负责生成查询语句的结果集

Executor

BaseExecutor为模板模式中的模板类, 子类包括ReuseExector, BatchExecutor,SimpleExecutor,
CachingExecutor 为装饰这模式, 结构图如下;
这里写图片描述

Executor主要结构:

//执行查询  
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;  

//执行update/insert/delete  
int update(MappedStatement ms, Object parameter) throws SQLException;  
//事务提交  
void commit(boolean required) throws SQLException;  
//事务回滚  
void rollback(boolean required) throws SQLException;  

//注意主要操作由StateMentHandler来操作
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

Executor主要操作由StateMentHandler来执行,(事务由Transaction对象处理),
参数映射由ParameterHandler 执行,
type映射由TypeHandler执行
结果映射由ResultSetHandler 执行.


StatementHandler

Executor利用StatementHandler执行SQL, StatementHandler执行过程如下:

  1. 获取一个数据库连接
  2. 调用StatementHandler.prepare()方法获取一个statement
  3. 调用StatementHandler.parameterize()方法设置sql执行时所需要的参数
  4. 调用StatementHandler.update或query方法执行sql

跟Executor的实现非常相似, 其类结构图如下

这里写图片描述


拦截器 Plugin

Mybatis是用jdk的动态代理来实现拦截器的,
详情参照 https://blog.csdn.net/yangsnow_rain_wind/article/details/79777137


缓存

一级缓存
  1. 一级缓存的生命周期与SqlSession的生命周期一样。
  2. 一级缓存是在BaseExecutor中实现, 是非线程安全的,好在是在sqlSession中使用的, sqlSession也是在单个线程中使用.而且访问完立即释放.
  3. 一级缓存只在同一个SqlSession中共享数据
  4. 在同一个SqlSession对象执行相同的sql并参数也要相同,缓存才有效。
  5. 如果在SqlSession中执行update/insert/detete语句的话,SqlSession中的executor对象会将一级缓存清空。
    配置方式:
<setting name="localCacheScope" value="SESSION|STATEMENT"/>  
二级缓存:
  1. 二级缓存对所有的SqlSession对象都有效,
  2. 在一个SqlSession中可以执行多个不同命名空间中的sql,也是就说一个SqlSession需要对多个Cache进行操作。
  3. 二级缓存不建议使用,, 最好使用自己其他方案代替,如 redis等

二级缓存的生命周期跟SqlSessionFactory一样,通常在整个应用中有效。二级缓存是通过CachingExecutor来实现的。


MapperProxy

Mapper接口是通过JDK动态代理实现的, 此处的代理可以认为一个”虚假”的动态代理,
方法被调用时会被mapperProxy拦截, myBatis会自动生成MapperProxy实例, 动态调用sqlsession完成连接数据库任务, 与mapper接口的实例压根没有关系,所以也就不需要再定义Mapper接口的实现类,
Mybatis编程中,开发人员只需要定义好Mapper接口(如:UserDao)就好了.

标准JDK动态代理 如下图:
这里写图片描述
MyBatis下只有接口定义而没有接口实现,代理图如下,可
这里写图片描述

MapperProxyFactory 源码:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {  
   //这个MapperProxyFactory是调用addMapper方法时加到knownMappers中的,  
   final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);  
   if (mapperProxyFactory == null)  
     //说明这个Mapper接口没有注册  
     throw new BindingException("Type " + type + " is not known to the MapperRegistry.");  
      //生成一个MapperProxy对象  
     return mapperProxyFactory.newInstance(sqlSession);  
 }  
public T newInstance(SqlSession sqlSession) {  
    //创建一个Mapperxy对象,这个方法实现了JDK动态代理中的InvocationHandler接口  
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);  
    return newInstance(mapperProxy);  
  }  

protected T newInstance(MapperProxy<T> mapperProxy) {  
    //mapperInterface,说明Mapper接口被代理了,这样子返回的对象就是Mapper接口的子类,方法被调用时会被mapperProxy拦截,也就是执行mapperProxy.invoke()方法  
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);  
  }  
  1. 在Mybatis提供的编程接口中,开发人员只需要定义好Mapper接口(如:UserDao),无需去实现
  2. 每个Mapper接口都会对应一个MapperProxyFactory对象实例,这个对应关系在Configuration.mapperRegistry.knownMappers中。
  3. 当getMapper()方法被调用时,Mybatis会找到相对应的MapperProxyFactory对象实例,利用这个工厂来创建一个jdk动态代理对象,是这个Mapper接口的实现类,当Mapper定义的方法被调用时,会调用MapperProxy来处理。
  4. MapperProxy会根据方法找到对应的MapperMethod对象来实现这次调用。
  5. MapperMethod对应会读取方法中的注解,从Configuration中找到相对应的MappedStatement对象,再执行。

小结:

  1. Mapperxy对象实现了JDK动态代理中的InvocationHandler接口
  2. 用户定义的Mapper接口被MapperProxy对象代理了,
  3. MapperMethod对象. Mapper接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系,这个methodCache是在MapperProxyFactory中持有的,MapperProxyFactory又是在Configuration中持有的,所以每个Mapper接口类对应的MapperProxyFactory和methodCache在整个应用中是共享的,一般只会有一个实例.
  4. 当getMapper()方法被调用时,Mybatis会找到相对应的MapperProxyFactory对象实例,利用这个工厂来创建一个jdk动态代理对象,是这个Mapper接口的实现类,当Mapper定义的方法被调用时,会调用MapperProxy来处理。
  5. MapperProxy会根据方法找到对应的MapperMethod对象来实现这次调用。
  6. MapperMethod对应会读取方法中的注解,从Configuration中找到相对应的MappedStatement对象,再执行。

其他

MyBatis主要对像生命周期:
namescope备注
SqlSessionFactoryBuildermethod只是在方法内部创建,方法结束时即消失
SqlSessionFactoryapplicationApplication中, 创建之后就一直存在,习惯被集成的Spring的Bean中
SqlSessionrequest/method
Mappermethod
MyBatis分页:

自带逻辑分页为逻辑分页, 全部加载数据, 然后在内存中分页

物理分页:

可以使用分页插件 Pagehelper
地址: https://github.com/pagehelper/Mybatis-PageHelper


不足:

SqlSession在一个生命周期中会产生大量的临时对象,如:Executor、Transaction、Cache、MetaObject、StatementHandler、ParameterHandler、ResultSetHandler等待。但是SqlSession的生命周期是非常短的。这样造成大量对象的产生,给JVM的GC工作带来了很多的消耗,这个应该是Mybatis比较大的缺点了。

备注:
看源码本来已经有点辛苦了, 把有限的知识写出来更累, 把知识写的有条理, 能让他人看懂更NND累.。
因资料比较多,引用的一些代码及图片没有备注出来, 望网友们海涵……
因知识有限,不足之处朋友们及时提出来, 多谢多谢,见谅见谅。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值