Mybatis源码分析

Mybatis源码

面试题

  • 我们想要研究Mybatis,需要搞懂两大类对象,一类是存储类对象一类是操作类对象
  • 类比与我们进行DAO层的开发时,我们先有数据(类比我们的存储类对象),然后使用Connection Statement 等操作这些数据(类比操作类对象)。
  • 也就是说我们Mybatis的存储类对象的作用就是读取我们写的所有配置文件,将其封装成对象
  • 操作类对象就是用来操作这个配置对象的,使用这些操作类对象与DB进行交互(建立连接发SQL拿结果等)

第一章-Mybatis的核心对象

运行流程图

1.1存储类对象Configuration

Mybatis如何解析XML配置文件

使用的是XPath方式 还有DOM SAX两种方式

XPathParser---->XNode

1.1.1概述

  • 在我们调用下面的代码时,Mybatis会将主配置文件(mybatis-config.xml)以及所有的XxxMapper.xml文件进行解析然后封装在Configuration对象中,然后创建一个SqlSession的工厂,将封装好的Configuration传入
 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
 SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
  • Mybatis对于主配置文件的封装是直接声明代表这个标签的对象
  • Mybatis对于Mapper文件的封装是基于每一个增删改查标签进行的封装,每一个(select|update|insert|delete)标签都封装成一个MappedStatement对象,然后将所有MappedStatement对象都设置在Configuration对象的一个Map属性中,key就是这个标签的namespace+标签id,value就是这个MappedStatement

1.1.2Configuration类

第一部分:封装的主配置文件信息

 //environments标签
protected Environment environment;  

//标签
protected boolean safeRowBoundsEnabled = false;   
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase = false;
protected boolean aggressiveLazyLoading = true;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys = false;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
protected boolean callSettersOnNulls = false;
protected boolean useActualParamName = true;

// 标签
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

.....

第二部分:封装的所有Mapper文件的信息

/*所有的增删改查标签被封装成了一个Map `
- key就是这个标签的namespace+标签id,
- value就是这个标签被封装成的MappedStatement对象
*/
  protected final Map<String, MappedStatement> mappedStatements 
    
  //所有的缓存信息 
  protected final Map<String, Cache> caches 
    
   /*所有的resultMap信息 跟MappedStatement类似 
  	key就是namespace+id value就是这个resultMap对象
    */
  protected final Map<String, ResultMap> resultMaps
  
    ...
MappedStatement类

里面包含了标签的 id 各种属性 SQL语句等

private Configuration configuration;  //我们的Configuration对象
private String id;  //标签的id
private Integer timeout;  //超时属性

/*
语句类型就是JDBC提供的有三种  默认是PreparedStatement
Statement          普通的,有SQL注入风险 弃用
PreparedStatement  预编译 默认使用
callableStatement  处理存储过程
*/
private StatementType statementType;     

//返回类型处理
private ResultSetType resultSetType;

/*
https://blog.csdn.net/lqzkcx3/article/details/78370567  关于SqlSource
https://blog.csdn.net/lqzkcx3/article/details/78370497   关于BoundSql

是一个接口 有四种实现类,具体看文章 
主要是构建一条SQL语句
*/
private SqlSource sqlSource;  //下面讲 里面存的是SQL语句 进行参数传入等

private boolean useCache;         //是否使用二级缓存

MappedStatement对于标签中SQL语句的封装

SQL语句以及对应的参数被封装在BoundSql类中

private String sql;   //封装的SQL语句 经过?替换后的SQL语句
private List<ParameterMapping> parameterMappings;  //SQL的参数列表中参数
private Object parameterObject;         //传入的参数值         
private Map<String, Object> additionalParameters;
private MetaObject metaParameters;

第三部分:创建四大对象的方法

//ParameterHandler的创建
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, 
                                            Object parameterObject,
                                            BoundSql boundSql) 
//ResultSetHandler的创建
public ResultSetHandler newResultSetHandler(Executor executor, 
                                            MappedStatement mappedStatement, 
                                            RowBounds rowBounds,                                                           ParameterHandler parameterHandler,
                                            ResultHandler resultHandler, 
                                            BoundSql boundSql)
//StatementHandler的创建
public StatementHandler newStatementHandler(Executor executor, 
                                            MappedStatement mappedStatement,                                               Object parameterObject,
                                            RowBounds rowBounds, ResultHandler 
                                            resultHandler, BoundSql boundSql)
//newExecutor对象的创建
public Executor newExecutor(Transaction transaction)
    
//newExecutor对象的创建
public Executor newExecutor(Transaction transaction, ExecutorType eecutorType) 

1.2操作类对象

1.Executor(接口)

是一个接口,Mybatis是处理功能的核心入口SqlSession中的功能底层都是调用的Executor中的这些方法,只不过对其进行了封装

增删改查+事务处理+处理缓存

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;
	
  //增删改 
  int update(MappedStatement ms, Object parameter) 
	
   //查询
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql)
	
	
   //事务
  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);
	
   //缓存
  void clearLocalCache();

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

核心的三个实现类

  • BatchExecutor 目的:批处理(JDBC中的addBatch操作)
  • ReuseExecutor 目的:复用Statement(前提:SQL语句+参数不能改变)
  • SimpleExecutor(最常用默认) 普通的执行器
//Configuration类中的配置
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

在那些位置可以指定Executor的类型

1、在主配置文件的settings标签中可以指定

2、在SqlSessionFactory创建SqlSession时可以传一个Executor类型参数

2.StatementHandler(接口)

StatementHandler是Mybatis封装了JDBC的Statement,真正Mybatis进行数据库访问操作增删改查的核心,Executor对其进行了包装,Executor的增删改查的底层都是StatementHandler帮其完成的

public interface StatementHandler {
    Statement prepare(Connection var1, Integer var2) throws SQLException;

    void parameterize(Statement var1) throws SQLException;
	
    //批量操作
    void batch(Statement var1) throws SQLException;
	
    //增删改
    int update(Statement var1) throws SQLException;
	
    //查 参数有一个ResultHandler处理返回结果 进行封装
    <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;

    <E> Cursor<E> queryCursor(Statement var1) throws SQLException;
	
    //SQL语句
    BoundSql getBoundSql();
	
    //参数处理
    ParameterHandler getParameterHandler();
}

三种主要实现(对应StatementType):

  • PreparedStatementHandler (默认) 预编译 PreparedStatement
  • SimpleStatementHandler 简单的Statement 不带预编译
  • CallableStatementHandler 处理存储过程

一些具体的操作--底层都是JDBC的操作

public class PreparedStatementHandler extends BaseStatementHandler {

@Override
public int update(Statement statement) throws SQLException {
  //获取PreparedStatement对象 --原生JDBC的操作步骤
  PreparedStatement ps = (PreparedStatement) statement;
  //执行 --原生JDBC的操作步骤
  ps.execute();
  int rows = ps.getUpdateCount();
  Object parameterObject = boundSql.getParameterObject();
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows;
}

@Override
public void batch(Statement statement) throws SQLException {
   //获取PreparedStatement对象 --原生JDBC的操作步骤
  PreparedStatement ps = (PreparedStatement) statement;
  //批量执行 --原生JDBC的操作
  ps.addBatch();
}

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler)    throws SQLException {
    //获取PreparedStatement对象 --原生JDBC的操作步骤
    PreparedStatement ps = (PreparedStatement) statement;
    //执行 --原生JDBC的操作步骤
    ps.execute();
    //获取查询结果 使用ResultSetHandler进行结果的封装
    return resultSetHandler.<E> handleResultSets(ps);
  }
   
}   

3.ParameterHandler(接口)

在StatementHandler执行SQL语句前,使用ParameterHandler填充预编译参数值

preparedStatement.set(int index,Obj)

4.ResultSetHandler(接口)

封装了JDBC原生的ResultSet

在StatementHandler执行SQL语句后,使用ResultSetHandler封装返回结果

5.TypeHandler(接口)

在ParameterHandler和ResultSetHandler处理数据时,底层其实就是使用的TypeHandler(类型处理器)来进行数据库与Java类型的转换,

public interface TypeHandler<T> {
	
   //在ParameterHandler设置预编译参数时 就是使用的这个方法 设置参数+类型转换
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

    
   //在ResultSetHandler处理结果封装时,就是使用的下面的方法 封装对象+类型转换
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

第二章-SqlSession详解

2.1SqlSessionFactory与SqlSession的联系

  • SqlSessionFactory是一个接口,我们一般使用的都是它的实现类DefaultSqlSessionFactory
  • SqlSession也是一个接口,我们一般使用的都是它的实现类DefaultSqlSession

源码流程解析

我们先来看一下它的源码,定义了一个Configuration类,然后在其构造器中为其赋值,就是我们读取主配置文件及Mapper文件后封装的Configuration对象

public class DefaultSqlSessionFactory implements SqlSessionFactory {
	
    //声明了Configuration属性 在build()时为其赋值
    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

然后我们要拿到一个session对象,使用的是SqlSessionFactory的openSession()方法,有很多重载的方法,我们这里就以最常用无参的来演示

public class DefaultSqlSessionFactory implements SqlSessionFactory {
   
@Override
public SqlSession openSession() {  
  //将我们 configuration对象的ExecutorType传入,获取一个Session对象
  return openSessionFromDataSource(configuration.getDefaultExecutorType(),    null, false);
  }    
}

点进openSessionFromDataSource()方法,最终就是返回了一个DefaultSqlSession对象,传入了

Configuration 配置对象

executor 执行器(默认的是SimpleExecutor)

autoCommit 是否自动提交

public class DefaultSqlSessionFactory implements SqlSessionFactory {
    
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
      //获取环境 为了获取连接
    final Environment environment = configuration.getEnvironment();
      //事务
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //获取执行器
    final Executor executor = configuration.newExecutor(tx, execType);
    //最终返回了一个  DefaultSqlSession对象 
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
  
}    

我们点进DefaultSqlSession类,看到了上一步创建DefaultSqlSession对象使用的构造器,也发现了其实在DefaultSqlSession中声明了 Configuration和Executor,调用构造器其实就是给这两个属性赋值。

也可以说明,在SqlSession被创建的时候,Executor就被创建了!!

public class DefaultSqlSession implements SqlSession { 

 private Configuration configuration;
 private   executor;

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
  this.configuration = configuration;
  this.executor = executor;
  this.dirty = false;
  this.autoCommit = autoCommit;
   }  
}    

总结

  • 经过上面的源码分析,我们可以知道,创建SqlSessionFactory实际上是为了为其内部的Configuration属性赋值,
  • 然后在创建SqlSession时也为其内部的Configuration属性和Executor赋值,这样,我们的SqlSesssion被创建时,Configuration和Executor也就初始化完成了

2.2SqlSession与Exeuctor建立的联系

我们有两种方式执行增删改查

//1、通过获取接口的代理调用方法 动态代理 MapperProxy
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee employee = mapper.getEmpById(1);

//1、直接使用sqlSession的增删改查方法
session.insert(xxx);

那么这两种方式有什么区别吗? 第一种方式就是对于第二种的封装

我们打开SqlSession的源码,发现里面定义了很多的方法,基本上都是关于

增删改查+缓存+事务

public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);
  <T> T selectOne(String statement, Object parameter);
  <E> List<E> selectList(String statement);
  <E> List<E> selectList(String statement, Object parameter);
  <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
  void select(String statement, Object parameter, ResultHandler handler);
  void select(String statement, ResultHandler handler);
  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
  int insert(String statement);
  int insert(String statement, Object parameter);
  int update(String statement);
  int update(String statement, Object parameter);
  int delete(String statement);
  int delete(String statement, Object parameter);
  void commit();
  void commit(boolean force);
  void rollback();
  void rollback(boolean force);
}

我们使用第一种方式执行,debug进入,发现增删改查的底层就是调用的SqlSession的方法,所以说我们接下来直接研究SqlSession的这些增删改查时如何执行的即可

sqlSession.insert(command.getName(), param)
sqlSession.update(command.getName(), param)
sqlSession.update(command.getName(), param)
    ....

执行过程解析

我们知道SqlSession是一个接口,它有一个实现类DefaultSqlSession,我们点进DefaultSqlSession就以一个查询方法为例,看一下执行流程,

我们发现,SqlSession调用的增删改查方法其实是Executor中的方法!!!!

public class DefaultSqlSession implements SqlSession {
    
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
      
      //调用的确实是Executor的方法 query()方法
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      
     
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  } 
 }   
}    

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
        
      //调用的是  executor的update方法
      return executor.update(ms, wrapCollection(parameter));
        
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

然后我们追踪一下在Executor中是怎样执行的

我们点进update方法,是接口中的方法,BaseExecutor对其进行了重写,

public abstract class BaseExecutor implements Executor {
    
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
   }
}   

调用的是doUpdate方法,这个doUpdate是由典型的三种Executor实现的,我们点进SimpleExecutor中看一下doUpdate方法,

我们发现,方法里面声明了原生的Statement,然后通过MappedStatement对象获取了Configuration,又通过Configuration创建了StatementHandler对象,最终是StatementHandler执行了此次的update操作

public class SimpleExecutor extends BaseExecutor {
@Override
public 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, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    closeStatement(stmt);
  }
  }
}   

最后,我们点进PreparedStatementHandler的update方法中,发现最终是由StatementHandler执行的方法。

public class PreparedStatementHandler extends BaseStatementHandler {

    @Override
public int update(Statement statement) throws SQLException {
   //JDBC原生操作
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  int rows = ps.getUpdateCount();
  Object parameterObject = boundSql.getParameterObject();
  KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
  keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
  return rows; 
  } 
}      

总结:

  • Executor是在创建SqlSession时就创建的

  • StatementHandler是在执行增删改查方法时在Executor中被Configuration对象创建。

  • 最终的原生JDBC操作就是由StatementHandler封装执行的。

  • ResultSetHandler ParameterHandler是在StatementHandler创建时创建的(在BaseStatementHandler的构造器赋值)

    所以,也印证了我们上面的执行流程图。

SqlSession——>Executor——>StatementHandler——>JDBC原生操作

2.3SqlSession获取代理的核心

使用SqlSessison调用getMapper()方法时,通过动态代理为我们的Mapper接口产生了一个实现类,那么底层是如何实现的呢?

XxxMapper mapper = sqlSession.getMapper(XxxMapper.class);

通过上面的研究我们可以发现,实际上通过代理调用增删改查的方式的底层还是调用的SqlSession的增删改查方法。那么我们不妨先简单的实现一下这个功能

动态代理的核心就是InvocationHandler,我们需要实现此接口来为原始方法增加额外功能

要生成代理对象,肯定要调用这个方法,类加载器 实现的接口 以及额外功能InvocationHandler,前两个都好说,第三我们需要自定义一个InvocationHandler

EmployeeDao dao = (EmployeeDao) Proxy.newProxyInstance(ClassLoader,interfaces,InvocationHandler)

分析:这时的动态代理属于是无中生有式 ,即我们没有目标类,我们的目的就是直接造一个接口的实现类即可,在实现类中调用SqlSession的增删改查方法即可,这也就是核心

public class MyMapperMethod  implements InvocationHandler {
	
    //注入一个SqlSession为了调用增删改查方法
    private SqlSession sqlSession;
    
    //注入接口的类对象 因为我们在使用SqlSession的增删改查方法时,
    //第一个参数就是 接口的全类名+id id就是方法名 我们可以通过method获取
    //所以我们需要接口的全类名 就把接口的类对象注入 
    private Class daoClass;

    public MyMapperMethod(SqlSession sqlSession, Class daoClass) {
        this.sqlSession = sqlSession;
        this.daoClass = daoClass;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object one = sqlSession.selectOne(daoClass.getName() + "." + method.getName(),1);
        return one;
    }
}

大致的思路就是上面的代码,核心就是我们得把接口的类对象传入,因为SqlSession调方法的时候需要 接口的全类名+方法名(即namespace+id)

所以我们在调用时就可以生成一个代理类对象,

EmployeeDao dao = (EmployeeDao) Proxy.newProxyInstance(Test1.class.getClassLoader(),
                        new Class[]{EmployeeDao.class}, 
                        new MyMapperMethod(session,EmployeeDao.class));

这只是一个大体的思路,Mybatis的实现比这个要复杂的多,要考虑参数,调的是什么方法等,但是核心的思路是一致的。

那么,Mybatis是如何实现的呢?

Mybatis使用了这两个类为我们的接口创建实现类

  • MapperProxy(就是一个InvocationHandler)
  • MapperProxyFactory (核心Proxy.newProxyInstance())

通过MapperProxyFactory工厂将我们的MapperProxy(额外功能)传入,底层调用Proxy的newProxyInstance方法为我们创建了一个代理类对象

MapperProxy核心源码

public class MapperProxy<T> implements InvocationHandler, Serializable {
	
  //SqlSession
  private final SqlSession sqlSession;
  //接口的类对象
  private final Class<T> mapperInterface;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

MapperProxyFactory创建代理类对象源码

public class MapperProxyFactory<T> {
    
protected T newInstance(MapperProxy<T> mapperProxy) {
    //通过此方法 创建一个代理了类对象
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new  Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    //通过传入 sqlsession和接口创建一个MapperProxy 然后传入上面的方法创建一个代理类
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
   }    

总结

获取代理对象的核心就是MapperProxy,就是一个额外功能处理器,将SqlSession和接口类对象传入然后调用SqlSession的方法。然后通过MapperProxyFactory创建一个代理对象

2.4封装方法为MapperMethod

在方法执行之前,实际上,Mybatis会将我们的方法封装成一个MapperMethod

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  if (Object.class.equals(method.getDeclaringClass())) {
    try {
      return method.invoke(this, args);
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }
    //这里 会将我们的方法封装成一个MapperMethod 然后执行的是MapperMethod
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  return mapperMethod.execute(sqlSession, args);
  }
} 

我们看一下MapperMethod的内部结构

public class MapperMethod {

    
  //SQL语句标签 后面要根据标签不同执行不同的方法(判断是增删改查)
  private final SqlCommand command;
    
  //SQL的返回值类型 (查询时返回值的情况)
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

你可能感兴趣的:(JavaSE,Mybatis,MySQL,mybatis,Mybatis源码解析,ORM框架,MySQL,JDBC)