Mybatis设计与源码分析

Mybatis设计分析一_踩踩踩从踩的博客-CSDN博客

 前言

前面文章主要针对mybatis有个大体的设计,包括 对 整个框架包括对于 mapper的存储,以及 如何应对我们常见的增删改查 如何去 定义 好 注解或者xml的方式来定义,如何进行参数之间的寻找等,都是需要设计的场景,以及 如何去执行sql 这是在 mybatis 框架给我们设计时,需要考虑到的; 这篇文章会继续 实现 mybatis框架 的各个部分,从而知道 mybatis框架 如何实现,最后在来看框架提供为我们做了多少事情。

设计框架的源码的好处

学习以及解析包括mybatis也好spring也好 ,这些热门的框架,都是让自己提升技术,包括在以后的开发中,别老是用什么 if else 这些 耦合性强的代码,而且不易后续功能的扩展以及 后后期维护时相当麻烦的,充分利用 接口 以及抽象类,将逻辑性的代码 进行抽象出来, 真正达到好的代码,单个类的单一原则等等,最后给我们的系统后期进行功能扩展也好 以及  重构也罢,是非常有必要的。  而且 当你学习 mybatis的源码 扩展点,自己也能写出来,通过接口的方式 对于 功能进行增强,这不是对于 我们一个大的提升么,可能 说这些对一些大牛来说都太小儿科了,所以废话不多说继续设计mybatis框架吧。

执行sql类的设计

对于mybatis最重要的一个类 ,Configuration 

这里面持有了mappedStatements  这些 数据 ,都是在 mybatis中 给我们 的一个总的类。

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
  protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
  protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

  protected final Set<String> loadedResources = new HashSet<String>();
  protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

框架完成接口对象的生成,JDBC执行过程。

我们得要有 DataSource ,谁来持有 DataSource,并且执行sql的地方 都需要一个类去解决。
像下图一下 让 configuration  来持有也行, 然后 在spring  中获取到对应的datasource.  
既方便又简单。

 谁来执行SQL

如果使用 Confifiguration去执行sql ,这样做耦合性是非常大的,而且不适合,相当复杂,mybatis也不可能这么做。
来做事的,那就先定义一个接口吧: SqlSession
具体的执行 接口  包括  执行sql 增删改查的操作等等。

 框架 对象接口的生成,实体类去执行,要做的事情 就是  要做的 sql 并且进行拼接等等。

具体的实现类的。  框架完成接口对象的生成,JDBC执行过程。

用户给定 Mapper 接口类,要为它生成对象,用户再使用这个对象完成对应的数据库操作。
相对应的数据库 操作。
UserDao userDao = sqlSession.getMapper(UserDao.class); 
userDao.addUser(user);
来为它定义一个实现类: DefaultSqlSession

 用户给入一个接口类,DefaultSqlSession中就为它生成一个对象  ,配置阶段扫描、解析Mapper接口时做个存储了

参数判断,类型转换,

 这也是配置信息,还是存在Confifiguration 中,就用个Set来存吧。

DefaultSqlSession中需要持有Confifiguration。

对象生成

如何为用户给入的 Mapper接口生成对象,使用代理的方式可以实现,JDK 动态代理。
Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { 
mapperInterface }, invocationHandler);
DefaultSqlSession 的实现:
public class DefaultSqlSession implements SqlSession {

  private Configuration configuration;

 public DefaultSqlSession(Configuration configuration) {
    super();
    this.configuration = configuration;
 }

 @Override
 public <T> T getMapper(Class<T> type) {
    //检查给入的接口
    if (!this.configuration.getMappers().contains(type)) {
        throw new RuntimeException(type + " 不在Mapper接口列表中!");
     }
    //得到 InvocationHandler
    InvocationHandler ih = null; // TODO 必须要有一个
    // 创建代理对象
    T t = (T)Proxy.newProxyInstance(type.getClassLoader(), new
    Class<?>[] {type}, ih);
    return t;
 }
}
代理对象中持有 InvocationHandler ,如果 InvocationHandler 能做到线程安全,就只需要一个
实例。
因为 这个sql 加载过后 是不变的。

执行SQLInvocationHandler

InvocationHandler 是在代理对象中完成增强。我们这里通过它来执行 SQL
public interface InvocationHandler {
     /** @param proxy 生成的代理对象
         @param method 被调用的方法 
        @param args @return Object 方法执行的返回值 
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 
}
实现我们的 InvocationHandler MapperProxy

 

package com.study.mybatis.session;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MapperProxy implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // TODO 这里需要完成哪些事?
    return null;
  }
}

这里需要做的事情 ,具体 要执行的。 

// 1 、获得方法对应的 SQL 语句
// 2 、解析 SQL 参数与方法参数的对应关系,得到真正的 SQL 与语句参数值
// 3 、获得数据库连接
// 4 、执行语句
// 5 、处理结果
获得方法对应的 SQL 语句
要获得 SQL 语句,需要用到 Confifiguration MapperProxy 中需持有 Confifiguration

 可以通过 method参数能得到方法名,但得到的类名不是Mapper接口类名。 

MapperProxy 持有其增强的 Mapper 接口类。

解析 SQL 参数与方法参数的对应关系,得到真正的 SQL 与语句参数值
逻辑:
1 、查找 SQL 语句中的 #{ 属性 } ,确定是第几个参数,再在方法参数中找到对应的值,存储下
来,替换 #{ 属性 } 为?
2 、查找 SQL 语句中的 ${ 属性 } ,确定是哪个参数,再在方法参数中找到对应的值,替换 ${
}
3 、返回最终的 SQL 与参数数组。
解析过程涉及的数据: SQL 语句、方法的参数定义、方法的参数值
@Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},# {sex},#{age})") 
void addUser(String id,@Param("name")String xname,String sex,int age);
Parameter[] params = method.getParameters();
public Object invoke(Object proxy, Method method, Object[] args)

这里是要确定 SQL 中的? N 是哪个参数值。
分三种情况: 方法参数是0 参数,单个参数、多个参数。
0 参数:不需要考虑参数了。
单个参数: SQL 中的参数取值参数的属性或就是参数本身。
多个参数:则需要确定 SQL 中的参数值取第几个参数的值。
多个参数的情况,可以有两种做法:
方式一:
查找 #{ 属性 } ,根据 Parameter[] 中的名称(注解名、序号)匹配确定是第几个参数,
再到 Object[] args 中取到对应的值。
方式二:先将 Parameter[] Object[] args 转为 Map ,参数名(注解名、序号)为 key
Object 参数值为值;然后再查找 SQL 语句中的 #{} ${} ,根据里面的名称到 map 中取对应的值。

所以所有的都要抽象出来,这才是正常的开发 模式。 和系统

我可以一层一层往下传递。 最好不要填写 switch 这样下去。

方式一相较于方式二,看起来复杂的地方是要遍历 Parameter[] 来确定索引号。
这个对应关系可以在扫描解析 Mapper 接口时做一次即可。在调用 Mapper 代理对象的方法时,
就可以直接根据索引号去 Object[] args 中取参数值了。
而 则每次调用 Mapper 代理对象的方法时,都需要创建转换 Map
而且方式一,单个参数与多个参数我们可以同样处理。
要在扫描解析 Mapper 接口时做参数解析我们就需要定义对应的存储结构,及修改
MappedStatement
N--- 参数索引号 的对应关系如何表示?
N 就是一个数值,而且是一个顺序数(只是 jdbc 中的?是从 1 开始)。我们完成可以用 List
存储。
@Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},# {user.name},#{user.sex},#{user.age},#{org.id})") 
void addUser(User user,Org org);
它应该是索引号、和里面的属性两部分。

解析阶段有它们俩完成这件事:

我们在 MappedStatement 中再增加一个方法来完成根据参数映射关系得到真正参数值的方
法:

 MapperProxyinvoke方法填填看:

@Override 
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
    // TODO 这里需要完成哪些事? 
    // 1、获得方法对应的SQL语句 
    String id = this.mapper.getName() + "." + method.getName();
     MappedStatement ms = this.configuration.getMappedStatement(id); 
    // 2、解析SQL参数与方法参数的对应关系,得到真正的SQL与语句参数值 
    RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args);
     // 3、获得数据库连接 
    Connection conn = this.configuration.getDataSource().getConnection(); 
    // 4、执行语句。 
    PreparedStatement pst = conn.prepareStatement(rsp.getSql()); 
    // 疑问:语句一定是PreparedStatement? 
    // 设置参数 
    if (rsp.getParamValues() != null) { 
        int i = 1;
         for (Object p : rsp.getParamValues()) { 
            pst.setxxx(i++, p); 
            //这里写不下去了.......如何决定该调用pst的哪 个set方法?
    }
  }
   // 5、处理结果 
    return null; 
}

JavaTypeJdbcType转换

JavaType java 中的数据类型。
JdbcType Jdbc 规范中根据数据库 sql 数据类型定义的一套数据类型规范,各数据库厂商遵照这
套规范来提供 jdbc 驱动中数据类型支持。

pstset方法中与对应的:

 这种情况:

 

 要使用的JDBCType,不然鬼知道他想要什么。

用户怎么指定
面向接口编程,  把不变 定义到  接口里面
以不变应万变  
TypeHandler

下面这个if-else-if的代码是否可以通过TypeHandler,换成策略模式

int i = 1; 
for (Object p : rsp.getParamValues()) {
     if (p instanceof Byte) { 
        pst.setByte(i++, (Byte) p);
     } else if (p instanceof Integer) { 
        pst.setInt(i++, (int) p); 
    }else if (p instanceof String) {
         pst.setString(i++, (String) p); 
    }... else if(...) 
  }

 

MapperProxy.invoke()中 设置 还需要JDBCType

 

 MapperProxy中的代码就变成下面这样了:

int i = 1; 
    for (ParamValue p : rsp.getParamValues()) { 
        TypeHandler th = p.getTypeHandler() 
        th.setParameter(pst,i++,p.getValue());
    }

MappedStatement又从哪里去获取TypeHandler

Confifiguration 吧,它最合适了。以什么结构来存储呢? 这里涉及到查找,需要根据 参数的javaType jdbcTyp 来查找。
Map<Type,Map<JDBCType,TypeHandler>> typeHandlerMap;
TypeHandlerRegistry 类来持有所有的 TypeHandler,Confifiguration 中则持有
TypeHandlerRegistry。

其实这样设计的方式可以放到 我们开发项目时,这个 应该都明白把。

这个在mybatis中也使用到的,可以采用 注册进去。

用户如何来指定它们的TypeHandler

mybatis-confifig.xml 中增加一个元素来让用户指定吧。
mybatis-confifig.dtd
<!ELEMENT configuration (mappers?, typeHandlers?)+ > 
<!ELEMENT mappers (mapper*,package*)> 
<!ELEMENT mapper EMPTY>
 <!ATTLIST mapper resource CDATA #IMPLIED url CDATA #IMPLIED class CDATA #IMPLIED ><!ELEMENT package EMPTY> 
 <!ATTLIST package name CDATA #IMPLIED type CDATA #IMPLIED annotation CDATA #IMPLIED ><!ELEMENT typeHandlers (typeHandler*,package*)> 
<!ELEMENT typeHandler EMPTY> <!ATTLIST typeHandler class CDATA #REQUIRED >
既可以用 typeHandler 指定单个,也可用 package 指定扫描的包,扫描包下实现了 TypeHandler
接口的类。 mybatis-confifig.xml
<configuration>
<mappers>
  <mapper resource="com/mike/UserMapper.xml"/>
  <mapper url="file:///var/mappers/CourseMapper.xml"/>
  <mapper class="com.study.dao.UserDao" />
  <package name="com.study.mapper" />
<mappers>
  <typeHandlers>
   <typeHandler class="com.study.type.XoTypeHandler" />
   <package name="com.study.type" />
  </typeHandlers>
</configuration>
解析注册的工作就交给 XMLConfifigBuilder

MappedStatement中来决定TypeHandler,它就需要Confifiguration

ParameterMap中增加typeHandler属性。

用户在 SQL 语句参数中必须要指定 JDBCType 吗?
常用的数据类型可以不指定,我们可以提供默认的 TypeHandler

用户在 SQL 中参数定义没有指定 JDBCType ,则我们可以直接使用我们默认的 TypeHandler
#{user.name}
我们判断它的参数类型为 String ,就可以指定它的 TypeHandler StringTypeHandler 。可能
它的数据库类型不为 VACHAR ,而是一个 CHAR 定长字符,没关系!因为 pst.setString
VARCHAR CHAR 是通用的。
其实所有的扩展 ,在项目中你也要考虑使用这样的方式去解决。
mybatis有大量的handler  的项目,

执行结果处理

执行结果处理要干的是什么事

pst.executeUpate() 的返回结果是 int ,影响的行数。
pst.executeQuery() 的返回结果是 ResultSet
在得到语句执行的结果后,要转为方法的返回结果进行返回。这就是执行结果处理要干的事
根据方法的返回值类型来进行相应的处理。
这里我们根据语句执行结果的不同,分开处理:
1 pst.executeUpate() 的返回结果是 int
  void int long ,其他的不可以!
2 pst.executeQuery() 的返回结果是 ResultSet
可以是 void 、单个值、集合。
集合的元素可以是任意类型的  
根据结果集列名与属性名对应等等的形式。
需用户显式说明映射规则

这里需要涉及到的 数据进行转换 ,集合的处理,  对应的字段名等处理。 

Executor  中 进行 执行 处理  并返回值。

考虑 JDBCType --- JavaType 的处理
根据方法的返回值类型来进行相应的处理。
这里我们根据语句执行结果的不同,分开处理:
// 6、执行语句并处理结果 
switch (ms.getSqlCommandType()) { 
    case INSERT: 
    case UPDATE: 
    case DELETE: 
        int rows = pst.executeUpdate(); 
        return handleUpdateReturn(rows, ms, method); 
    case SELECT:
         ResultSet rs = pst.executeQuery(); 
        return handleResultSetReturn(rs, ms, method); 
 }

pst.executeUpate()的返回结果处理。

pst.executeUpate() 的返回结果是 int
pst.executeQuery() 的返回结果处理
pst.executeQuery() 的返回结果是 ResultSet
方法的返回值可以是什么?
可以是 void 、单个值、集合。
单个值可以是什么类型的值?
任意值、( map)
@Select("select count(1) from t_user where sex = #{sex}") 
int query(String sex); 
@Select("select id,name,sex,age,address from t_user where id = #{id}")
 User queryUser(String id); 
@Select("select id,name,sex,age,address from t_user where id = #{id}")
 Map queryUser1(String id);

这里也需要 策略模式 ,来根据不同的变换 来走不同的路径 进行转换数据。

无论结果是什么类型的,在这里我们都是要完成一件事:从查询结果中获得数据返回,只是返
回类型不同,有不同的获取数据的方式。
这也是变与不变的部分。
@Override
  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);

    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) {
    // TODO Auto-generated method stub
   return null;
 }
我需要在此做个抽象,应用策略模式,不同的处理实现这个抽象接口。
必须要将这部分进行拆分出来。

那么在handleResultSetReturn()方法中我们从哪得到ResultHandler

这肯定是在解析时,就能获取到,

MappedStatement 中获取,每个语句对象(查询类型的)中都持有它对应的结果处理器。
在解析准备 MappedStatement 对象时根据方法的返回值类型选定对应的 ResultHandler

 handleResultSetReturn方法中只需调用ms中的ResultHandler

private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) throws Throwable {
 return ms.getResultHandler().handle(rs, args); 
}

基本数据类型、String 如何处理

针对这种情况,提供对应的ResultHandler实现:

 handle方法中的逻辑

public Object handle(ResultSet rs, Object[] args) throws Throwable { 
//从rs中取对应值 
return rs.getXXX(OOO);
 }

该调用rs的哪个get方法

得根据返回值来,返回值类型从哪来?
SimpleTypeResultHandler 中取,在创建 MappedStatement 时,根据反射获得的返回值类
型给入到 SimpleTypeResultHandler

对于mybatis来说 更加麻烦一点,是放到  factory中做的处理。

 SimpleTypeResultHandlerhandle方法中的代码逻辑如下:

该返回值情况下不允许结果集多列。

不限制,用户指定列名,不指定则取第一列。

if else 的解决办法,利用策略模式  去解决。

public interface TypeHandler<T> { 
    Type getType(); JDBCType getJDBCType(); 
    void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException;
 T getResult(ResultSet rs, String columnName) throws SQLException; 
 T getResult(ResultSet rs, int columnIndex) throws SQLException; 
}

 

 在启动解析阶段完成结果的TypeHandler选定。

根据返回值类型,从 TypeHandlerRegistry 中取,要取,还得有 JDBCType ,用户可以指定,也
可不指定,不指定则使用默认的该类型的 TypeHandler
默认 TypeHandler 如何注册,修改 registerTypeHandler 方法的定义:

 很好,那就可以在SimpleTypeResultHandler中持有对应的TypeHandler

SimpleTypeResultHandler 中还有必要持有 Class<?> returnType

SimpleTypeResultHandler handle方法代码就简单了:

public Object handle(ResultSet rs, Object[] args) throws Throwable { 
    if (StringUtils.isNotEmpty(columnName)) { 
        return typeHandler.getResult(rs, columnName); 
    } else { 
        return typeHandler.getResult(rs, columnIndex); 
    } 
}

 对于对象的情况下。通过 工厂类,进行创建 对象,

创建对象,则需要对应的构造参数值。
得定义构造参数与 ResultSet 中列的对应规则
1 、优先采用指定列名的方式:用参数名称当列名、或用户为参数指定列名(参数名与列名不
一致时、取不到参数名时);
2 、如不能取得参数名,则按参数顺序来取对应顺序的列。
注解、 xml配置 指定列名
利用别名的方式。
对应的  参数 解析的时候,都会放到里面。
定义一个结果映射实体: ResultMap
在创建 ResultMap 时,当用户没有指定 TypeHandler 或是 UndefifinedTypeHandler
时,要根据 type jdbcType 取对应的 typeHandler ,没有则为 null;
ResultMap 类定义本身就是表示一种 java 类型与 JDBCType 类型的映射,基本数据类型与复合类
型(类)都是 java 类型。 扩充一下ResultMap 即可:

xml :根据 constructor 元素中 arg 元素的数量、 javaType 来确定构造函数。注意 arg 有顺序规
则、必须指定构造方法的全部参数。
结果集中取值来填装对象则是复杂的
不是一行一个 Blog 对象,处理行时要判断该行的 blog 是否已取过了。  需要判断  注解方式:在 @Arg @Rersult 注解中增加 id 指定项。 
怎么知道,属性列表和 和字段值进行对比,都可以采用这种方式去,查询数据等。

 这里需要根据具体的 的去判断 到底是那个数据的转换。

TypeHandler ---> javaType
TypehandlerRegistry 中定义一个 JDBCType 类型对应的默认的 TypeHandler 集合,来完成取
java 值放入到 Map
然后其他的怎么判断 的,都可以通过策略模式将  隔离开。
第一次处理结果时,要把这个 ResultMaps 填充好,后需查询结果的处理就是直接使用
resultMap

 Mybatis源码解读

Mybatis整体架构图

 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

Mybatis 核心流程三大阶段
1. 初始化阶段
读取 XML 配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作;
2. 代理阶段
封装 iBatis 的编程模型,使用 mapper 接口开发的初始化工作;
3. 数据读写阶段
通过 SqlSession 完成 SQL 的解析,参数的映射、 SQL 的执行、结果的解析过程;

Mybatis 的初始化 建造者模式
建造者模式( Builder Pattern )使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计 模 式属于创建型模式,它提供了一种创建对象的最佳方式。

将各个部分进行组装起来的。

Builder :给出一个抽象接口,以规范产品对象的各个组成成分的建造。这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建;
ConcreteBuilder :实现 Builder 接口,针对不同的商业逻辑,具体化复杂对象的各部分的创建。 在建造过程完成后,提供产品的实例;
Director :调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建;
Product :要创建的复杂对象需要生成的对象具有复杂的内部结构,实例化对象时要屏蔽掉对象内部的细节,让上层代码与复杂对象的实例化过程解耦,可以使用建造者模式;简而言之,如果“ 遇到多个构造器参数时要考虑用构建器”
一个对象的实例化是依赖各个组件的产生以及装配顺序,关注的是一步一步地组装出目标对象,可以使用建造器模式;

  • XMLConfigBuilder: 主要负责解析mybatis-config.xml
  • XMLMapperBuilder: 主要负责解析映射配置文件;
org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement()

 XMLStatementBuilder: 主要负责解析映射配置文件中的SQL节点;

从 springboot中就能看到

它帮我们创建了  sqlsessionfactory   

映射器的关键类
Configuration Mybatis 启动初始化的核心就是将所有 xml 配置文件信息加载到 Configuration 对象
中, Configuration 是单例的,生命周期是应用级的;

MapperRegistry mapper 接口动态代理工厂类的注册中心。在 MyBatis 中,通过 mapperProxy 实现InvocationHandler接口, MapperProxyFactory 用于生成动态代理的实例对象;
ResultMap :用于解析 mapper.xml 文件中的 resultMap 节点,使用 ResultMapping 来封装 id result 等子元素;
MappedStatement :用于存储 mapper.xml 文件中的 select insert update delete 节点,同时还包含了这些节点的很多重要属性;
SqlSource mapper.xml 文件中的 sql 语句会被解析成 SqlSource 对象,经过解析 SqlSource 包含的语句最终仅仅包含?占位符,可以直接提交给数据库执行;

这几大部分 都是存到Configuration 中的。

里面也也包括了 缓存 , 将事务工厂等,都注入到 容器中去

配置configruation  ,配置 的 的信息。

最终会产生sqlsessionfactory 

自定义的typehander  等  都需要全需要这里 配置的。 所以我们要自定义去创建  factory ,根据需要去设置。

利用 after proertiesset  方法  加载这个类之后,去创建 xmlbuilder.

 将configuration 中创建  的部分。  采用这种方式去加载的。最终返回configration  

 

继承自 basebuilder 

 Mybatis的初始化

 解析所有的节点。

对于所有的节点 ,进行解析。

 对于 一一的解析,拿到相关对应的mapper,以及 相关对应的mapper 

 根据不同的内容进行 解析开来。  

解析好了xml  添加进 mapperstatement

 根据 langdriver创建 对应 的 source

 最终还是添加到 configuration 中去的。

 这里面 也有对应的mapper.

 Configuration类图解

 MappedStatment图解

整个的初始化过程

sqlsessiontemplate

 

 对于  sqlsession 进行功能增强的,统一标准化。

扫描  对应的 对应 的mapper.  找到对应的。

 代理生成过程

 sqlsession 

 里面的方法,包括对应的  delete  inst  commit  update  commit等等 

 返回 getmapper。

代理生成过程

配置文件解读 + 动态代理的增强

 MapperProxy

MapperRegistry mapper 接口和对应的代理对象工厂的注册中心;
MapperProxyFactory :用于生成 mapper 接口动态代理的实例对象;
MapperProxy :实现了 InvocationHandler 接口,它是增强 mapper 接口的实现;
MapperMethod :封装了 Mapper 接口中对应方法的信息,以及对应的 sql 语句的信息;它是 mapper 接口与映射配置文件中sql 语句的桥梁;
MapperMethod
MapperMethod :封装了 Mapper 接口中对应方法的信息,以及对应的 sql 语句的信息;它是 mapper 接口与映射配置文件中sql 语句的桥梁; MapperMethod 对象不记录任何状态信息,所以它可以在多个代 理对象之间共享;
  • SqlCommand : 从configuration中获取方法的命名空间.方法名以及SQL语句的类型;
  • MethodSignature:封装mapper接口方法的相关信息(入参,返回类型);
  • ParamNameResolver: 解析mapper接口方法中的入参;

 

 

SqlSession原理

查看方法 Mybatis DefaultSqlSessionFactory#openSessionFromDataSource 方法
SqlSession MyBaits 对外提供的最关键的核心接口,通过它可以执行数据库读写命令、获取映射器、管 理事务等;

 

 

 

SqlSessionManager
SqlSessionManager 同时继承了 SqlSession 接口和 SqlSessionFactroy 接口,提供了创建 SqlSession 对象和操纵数据库的能力;
SqlSessionManager 有两种获取 SqlSession 的模式:
第一种模式和 SqlSessionFactroy 相同,同一个线程每次访问数据库,每次都可以创建新的
SqlSession 对象;
第二种模式,同一个线程每次访问数据库,都是使用同一个 SqlSession 对象 , 通过 localSqlSession 实现;

 

SqlSession 查询接口嵌套关系

 

Executor

Executor MyBaits 核心接口之一,定义了数据库操作最基本的方法, SqlSession 的功能都是基于它来实现的;

 

 

Executor 的执行过程是由了模板方法设计模式,例如查询操作流程。
1. 开始 SqlSession 执行 SQL
2. Executor.query
3. 获取 sql 信息( boundSql
4. 拼装 CacheKey cacheKey
5. 判断是否需要清空一级缓存
6. 根据 cacheKey 查找一级缓存
7. 是否命中一级缓存,命中则进行 SqlSession 执行 SQL
8. 未命中缓存,查询数据库得到结果 ResultSet
有三种方式执行: SIMPLE REUSE BATCH
9. 结果保存到一级缓存
10. 缓存延迟加载处理
11. SqlSession 执行 SQL

 

  • SimpleExecutor:默认配置,使用statement对象访问数据库,每次访问都要创建新的statement对象;
  • ReuseExecutor:可以重用的执行器,使用预编译PrepareStatement对象访问数据库,访问时,会重用缓存中的statement对象;

 

  • BatchExecutor:实现批量执行多条SQL语句的能力;
Executor 下的三个 Handler
通过对 SimpleExecutor doQuery() 方法的解读发现, Executor 是个指挥官,它在调度三个 Handler 工作:
1. StatementHandler :它的作用是使用数据库的 Statement PrepareStatement 执行操作,启承
上启下作用;
2. ParameterHandler :对预编译的 SQL 语句进行参数设置, SQL 语句中的的占位符 都对应
BoundSql.parameterMappings 集合中的一个元素,在该对象中记录了对应的参数名称以及该参数
的相关属性
3. ResultSetHandler :对数据库返回的结果集( ResultSet )进行封装,返回用户指定的实体型;

 参数的处理。

Executor内部运作过程

结果参数绑定

StatementHandler 分析
StatementHandler 完成 Mybatis 最核心的工作,也是 Executor 实现的基础;功能包括:创建 statement对象, 为sql 语句绑定参数,执行增删改查等 SQL 语句、将结果映射集进行转化;

 

 

BaseStatementHandler :所有子类的抽象父类,定义了初始化 statement 的操作顺序,由子类实现具体的实例化不同的statement (模板模式);
RoutingStatementHandler Excutor 组件真正实例化的子类,使用静态代理模式,根据上下文决定 创建哪个具体实体类;
SimpleStatmentHandler :使用 statement 对象访问数据库,无须参数化;
PreparedStatmentHandler :使用预编译 PrepareStatement 对象访问数据库;
CallableStatmentHandler :调用存储过程;

ResultSetHandler 分析
ResultSetHandler 将从数据库查询得到的结果按照映射配置文件的映射规则,映射成相应的结果集对象;

 

Mybatis事务

Mybatis 事务管理
对数据库的事务而言,具有:创建( create )、提交( commit )、回滚( rollback )、关闭( close )。 对应地,MyBatis 将事务抽象成了 Transaction 接口,其接口定义如下:

 

MyBatis的事务管理分为两种形式: 

1. 使用 JDBC 的事务管理机制:即利用 java.sql.Connection 对象完成对事务的提交( commit() )、回滚(rollback() )、关闭( close() )等。
2. 使用 MANAGED 的事务管理机制:这种机制 MyBatis 自身不会去实现事务管理,而是让程序的容器如(JBOSS Weblogic )来实现对事务的管理。
Spring 是另外的实现,不属于 Mybatis 事务管理范畴, Spring 的事务管理这里就不去涉及了,可以参考前面Spring 专题的内容。

事务工厂的创建

MyBatis 事务的创建是交给 TransactionFactory 事务工厂来创建的,如果我们将 <transactionManager>
type 配置为 "JDBC", 那么,在 MyBatis 初始化解析 <environment> 节点时,会根据 type="JDBC" 创建一个JdbcTransactionFactory 工厂,源码如下

如上述代码所示,如果 type = "JDBC", MyBatis 会创建一个 JdbcTransactionFactory.class 实例;如果type="MANAGED" ,则 MyBatis 会创建一个 MangedTransactionFactory.class 实例。
MyBatis <transactionManager> 节点的解析会生成 TransactionFactory 实例;而对 <dataSource> 解 析会生成datasouce 实例。

TransactionFactory定义

事务工厂 Transaction 定义了创建 Transaction 的两个方法:
通过指定的 Connection 对象创建 Transaction
通过数据源 DataSource 来创建 Transaction
JDBC MANAGED 两种 Transaction 相对应, TransactionFactory 有两个对应的实现的子类:
ManagedTransactionFactory JdbcTransactionFactory TransactionFactory 很容易获取到
Transaction 对象实例。

JdbcTransaction

JdbcTransaction 直接使用 JDBC 的提交和回滚事务管理机制 。它依赖与从 dataSource 中取得的连接connection 来管理 transaction 的作用域, connection 对象的获取被延迟到调用 getConnection() 方法。 如果autocommit 设置为 on ,开启状态的话,它会忽略 commit rollback
直观地讲,就是 JdbcTransaction 是使用的 java.sql.Connection 上的 commit rollback 功能,
JdbcTransaction 只是相当于对 java.sql.Connection 事务处理进行了一次包装( wrapper ),
Transaction 的事务管理都是通过 java.sql.Connection 实现的。

 

ManagedTransaction

ManagedTransaction 让容器来管理事务 Transaction 的整个生命周期,意思就是说,使用
ManagedTransaction commit rollback 功能不会对事务有任何的影响,它什么都不会做,它将事务管理的权利移交给了容器来实现。
注意:如果我们使用 MyBatis 构建本地程序,即不是 WEB 程序,若将 type 设置成 "MANAGED" ,那么,我们执行的任何update 操作,即使我们最后执行了 commit 操作,数据也不会保留,不会对数据库造成任何影响。因为我们将MyBatis 配置成了 “MANAGED” ,即 MyBatis 自己不管理事务,而我们又是运行的本地程序,没有事务管理功能,所以对数据库的update 操作都是无效的。
事务的最终控制
MyBatis 使用 sqlSession 对象来封装对一次数据库的会话访问。通过 sqlSession 对象,由 Executor 调用,实现事务的控制和数据查询。

 

  • PooledConnection:使用动态代理封装了真正的数据库连接对象;
  • PoolState:用于管理PooledConnection对象状态的组件,通过两个list分别 管理空闲状态的连接资源和活跃状态的连接资源
  • PooledDataSource:一个简单,同步的、线程安全的数据库连接池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

踩踩踩从踩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值