mybatis源码分析

简介: 基于xml构建mybatis的源码分析

mybatis源码分析

        SqlSession sqlSession = null;
        try {
   
   
            // 加载mybatis核心配置文件
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            // 构建工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            // 创建sqlSession
            sqlSession = sqlSessionFactory.openSession();
            // 获取映射器
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            // 查询结果
            User user = userMapper.findOne(1L);
            System.out.println(userMapper.getClass());
            System.out.println(user);
        } catch (Exception e) {
   
   
            e.printStackTrace();
        }finally {
   
   
            // 关闭连接
            MybatisUtil.INSTANCE.closeSession(sqlSession);
        }

1.创建sqlsessionfactory的流程

1.创建sqlsessionfactorybuilder对象
2.build(inputstream)
3.xmlconfigbuilder创建解析器parser
image-20230512094821690.png

4.解析核心配置文件mybatis-config每一个标签信息,封装在configuration中
image-20230512095006031.png

5.解析mapper.xml,返回configuration,其中包含所有配置信息,例如resulttype,resultmap,sql的原信息
5.1 其中mappedstatement对应一个包含增删改查所有信息,例如select等,

image-20230512095135439.png

   5.2 mapperregistry中有生成代理mapper类

6.build(config)返回创建的defaultsqlsessionfactory

image-20230512095226306.png

总结:解析文件的配置信息,封装在configuration中,返回defaultsqlsessionfactory

2.创建sqlsession的流程

1.从数据库中opensession
2.configuration根据配置的类型创建对应的执行器executor,默认是simple,executor做crud
3.每个executor创建时会调用拦截器链(interceptorchain.pluginAll)加载所有插件

image-20230512095718138.png

4.最后返回defaultsqlsession,其中包含executor以及configuration等

image-20230512095843139.png

3.getmapper,返回底层动态代理对象-mapperproxy

image-20230512100803235.png

configuration.getmapper(type)

 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   
   
    return mapperRegistry.getMapper(type, sqlSession);
  }
//=========================================================
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   
   

    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
   
   
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
   
   
        // 创建mapperproxy代理对象并返回,其中包含sqlsession做crud,这里使用到了jdk动态代理
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
   
   
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
//=================================================================
public T newInstance(SqlSession sqlSession) {
   
   
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

 protected T newInstance(MapperProxy<T> mapperProxy) {
   
   
     // 这里是jdk动态代理代码
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
   
    mapperInterface }, mapperProxy);
  }

总结:使用mapperRegistry获取mapper,底层会调用mapperProxyFactory产生动态代理对象MapperProxy并返回

MapperProxy中包含sqlsession做crud

4.mybatis查询流程

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 (method.isDefault()) {
   
   
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
   
   
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // MapperProxy调用MapperMethod执行sql语句
    return mapperMethod.execute(sqlSession, args);
  }
  // ===================================================================
public Object execute(SqlSession sqlSession, Object[] args) {
   
   
    // 根据crud的类型执行不同
    Object result;
    switch (command.getType()) {
   
   
      case INSERT: {
   
   
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
   
   
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
   
   
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
   
   
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
   
   
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
   
   
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
   
   
          result = executeForCursor(sqlSession, args);
        } else {
   
   
          Object param = method.convertArgsToSqlCommandParam(args);
            // 这里就是查询单个的结果
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
   
   
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
   
   
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
// =================================================================
// 这里查询单个结果
public <T> T selectOne(String statement, Object parameter) {
   
   
    // Popular vote was to return null on 0 results and throw exception on too many.
    // 单个也是查询的selectList然后取第1条,如果查出多条就会报错
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
   
   
      return list.get(0);
    } else if (list.size() > 1) {
   
   
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
   
   
      return null;
    }
  }
// =======================================================================
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
   
   
    try {
   
   
      MappedStatement ms = configuration.getMappedStatement(statement);
        // 调用执行器执行查询,这里是cachingExecutor
      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();
    }
  }
// ===========================================================================
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   
   
      // 这里获取sql信息,BoundSql中有sql语句以及参数等
    BoundSql boundSql = ms.getBoundSql(parameterObject);
      // 创建缓存的key,key的规则是namespace+sql语句+id+参数+环境等
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
      // 执行查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
// ================================================================================
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
   
   
    // 这里是二级缓存
    Cache cache = ms.getCache();
    if (cache != null) {
   
   
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
   
   
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) 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;
      }
    }
    // 这里调用simpleExcutor执行查询,没有开启二级缓存
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
//================================================================================
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   
   
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
   
   
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
   
   
      clearLocalCache();
    }
    List<E> list;
    try {
   
   
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
   
   
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
   
   
          // 这里从数据库中去查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
   
   
      queryStack--;
    }
    if (queryStack == 0) {
   
   
      for (DeferredLoad deferredLoad : deferredLoads) {
   
   
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
   
   
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
//===========================================================
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 {
   
   
        // 从数据库查数据
      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;
  }
//================================================================
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 ,StatementHandler也会加入Configuration总的 interceptorChain拦截器链
        // 这里父类的BaseStatementHandler会赋值2个handler,
        //this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
        //this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

        // 其中parameterHandler-->defaultParameterHandler.setParameters(PreparedStatement ps)设置预编译参数
        // 其中resultsetHandler-->defaultResultsetHandler.handleResultSets(Statement stmt)处理结果集

      stmt = prepareStatement(handler, ms.getStatementLog());
        // 调用StatementHandler去执行Statement语句
      return handler.query(stmt, resultHandler);
    } finally {
   
   
      closeStatement(stmt);
    }
  }
// ====================================================
 public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
   
   
    PreparedStatement ps = (PreparedStatement) statement;
     // 这里使用的是原生jdbc操作
    ps.execute();
     // 处理结果集,这里使用DefaultResultSetHandler处理结果集,然后返回流程结束
    return resultSetHandler.handleResultSets(ps);
  }

mybatis查询流程总结

  • MapperProxy调用MapperMethod执行sql语句
  • 根据crud类型,sqlSession.selectList方法
  • 没有开启二级缓存,调用simpleExcutor执行查询
  • 查询先从二级缓存(cache)中拿,再从(localcache)中拿,没有的话最后调用queryfromDB,查出的数据放入一级缓存中
  • baseExecutor#doQuery,创建StatementHandler,里面BaseStatementHandler会赋值2个handler
  • 其中parameterHandler-->defaultParameterHandler.setParameters(PreparedStatement ps)设置预编译参数
  • 其中resultsetHandler-->defaultResultsetHandler.handleResultSets(Statement stmt)处理结果集
  • 底层是原生jdbc操作 ,使用DefaultResultSetHandler处理结果集,然后返回流程结束

创建mybatis四大对象 [parameterHandler,executor,resultsetHandler,StatementHandler] 时

都会interceptorChain.pluginAll()加载插件然后返回,这也是mybatis插件的原理,接下来就来自定义插件

自定义mybatis插件

  • 步骤1:实现mybatis中interceptor接口
  • 步骤2:配置签名,拦截目标类的目标方法
  • 步骤3:配置插件到核心配置文件中
/**
 * 自定义插件
 */
// 配置插件签名,当前插件拦截什么对象什么方法,这里拦截StatementHandler设置参数的方法
@Intercepts({
   
   
        @Signature(type = StatementHandler.class,method = "parameterize",args = Statement.class)
})
public class MyPlugin1 implements Interceptor {
   
   
    /**
     * 拦截的目标对象目标方法的执行
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
   
   
        Object target = invocation.getTarget();
        System.out.println("MyPlugin1....拦截的目标对象:"+target);
        System.out.println("MyPlugin1....拦截的方法:"+invocation.getMethod());
        System.out.println("MyPlugin1....拦截的参数:"+ Arrays.toString(invocation.getArgs()));
        // 动态改变查询的参数
        // 获取目标对象的元数据
        MetaObject metaObject = SystemMetaObject.forObject(target);
        // 获取参数PreparedStatementHandler中的parameterObject属性  parameterHandler.parameterObject
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("获取的参数值:"+value);
        // 修改一下参数
        metaObject.setValue("parameterHandler.parameterObject",3L);
        // 执行目标方法
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 为目标对象创建一个代理对象
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
   
   
        // 使用动态代理,mybatis提供了一个包装工具类Plugin.wrap
        System.out.println("将要包装的对象"+target);
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    /**
     * 插件注册时,设置属性值
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
   
   
        System.out.println("设置的属性值:"+properties);
    }
}
<!--配置插件-->
    <plugins>
        <plugin interceptor="cn.demo.interceptor.MyPlugin1">
            <!--设置属性-->
            <property name="name" value="zs"/>
            <property name="age" value="18"/>
        </plugin>
    </plugins>

备注:配置多个插件执行流程:创建动态代理时,按照插件配置顺序层层代理,执行目标方法是倒序执行

分页插件pageHelper的使用

  • 引入依赖
<!--分页插件-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>
  • 注册分页插件
<!--注册分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor">

</plugin>
  • 代码中使用
// 使用分页插件,用PageHelper或者pageInfo
Page<Object> page = PageHelper.startPage(1, 2);
List<User> list = userMapper.findAll();
list.forEach(e->System.out.println(e));
System.out.println("总条数:"+page.getTotal());
System.out.println("当前页:"+page.getPageNum());
System.out.println("数据:"+page.getResult());

mybatis批量操作

创建sqlsession时指定ExecutorType.BATCH,默认是simple,也可以在全局配置setting中指定,但这样不是很灵活

在spring中使用单独配置一个sqlsession,指定为批量执行器,使用时注入sqlsession即可

<!--配置批量执行器  -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
        <constructor-arg name="executorType" value="BATCH"></constructor-arg>
    </bean>

mybatis自定义类型处理器-typeHandler

<!--指定类型处理器-->
    <typeHandlers>
        <!--处理枚举类型用索引方式,指定处理的java类型为xx,不指定就是所有的枚举类型都用这个处理
        保存枚举的时候默认保存的是枚举的名字,用的EnumTypeHandler
        -->
        <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="cn.demo.domain.Gender"/>
    </typeHandlers>

保存枚举希望保存的是状态码,这里需要用到自定义类型处理器

  • 实现typeHandler接口或者继承BaseTypeHandler
package cn.demo.typehandler;

import cn.demo.domain.Gender;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 自定义类型处理器,处理枚举类型
 */
public class MyEnumTypeHandler implements TypeHandler<Gender> {
   
   
    @Override
    public void setParameter(PreparedStatement ps, int i, Gender parameter, JdbcType jdbcType) throws SQLException {
   
   
        // 将枚举类型保存为状态码
        ps.setString(i, String.valueOf(parameter.getCode()));
    }

    @Override
    public Gender getResult(ResultSet rs, String columnName) throws SQLException {
   
   
        // 根据数据库中拿到的状态码返回枚举对象
        String code = rs.getString(columnName);
        return Gender.getGenderByCode(Integer.valueOf(code));
    }

    @Override
    public Gender getResult(ResultSet rs, int columnIndex) throws SQLException {
   
   
        // 根据数据库中拿到的状态码返回枚举对象
        return Gender.getGenderByCode(Integer.valueOf(rs.getString(columnIndex)));
    }

    @Override
    public Gender getResult(CallableStatement cs, int columnIndex) throws SQLException {
   
   
        // 根据数据库中拿到的状态码返回枚举对象
        return Gender.getGenderByCode(Integer.valueOf(cs.getString(columnIndex)));
    }
}
  • 重写方法,设置要保存的东西
  • 配置在核心配置文件,typeHandler中
<typeHandlers>
    方式1:直接配置在核心配置文件中
    <typeHandler handler="cn.demo.typehandler.MyEnumTypeHandler" javaType="cn.demo.domain.Gender"/>
    方式2:在插入字段上指定类型处理器,结果映射集上指定返回的枚举类型处理
    <insert id="insert" parameterType="User" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
        insert into user (name,pwd,gender)
        values (#{name},#{pwd},#{gender,typeHandler=类型处理器全类名})
    </insert>
    <!--自定义结果映射集-->
    <resultMap id="baseResult" type="User">
        <id column="id" property="id"/>
         <!--返回结果字段处理枚举类型,注意设置参数和返回结果集枚举类型处理须保持一致-->
        <result column="gender" property="gender" typeHandler="cn.demo.typehandler.MyEnumTypeHandler"/>
    </resultMap>
</typeHandlers>
相关文章
|
6月前
|
SQL Java 数据库连接
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
Mybatis源码分析系列之第三篇:Mybatis的操作类型对象
|
SQL XML Java
源码分析系列教程(08) - 手写MyBatis(注解版)
源码分析系列教程(08) - 手写MyBatis(注解版)
112 0
|
1月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
129 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
6月前
|
存储 缓存 Java
探秘MyBatis缓存原理:Cache接口与实现类源码分析
探秘MyBatis缓存原理:Cache接口与实现类源码分析
101 2
探秘MyBatis缓存原理:Cache接口与实现类源码分析
|
6月前
|
SQL Java 数据库连接
MyBatis源码篇:mybatis拦截器源码分析
MyBatis源码篇:mybatis拦截器源码分析
|
6月前
|
缓存 Java 数据库连接
|
6月前
|
SQL Java 数据库连接
|
6月前
|
设计模式 SQL Java
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
Mybatis源码分析系列之第四篇:Mybatis中代理设计模型源码详解
|
6月前
|
存储 SQL Java
Mybatis源码分析系列之第二篇:Mybatis的数据存储对象
Mybatis源码分析系列之第二篇:Mybatis的数据存储对象
|
6月前
|
Java 关系型数据库 数据库连接
Mybatis源码分析系列之第一篇:回顾一下MyBatis的使用
Mybatis源码分析系列之第一篇:回顾一下MyBatis的使用