橘子学Mybatis02之基本对象以及执行流程全局一览

一、前置知识

1、面向对象

首先明白一点,面向对象是啥意思。字面意思就是你面向的是对象,也就是java世界里面万物都是对象。在mybatis这个使用java开发的框架里面,也是一样的,也就是说你看到的所有东西,最后落地都是一个个对象,包括你的那些配置文件,那些xml文件都是一样的,最后都要被框架加载读取解析封装成为一个对象在mybatis的体系中发挥作用。OK,了解一下这个后面看到那些配置文件的处理也就自然了。

2、IO流处理

操作系统中io调用是要发起内核函数的调用的,mybatis程序是运行在用户态的,前面我们说读取配置文件转为对象,这个读取就是一次io,众所周知,用户态进入内核态是一件很"耗"的事情。所以我们尽量减少这个操作,所以你得那些配置文件尽量少读几次,那最少得读一次,好吧,那就读一次最少。
OK,这些基本就是前置要了解的东西,东西不多,就那么个意思。

3、关于JDBC

我认识一个哥们,他问我要学啥东西,我说没事干看看Mybatis,这个哥们对我说:

不就是JDBC的封装吗,有啥可学的。

其实他说的不错,mybatis说的简单一点就是对JDBC的封装,那既然是封装,那最后一定是落脚到了JDBC上,你可以这么想,给了你一个JDBC的代码,让你封装一下,你肯定得知道JDBC有哪些东西,然后你才能去做封装。我们先来看一下JDBC有哪些组件。

Connection conn=DriverManager.getConnection(url, user, password); 
String sql="select * from user"; 
//statement每次执行sql,相关数据库都要执行sql语句的编译,preparedstatement是预编 译的, 
//preparedstatement支持批处理,处理多次请求的效率高 PreparedStatement 
preparedStatement=conn.prepareStatement(sql);
//调用executeQuery()方法,返回一个结果集rs,到此,数据库查询数据的工作就完成了 
ResultSet rs=preparedStatement.executeQuery();

上面就是jdbc的主要操作,可以看到主要就是三个类:

  • Connection 连接:和数据库建立连接的类
  • Statement:在连接之后负责执行sql的类,这个是个接口,他有多种实现,默认的那种不能预防sql糊注入,,所以用下面两种子类的多些。
    • PreparedStatement:这个是预编译执行的,效率高,大部分现在都用这个
    • CallableStatement:这个是执行存储过程的,现在应该是狗都不用了
  • ResultSet:执行sql之后返回的结果集的类
    mybatis底层其实就是对这些做了封装处理。

二、Mybatis的主要类

mybatis有这么几种对象:

  • 封装信息类的对象:
    我们前面说了,在面向对象的世界里面,你要把所有的东西都封装在一个对象里面,
  • 执行操作的对象:
    我们最终是要执行jdbc操作的,所以还需要一些执行操作的对象类,mybatis同样提供这类对象。

1、封装信息的对象

我们在编写mybatis代码的时候,有两种配置文件,一种是mybatis的核心配置,一种是你的sql的mapper文件,这两种文件都是要封装在对象里面的,因为你不能在使用的时候随时做读取解析吧,这种io操作必然不能多做,只能是一次读取解析封装完毕了,就放在对象里面了,以后随用随拿。

1.1、Configuration

这个对象就是用来封装mybatis的核心配置文件的,我们来看一下这个类。这个类里面有很多属性,我们先看几个核心的主要的。
这个类就是封装的核心配置文件,也就是我们之前例子里面的那个sqlMapConfig.xml文件,里面所有你的配置都可以来这里找到,进而做解析封装进来。我们来看几个。

// 封装了environment属性,这个是你jdbc数据源的环境配置
protected Environment environment;
// 这个是一级缓存是不是打开,默认是true,就是打开的
protected boolean cacheEnabled = true;
// 执行器,我们看到类型默认是SIMPLE,后面跟源码的时候可以看到就是这个的执行
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
// MappedStatement这个其实是你mapper。xml文件的封装。下面会详细说这个
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");

这里列举了几个主要的,我只是想说明一个问题,就是你那个核心配置类里面的属性都能解析封装到这个对象里面,这里我没列全,实际上,这个类里面有多少属性,那个配置文件就能配置多少,所以有时候官方打不开了,可以来源码这里看看具体能配置啥。
此外这个类下面还有这么几个对象:

// 我们看到其实他还帮你new出来很多处理器,都在这个类里面,所以这个Configuration是十分全面的。
// 而且Configuration还帮我们创建了下面这几个核心对象,所以Configuration实际上是他们几个的工厂。
// 这几个类由Configuration来创建。所以他很牛逼。核心类。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

  public Executor newExecutor(Transaction transaction) {
    return newExecutor(transaction, defaultExecutorType);
  }

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

有了这个类,mybatis在启动的时候就加载了配置文件,然后读取,解析,最后把你的配置封装到这个对象里面,以后随处用的时候直接拿出来用就可以了。

1.2、MappedStatement

我们还有一个配置文件,也就是你写sql的那些mapper.xml的文件,这类文件也是不能经常io,只读取一次就行了。那么他被封装到了这个MappedStatement的对象里面。

private String resource;
// 他这里其实还持有了Configuration 这个对象,Configuration 也封装了所有的MappedStatemen,他们能互相拿到。灵活度更高。
private Configuration configuration;
// 这就是你写sql标签里面的那个id,什么selectById就是这个玩意,在这里封装你看他这里的设计,一个id就是一个对象,所以他其实这个对象更细分的话他是封装的xml里面的一个增删改查的标签片段,但是我们又说了selectById这个只是方法名,每个mapper里面都可能有,所以他光靠这个不能确定唯一,他需要namespace结合一起来唯一确定,所以这个对象里面的id是由namespace+mapper中的id来组合确定的。
private String id;
// 每次查询的个数,批次大小,
private Integer fetchSize;
// 超时时间
private Integer timeout;
// 你使用的jdbc的Statement类型
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
// 这个类的构造方法,是个建造者模式
public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      // 我们看到了StatementType 属性默认是PreparedStatement这个预编译的,可见他也支持用这个,当然了你可以在配置文件里面修改,类似下面的例子,所以他默认给你的是这个预编译的statement
      mappedStatement.statementType = StatementType.PREPARED;

在这里插入图片描述

1.3、一个规则

我们在Configuration里面看到了这么一类属性

// MappedStatement这个其实是你mapper。xml文件的封装。下面会详细说这个
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());

我们看到了他封装了一个map,map里面是key-MappedStatement的一个key-value这个关系,那么他啥意思呢,他是在你封装了mapper.xml的MappedStatement的对象作为一个个的map,因为可能有多个xml所以最后是多个,那么这个多个他用key去做的区分,这个key要唯一区别的话第一个就是你写的那个id在xml里面要唯一,但是话又说回来了,我们可不止一个userMapper.xml,还可能有studentMapper.xml,那个id只是在自己的xml里面唯一的,但是你要封装到这个全局的map里面这个id就不能保证唯一key了,所以他实际上是依赖的userMapper.xml的两个部分来区分的,
也就是:

<mapper namespace="com.yx.dao.IUserDao">

  <select id="findAll" resultType="com.yx.domain.User" statementType="CALLABLE">
          SELECT * FROM `user`
  </select>
其实就是namespace和id合起来做唯一区分,至于你是用逗号隔开,还是空格啥的无所谓了,实际上mybatis是用.来隔开的。

基于以上我们可以得出这么一个结果,就是你的核心配置文件疯转成为Configuration,里面的每一个配置都在对象中有一个属性能对应上。
而你mapper.xml是封装在MappedStatement这个对象里面的,但是他封装的维度不是一整个文件,而是文件里面的一个个sql标签,区分这些sql标签的方式就是namesapce.id。具体可以参考下面这个图示。
在这里插入图片描述

2、处理行为的对象

上面我们已经解析了配置文件,把对应的信息封装到了对象里面,以后想用的时候随时拿来用就行了。我们这里先看一下我从网上找的一个架构图。而我们上面看到这类操作类的对象其实也是在Configuration里面创建的,Configuration是他们的工厂。这类对象就要负责对最终的数据库做操作,实现数据的操作。
在这里插入图片描述
大家都能看到,我们最终暴露给客户端调用的就是sqlSession这个类,他下面执行是调用的excutor这个类,看名字也知道是执行器的意思,我盲猜这里就是真正的操作的地方,我们去看一下这个类。

2.1、Executor执行器

他是个接口,里面定义了方法的模板实现,他是mybatis中处理功能的核心。而代码中设计的操作只有query和update,这里需要说一下就是在代码中的update其实对应到数据库操作其实是增删改,而查就是query,其他的缓存操作,事务操作他也是支持的。

他的实现类包括:BatchExecutor(批量操作,在jdbc中每一次执行sql都要做一次连接都要创建statement,所以批量执行其实是很有必要支持的,所以其实也支持batch操作。),ReuseExecutor(复用statement对象的时候,sql完全不变,包括参数之类的,就可以复用statement来重复执行sql),SimpleExecutor(最基本的,最常用的,也是mybatis默认的。),
我们看到这些方法也都是对应的你对数据库做的操作方法。其主要方法也就是这么几个

  • sql的生成和执行
  • 缓存的处理
  • 事务的管理
    这么多内容都在这里做了定义,我们这里先只看sql的执行,我们在上面的图里面可以看到,他是把第一个功能的sql的生成和执行都交给了StatementHandler这个类,真正做到了职责单一,把每一个功能都封装到不同的类里面,各司其职,坚决不混淆。这种设计是很好的。然后每个职责的类都定义为接口,去做不同的实现,利用多态做到扩展性。比如mybatis就支持批量的操作,默认的操作,简单的操作这都是不同的Executor实现。这里有一个原则就是在java开发中所有的操作类型的类,就是这个类的功能是用来操作数据的,其实都应该考虑设计为接口。即使你不知道哪些实现,为了你后续的扩展也应该考虑设计为接口。mybatis中大量使用这个原则,比如ssqlSession。
    Eexcutor是真正开始操作的地方,但是他是上层,他会把任务交给下面的各个类来完成。其中我们知道Statement是操作sql的,而StatementHandler是封装了jdbc的Statement,而Executor就是把操作sql交给了StatementHandler。

2.2、StatementHandler的sql处理

实际上StatementHandler也就是完成了sql的执行,但是事务,缓存其实还在其他地方。他完成的是最基本的。
这里就是封装了jdbc的Statement,而jdbc中的sql就是Statement操作的,所以这里其实就是执行sql操作了。进行jdbc操作。
在这里插入图片描述
他也是一个接口,这种操作类定义成为接口做多态扩展的实现在mybatis里面层出不穷。我们看下他的实现类。
其中base是一个适配器,不是最终真正实现的,上面的Executor其实也是这样。
在这里插入图片描述
在这里插入图片描述
所以我们到这里先局部总结一下:

我们建立会话开始处理最外层的类是SqlSession:但是他啥也不干,就是做了个封装。
SqlSession把执行的具体操作交给了Excutor类,这个类负责大体三个功能,执行sql,管理缓存,操作事务。
其中他把执行sql这个操作交给了StatementHandler(看名字也知道,因为最后是和jdbc打交道,其实就是Statement),其他两个管理缓存,操作事务他不负责。

2.3、ParameterHandler的处理

上面是把sql执行都做了,但是还有个问题就是参数,我们在mybatis写的sql来看都是那种#{}这种的,StatementHandler最后执行的时候人家从BoundSQL里面拿到的sql必须就是要一个最终的sql,所以还要做参数的替换,这种你自己也可以实现一下,就是个正则表达式替换,把我们mybatis的参数替换为jdbc的参数,最终sql封装在BoundSQL交给Statement。
然后mybatis把参数处理的工作交给了ParameterHandler下面的实现。你其实可以看到,他的这种职责划分的是很细致的。

2.4、TypeHandler的处理

最后执行的时候,以及执行完了做返回的时候,你执行的时候,要传参,最后执行完了返回的时候有结果,
这里面涉及到一个mybatis的java类型和数据库的mysql类型的转换映射,举个例子,你mybatis里面写的时候是string类型,到了数据库可能就是varchar类型。这种需要做转换,不然类型不对应,这个转换工作就是TypeHandler来做的,因为这个转换可能发生在执行sql的时候的参数转换以及查询结果的结果类型转换,所以其一定是和StatementHandler和ParameterHandler有关系的。
这个转换是双向的,你查的时候要把java的类型转去数据库,也要查回来把数据库类型转为java。

2.5、ResultSetHandler的处理

上面有了sql了,类型转换也完了,最后就是执行sql,那这个执行结果查出来之后这个结果集的封装就是在这里做的。返回给程序,他是和jdbc重的ResultSet差不多一个意思。

3、总结一下

我们一直说mybatis是jdbc的封装,jdbc是java执行数据库sql的工具。那我们这里面其实就是涉及到了java执行以及jdbc这两个内容。
其中jdbc包括:

  • sql
  • statement
  • ResultSet
这几个东西,那我们换位思考,我们设计mybatis的时候就是封装这几个东西就行了,所以他拆分了几个功能,
每个功能都负责对应的事情。我们从底向上开始解析一下:
其中StatementHandler负责封装statement,最后和sql打交道执行。
其中ParameterHandler负责封装对你xml里面参数的转换处理。最后传进去sql文本。
其中ResultSetHandler负责封装查询结果集的结果。最后返回给客户端。
其中TypeHandler负责对传参时候以及查询结果的数据库和java之间的类型转换。
最后他们统一在Excutor这个类里面做调用,其中Excutor不仅仅是上面那些执行sql的操作,人家还管着缓存和事务的功能。Excutor再封装给sqlSession最后做统一调用。默认的是DefaultSqlSession。
最终他们这些功能处理的类,统一封装成门面也就是sqlSession暴露给程序员客户端做调用。至此为止,这就是mybatis执行类的所有内容。示意图如下。

在这里插入图片描述

三、思辨

1、我们看到了这些结构,他把文件解析出来,拆成了一个个的对象,而且把对象拆的很细分,各处理各的,这是一种单依职责的体现,一个类只负责一个功能,在以后修改的时候我们只需要改这个类就行了,对其他类影响的也不多,实际开发可以基于这个实现一些你的代码。不要过度冗余一个类。

再补充一个例子:假如我们来设计mybatis的类结构,我们在封装MappedStatement的时候会怎么做呢?
我的第一反应就是,我把mapper.xml这个文件解析了,然后封装成为一个个的小对象,MappedStatement里面持有一个这个小对象的集合。这个小对象就是包含了这些sql标签,我可能会直接把标签上面的属性和sql文本都封装到一起,一个大类。
但是mybatis设计的时候他把文本单独拆出去了,又组合了一部分的属性,参数,这样在后面解析sql的时候对象的职责就隔离的很开,改动的时候不会改了这里,影响到哪里,做到了单依职责的解耦。这一点需要我们注意。

2、关于执行

@Test
public void test1() throws IOException {
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    List<User> userList = userDao.findAll();
}

我们上面分析了一大堆,最后知道了过程是sqlSession调用excutor以及后面的一堆handler处理的整个执行流程。那么问题来了,我实际写代码的时候是上面那两行,我调用的是我userDAO里面的findAll自己定义的方法,我这个方法是怎么和你的那些东西扯上关系的呢,而且你有没有发现一件事情。我的dao一直就是一个接口,也没有实现类,接口最后是怎么实现的呢?此时需要引出一个东西,叫做代理模式,后面我写一下静态代理和动态代理的东西。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值