Mybatis概述

前言

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL, 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

mybatis核心流程

以下是mybatis对Jdbc的一个封装流程。
在这里插入图片描述

Configuration

配置类。用配置文件(也可以走注解)描述就是如下图所示

<configuration>  
   <!-- 事务管理器和数据源的设置,和spring集成的关键 -->
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC" />
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver" />
               <property name="url" value="jdbc:mysql://127.0.0.1:3306/iMybatis?characterEncoding=GBK" />
               <property name="username" value="iMybatis" />
               <property name="password" value="iMybatis" />
           </dataSource>
       </environment>
   </environments>
   
   <!-- 配置mapper,sql的脚本 -->
   <mappers>
       <mapper resource="org/iMybatis/abc/dao/UserDao.xml" />
   </mappers>
</configuration>

<mapper namespace="org.iMybatis.abc.dao.UserDao">  
    <select id="queryUsers" parameterType="UserDto" resultType="UserDto"  
        useCache="false">  
        <![CDATA[ 
        select * from t_user t where t.username = #{username} 
        ]]>  
    </select>  
</mapper>
MappedStatement

mapper 解析映射的主要类。

  1. sqlSource 对应sql文本
  2. ResultMap 对应结果集与实体的映射关系
  3. ParameterMap 入参实体与sql参数的映射关系
  4. …(具体参考MappedStatement)

可通过configuration.getMappedStatement(statement) 获取。
注:statement为namespace + id.

其中 忏

SqlSession

MyBatis中会话。相当于jdbc中一事务。查看SqlSession的构建类SqlSessionFactory,主要有以下4种参数

  1. autoCommit 是否自动提交
  2. connection 使用此连接创建
  3. level 事务的隔离级别
  4. executorType 执行类型
Executor

MyBatis执行器。相当于jdbc的事务中每一条语句的执行。根据SqlSession
中的executorType,Mybatis提供了三种模式

  • simple: 常规执行器,每次执行都会创建一个statement,用完后关闭
  • batch:批处理型执行器。doUpdate预处理存储过程或批处理操作

官方说明这个executor是用于执行存储过程的和批量操作的,因此这个方法是循环或者多次执行构建一个存储过程或批处理过程。相当于jdbc中的addBatch

  • reuse:可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement

不会每次执行完关闭statement,而是把statement放到缓存中。缓存的key为sql语句,value即为对应的statement。也就是说不会每一次调用都去创建一个 Statement 对象,而是会重复利用以前创建好的(如果SQL相同的话),这也就是在很多数据连接池库中常见的 PSCache 概念 。

BoundSql

通过MappedStatement与实现转入参数真实动态解析SQL文件。并将解析结果存入BoundSql类中。

解析方法为MappedStatement.getBoundSql

public class BoundSql {
  //解析后的sql
  private final String sql;
  //解析后的参数映射关系
  //一般情况下是一样的
  //sql包含foreach语法,则会根据入参list自动生成对应有关系
  private final List<ParameterMapping> parameterMappings;
  //入参
  private final Object parameterObject;
  //parameterMappings中系统有自动生成时,会根据入参list自动生成对应名称的值
  private final Map<String, Object> additionalParameters;
  //入参的反射类,方便取指
  private final MetaObject metaParameters;
}
StatementHandler

StatementHandler 封装了JDBC Statement操作.

  1. STATEMENT:直接操作sql

同时sql里的属有变量只支持${xxxx},而不支持#{xxx} .sql就是直接进行的字符串拼接

  1. PREPARED:预处理,参数,进行预编译

支持${xxxx}和#{xxx}。 我们的#会转换为?再设置对应的参数的值, 为 mybatis 默认模式

  1. CALLABLE:执行存储过程
ParameterHandler

负责对用户传递的参数转换成JDBC Statement 所需要的参数。

   //DefaultParameterHandler
   //设置参数到sql的核心代码
    public void setParameters(PreparedStatement ps) {
        //取出sql脚本中所有的类型设置
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //sql中参数名称
            String propertyName = parameterMapping.getProperty();
           //这属性是不是解析Sql脚本自动生成的
           if (boundSql.hasAdditionalParameter(propertyName)) { 
              value = boundSql.getAdditionalParameter(propertyName);
           }else{
              //代理实际参数类,方便使用反射
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              //从实现参数中获取值
              value = metaObject.getValue(propertyName);
           }
           
           //取出转换器
           TypeHandler typeHandler = parameterMapping.getTypeHandler();
            
           //设置值
           typeHandler.setParameter(ps, i + 1, value, jdbcType);            
        }
    }
TypeHandler

负责java数据类型和jdbc数据类型之间的映射和转换.TypeHandlerRegistry是其管理类。在Configuration创建时,自动创建。里面已经注大多数TypeHandler。主要有

// jdbc - handler
Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

// java - jdbc - handler 双维度表
// 支持jdbc 为 null
Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

// javaTypeClass - handler  javaClass对应 handler
Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

//根据 java - jdbc 双维度去查找
//ParameterMapping-typeHadler 默认为 type - 参数的java类型,   jdbcType 是 null
//ResultMapping - typeHadler  默认为 type - 参数的java类型,   jdbcType 是 数据库的的类型
getTypeHandler(Type type, JdbcType jdbcType)

//对它过以下设置,可以自定义typeHandler
<typeHandlers>
    <typeHandler jdbcType="VARCHAR" javaType="string" handler="com.mybatis.test.MyTypeHandler"/>
</typeHandlers>
ResultSetHandler

负责将JDBC返回的ResultSet结果集对象转换成JAVA类型的集合.

//DefaultResultSetHandler
//将返回结果集包装
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    //最终结果存储
    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    //一般情况下。第一个结果集
    //使用存储程的时候可能有多个结果集
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理结果集
      //取mapping 找 typeHandler
      //通后反射设置值
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    //多结果集及加嵌套代码省略

    return collapseSingleResultList(multipleResults);
  }

插件扩展

不能自定义扩展的,不是好框架。Mybatis也提供了Plugin方法。在创建ParameterHandler、ResultSetHandler、StatementHandler、Executor类时,会调.Plugin.wrap方法进行代理。

 //取得每个interceptor,signature的内容,看看其是否符合代理要求
 //如果符合则使用JDK提供的动态代理技术
 public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> 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;
  }

另外Mybatis也提供了ResultHandler,结果遍历,切入结果集每一行的收订。

//具体用计参才DefaultResultHandler
public interface ResultHandler<T> {
  // resultContext 第个结果集
  void handleResult(ResultContext<? extends T> resultContext);

}

缓存

mybatis 中存在一,二级缓存的概念。

  • 一级缓存:在BaseExecutor 中localCache。也就是其存储作用域为 Session
  • 二缓缓存:cacheEnabled= true 触发 CachingExecutor。其缓存取的是MappedStatement.getCache 也就是其存储作用域为全局 MappedStatement。(因此推荐不开)
Mapper映射

上术讲的是Mybatis的核心流程,我们可以使用Executor提供的接口调用了

User user1 = sqlSession.selectOne("mapper.UserMapper.selectUser2", 30);
System.out.println(user1.getUsername());

但我们直接开发中,更希望希望直接调用接口的方式

User user = sqlSession.getMapper(UserMapper.class).selectUser(30, Arrays.asList(118767287L, 1L));

通过JDK态方式代码接口

//代理接口
//MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

//调用接口时真正触发
//类MapperMethod
//省略了大部份代码
public Object execute(SqlSession sqlSession, Object[] args) {
     //封闭参数,将bean之类转成Map
     Object param = method.convertArgsToSqlCommandParam(args);
     //调用直接的代码
     Object result = sqlSession.selectOne(command.getName(), param);
     //判断接口返回值,来类型转换
     if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
    return result;
  }

总结

Mybatis是半自动代的ORM开源框架。但还免不了要写一些SQL.外面也有人开发了减少SQL的mybatis辅助框架。

  • mybatis-plus
  • mybatis-generator(代码生成)

个人觉的:很多功能的的瓶颈都在SQL上,所以我觉的以mybatis-generator为主,参考mybatis-plus功能点。

主要参考

Mybatis架构与原理
Mybatis文档
MyBatis-Plus文档
mybatis 3.x源码深度解析与最佳实践

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值