mybatis-核心接口和它们之间的关系

135 阅读9分钟

核心接口和它们之间的关系

首先MyBatis基于Java的一个ORM(对象关系映射框架),作用就是将数据库中的数据转换为可操作的Java对象,这既是它的核心概念,又是它的全部用法。

前置:

  1. 需要mybatis-config.xml来编写MyBatis的基础配置
  2. mybatis-mapper来对SQL进行封装

简单的使用:

public void test1(){
        InputStream resourceAsStream = OriginMybatisTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        System.out.println("hello world");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
				// 获取session执行mapper
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
				// 为了省事就直接写了Map
        Map<String, String> userById = mapper.getUserById(1);
        userById.forEach((k,v)->{
            System.out.println(k+":"+v);
        });
        sqlSession.close();
    }

这种使用方式是我们最常用的方式,也是Spring也是基于这一种方式进行的封装,我们只需要传入对象的接口实现,MyBatis通过比对就可以找到对应的标签,但是除了这种还有一种比较原始的方式:

InputStream resourceAsStream = OriginMybatisTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        System.out.println("hello world");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession();
        List<Object> objects = sqlSession.selectList("mybatis.study.mapper.UserMapper.getUserById");
        System.out.println(objects);

直接使用SqlSession中的原有的select、insert、delete,其实这也是MyBatis底层使用的方法.

sqlSession.selectList("mybatis.study.mapper.UserMapper.getUserById"); sqlSession.delete(""); sqlSession.update(""); sqlSession.insert("");

在最开始使用MyBaits的时候,使用的都是第二种方式,但是第二种方式写起来太繁杂了,并且无法表达准确的含义,便出现了第一种用,而第一种开始本质上就是对第二种开发的封装(代理设计模式).

在mybatis中将对象分为了两类,一类是数据存储类型,一类是数据操作类型.

MyBatis的数据存储类型对象

Configuration

mybatis-config.xml作为配置文件,对于Java来说如果想要使用配置文件中的属性,那就必须要读取到JVM中的一个类里面,只有这样我们才可以使用并且因为每次读取都是要耗费IO资源的,因此也避免了重复读取,达成了复用的目的;与mybait-config.xml对应的类就是Configuartion,位于org.apache.ibatis.session.Configuration

首先,在这个类中有许多属性是与config文件中对应的,即使是名字也起的非常相近,只拿几个做为对比

mapUnderscoreToCamelCase(config.xml)---->mapUnderscoreToCamelCase(configuration)
cacheEnabled(config.xml)---->cacheEnabled(configuration)
useGeneratedKeys(config.xml)---->useGeneratedKeys(configuration)

<mappers/> ---> loadedResources 封装了mapper标签内的resource路径 ---->mappedStatements 进一步解析mapper.xml中的各种标签(select,insert,update,delete)进行了封装

可以看到这些属性名称是基本上相同的,这就是配置文件属性向Java对象的转变,在加载过程中会将所有的配置文件属性都迁移到一个对象当中进行处理。除了这些属性外,当然还有typeHandlerstypeAliasesd、plugins等等都会封装进Configuration 以备后续使用.

第二,configuration作为一个核心类不仅仅只是对config.xml文件的一个映射,它还对XXXmapper文件中属性进行了收集:

	// mappedStatement就是对select,insert,update,delete标签的封装
	protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
	// caches就是对缓存的存储
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
	// resultMap用来处理对象映射关系,放在这里就可以判断一个resultMap并不从属于书写它的mapper文件,而是一个公用的状态,因此当出现两个mapper中有相同的resultMap也是不允许的
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
	// paramMap 也是只不过现在很少用了。
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");

只需要看到这几个对象的名字就应该知道其中的意思了

第三,上面我们提到了mybatis中的对象总体分为两类,而数据操作类对象初始化其实也是在configuration中完成的:

	// 对参数进行解析和封装,比如我们的对象是如何被解析成一个一个属性的
	public ParameterHandler newParameterHandler()
	// 对返回值进行处理,封装成对应的返回值类型
  public ResultSetHandler newResultSetHandler()
	// 将SQL语句进行封装,比如替换#{},${}组装成一条完整的SQL语句
  public StatementHandler newStatementHandler()
	// 发起真正的JDBC请求	
  public Executor newExecutor()

去掉了参数信息,只看方法名称更加直观一些,这些操作类对象就是MyBatis可以发起JDBC请求的核心,这些类的初始化也是configuration中完成的.

综上所述,可以说Configuration就是MyBatis中最核心的类了,我们也可以认为Configuration对象就是这些核心类的工厂类!!!

MappedStatement

MapperStatement封装的就是一个一个的标签,也就是在Mapper.xml文件中书写的select、update、delete、insert这些标签,因此在MyBatis中这些标签属于一个打算的状态,而不是像我们书写的时候那样那个标签属于那个mapper.xml,不是这样的,它们是一个公用的状态.

因此MappedStatment中就封装了mapper.xml的属性,并且属性中有一个是Configuration!!!可以对比一个两者的属性.

	
	private Configuration configuration;
  private String id; id略不同:是标签的所属mapper的namespace+id
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
	
	private SqlSource sqlSource;
<select id="getUserById" 
	resultType="java.util.Map" 
	fetchSize="" 
	parameterMap="xxx"
	statementType="" 
	timeout="xxx">
        select * from t_emp where ic_no = 0108026
   </select>

注意通过观察我们会看到所有的标签中都会存在一个东西,非常重要的东西,就是SQL语句,如果没有SQL语句那我们执行什么呢?对吧!而且在书写形式上面,SQL语句其实就是select这种标签内部,那说明对于SQL的封装肯定也在MappedStatement这个对象内部,这个对象是什么呢?就是SqlSource 这个接口的实现方法为getBoundSql(),它的返回值BoundSql就是我们的主角。

BoundSql.java

public class BoundSql {
	sql就是我们的SQL语句,那肯定是一个字符串类型。
  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;
}

这就是MyBatis中两个比较核心数据存储类型的对象.

数据操作类对象

说完存储对象Configuration和MappedStatement,那总得有东西来使用存储的这些数据吧,不然的话这些数据只是搁那放着也没啥太大的意义阿,所以这就引出了我们接下来的数据操作类对象,其本质就是使用存储的数据完成一次次的SQL请求。

下面简单先介绍一下各个对象:

Executor

这个对象故名思意就是一个执行器,这个执行器就是封装了SQL请求,在最前面的例子里,是使用的SqlSession执行的,而SqlSession底层就是使用的这些个执行器。

在执行器中主要包含了三类方法:

  • 执行查询和更新(增删改)SQL操作:query() update()
  • 事务相关,可以在接口内部很明了的看到两个方法:commit、rollback
  • 缓存相关,这个缓存就是MyBatis中的缓存吧?

执行器的实现也有以下几种:

BaseExecutor: 作为一个适配器,实现了基础方法,是一个抽象类

SimpleExecutor:一个简单而又最常用的执行器,也是Mybatis默认的执行器。

ReuseExecutor:一个复用的执行,复用的目标就是Statement,要知道所有的SQL,对于JDBC来说都是封装进Statement进行执行,而这里对Statement进行复用,就简化解析的过程。但是呢?这个复用的条件非常严苛,原来的SQL语句多一个空格、大小写不一致都会导致复用失败,所以使用的还是很少的。

BatchExector:批处理执行器,在同一个连接上执行多个SQL语句。

StatementHandler

在JDBC中最终与数据进行交互的对象就叫做Statment,而Mybatis实际上就是JDBC的封装,因此StatementHandler就是对Statement的封装。

在上面我们说了Executor是封装了对SQL的请求操作,那么这个被封装的请求操作就是StatmentHandler。

看一下这个接口中类便更加豁然开朗:

	**注意第一个入参就是connection!!**
	Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  void parameterize(Statement statement)
      throws SQLException;
	**对数据库的批量操作**
  void batch(Statement statement)
      throws SQLException;
	**对数据库的增删改操作**
  int update(Statement statement)
      throws SQLException;
	**对数据库的查询操作**
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
	**游标**
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

	**之前提到的BoundSql!!,就是要执行的SQL语句**
  BoundSql getBoundSql();
	
  ParameterHandler getParameterHandler();

通过方法的分析,就更加明显的知道这个接口的核心作用就是用来执行SQL操作的,除此之外啥也不干!!

StatementHandler作为接口也有几个实现:

  • BaseStatementHandler: 作为一个抽象类,实现一些通用操作
  • CallableStatementHandler 用于存储过程
  • PreparedStatementHandler 用于预处理
  • SimpleStatementHandler 简单SQL

以上三种具体的实现也对应之前说过的三种枚举类型:

public enumStatementType {
STATEMENT,PREPARED,CALLABLE
}

ParameterHandler

parameterHandler的目的就是将Mybatis中的占位符换成JDBC中的参数,比如最鲜明的就是:#{xxx} —>? ,对于预编译的Statement来说需要填充的sql中应该是一个?而不是#{},这是不符合JDBC规范的。甚至说,我们在编写接口的时候会使用@Param来限定属性的名称,那么如何将对应的@Param替换为?都是由parameterHandler完成的

public interfaceParameterHandler{

  Object getParameterObject();

voidsetParameters(PreparedStatement ps)
throwsSQLException;

}

这个接口就很简单了,获取参数对象也就是,最后任何参数对象都会被封装进一个Map中。设置参数对象。

ResultSetHandler

看到ResultSet就明白了一半了,这个对象不就是JDBC的返回值嘛,那么后面加了一个Handler就表明就是用来处理ResultSet的,因为Mybatis最后可以根据我们设置的resultType、resultMap直接帮助我们返回我们指定的方法返回类型,这个操作就是由ResultSetHandler完成的。

public interface ResultSetHandler {
	处理resultSet,但是传入的是Statement,因为Statement 中除了resulrSet还可以包含更多属性
  <E> List<E> handleResultSets(Statement stmt) throws SQLException;

  <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;

  void handleOutputParameters(CallableStatement cs) throws SQLException;

}

TypeHandler

因为在于数据库交互的过程中会涉及到类型转换问题:(java)string—>(mysql)varchar , (java)int —>(mysql)number,这些类型的差异就需要有一个组件来完成相互的转化,这个组件就是TypeHandler。而TypeHandler发挥作用的地方就是在parameterHandler和resultSetHandler当中了。

小结

这样我们就可以构成一个非常简单的关系了:

图片转存失败,建议将图片保存下来直接上传

至此MyBatis的核心接口以及接口之间的关系就告一段落了。