前文回顾:
在前一篇文章中我们已经知道了在Mybatis中我们所书写的配置文件(mybatis-config.xml)最终会被封装成为Configuration对象,今天我继续研究Mybatis中的其他核心对象。
一、SqlSession
1、SqlSession简介
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//通过SqlSessionFactory创建SqlSession (try-with-resource,会自动关闭连接)
try (SqlSession session = sqlSessionFactory.openSession()) {
PersonMapper personMapper = session.getMapper(PersonMapper.class);
}
}
如上述代码所示,Mybatis拿到了输入流,封装了Configuration对象之后又创建了SqlSessionFactory(工厂模式)这个对象,用于创建SqlSession。SqlSessionFactory只是一个工厂没有太多值得研究的地方,工厂的作用就是创建我们所需要的对象,所以这里我们研究的重点放在**SqlSession**上。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2816324/1658735969761-7eb9cba9-1460-4c46-bfdb-e9e70033ed6c.png#clientId=ud80567cf-dda2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=762&id=u7545a9ad&margin=%5Bobject%20Object%5D&name=image.png&originHeight=953&originWidth=1901&originalType=binary&ratio=1&rotation=0&showTitle=false&size=259104&status=done&style=none&taskId=u62b2a8fa-dabf-4769-8ca4-716a4fe1aca&title=&width=1520.8)<br />首先SqlSession是一个接口,其中定义了许多的和数据库操作相关的方法,例如查询列表(selectList)、插入数据(insert)等等。这里采用了门,SqlSession只是一个门面真正做数据库操作的是Executor(执行器,这个后续也会讲解)。
2、SqlSession中常用的方法
数据的操作归纳起来无非就是两种修改(update:增删改)和查询(query),所以我们各找一个方法来探究Mybatis是如何实现对数据库的操作的。
2.1、SelectList方法
从源码中不难看出selectOne其实调用的就是selectList方法只不过返回list首个元素,所以我们以selectList方法为切入点。
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//通过执行器进行查询操作
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();
}
}
源码分析:
(1)从配置类(Configuration)中获取MappedStatement(后续会分析)对象,该对象分装了我们所写的xxxMapper.xml文件。
(2)通过Executor(执行器)来执行具体的query操作,并返回结果集。
2.2、增删改操作
在Mybatis中,增删改统一被归为update操作,而查询则为query。所以研究增删改其实只要研究query方法即可。可以看到update方法和query方法非常的相似,都是获取到MappedStatement对象然后通过执行器来执行具体的数据库操作。
2.3、SqlSession小结
至此我们已经了解了SqlSession这个接口,这个接口定义了一些列的数据库操作,而数据库操作在Mybatis被分为了Update和Query两种。该接口代码相对简单采用了门面设计模式来简化了开发者的使用成本,接下来我们需要探究其中实际发挥作用的两个对象MappedStatement和Executor。
二、MappedStatement
1、MappedStatement部分源码
MappedStatement翻译过来为映射语句,可以看出我们自己编写的Mapper文件中的属性都能在MappedStatement中找对对应的属性。所以我们可以得出结论,xxxMapper.xml最终会被封装成为MappedStatement,接下来我们分析几个常用的属性。
2、常见属性分析
2.1、ResultMap
在很多时候我们会定义一个ResultMap用于映射数据库字段和Java实体类,可以看出ResultMap中的属性和我们再Mapper中编写的ResultMap节点也是能对应上的。
2.2、SqlSource
我们所编写的Sql在Mybatis中是如何被封装的呢?可以看到有一个名为SqlSource的属性,进入该属性发现是一个接口,并且返回一个名为BoundSql的对象,部分源码如下。
//我们编写的Sql
private final String sql;
//参数映射
private final List<ParameterMapping> parameterMappings;
//Object类型的参数,例如插入数据时我们传入的实体类
private final Object parameterObject;
//额外的参数
private final Map<String, Object> additionalParameters;
//元数据参数
private final MetaObject metaParameters;
2.3、小结
MappedStatement中还有许多的属性,这里不一一列举,分析过程都大同小异。我们需要知道的是在Mybatis中我们编写的Mapper会被封装成一个个MappedStatement对象即可。
三、Executor
1、Executor简介
Executor翻译过来为执行器,主要用于执行我们所编写的SQL。前面的小结探究了SqlSession接口,定义了许多数据库操作相关的接口,而实际上进行数据库操作的则是Executor对象。可以看到Executor中的方法与SqlSession中基本一致,这也佐证了Executor是实际处理数据库操作的类。
2、源码解读
我们进入Executor其中一个子类BaseExecutor探究一下Executor是如何操作数据库的,接下来以Query方法为切入点。(删除了部分代码,只保留核心)
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//删除部分代码
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--;
}
//删除部分代码
return list;
}
首先Mybtis会做一系列的校验(这部分代码删除了),接着会尝试从缓存中获取数据,如果获取不到则会尝试从数据库查询数据。这也是我们要关注的重点。进入**queryFromDatabase**方法
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//省略非核心代码
try {
//重点!!!
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
//省略非核心代码
return list;
}
该方法的核心为doQuery,继续进入,会发现doQuery是一个抽象方法,有4种不同的实现。我们选择其中的SImpleExecutor<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2816324/1658740723858-f1d1e1c0-4ab6-498d-b510-f3887429632b.png#clientId=ud80567cf-dda2-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=148&id=uf525251f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=185&originWidth=1447&originalType=binary&ratio=1&rotation=0&showTitle=false&size=50946&status=done&style=none&taskId=u14bb08f6-0e31-497f-a030-25248276f70&title=&width=1157.6)<br />进入SimpleExecutor中的doQuery方法,首先会获取配置类(Configuration),然后从配置类中获取到StatementHandler(处理器,后续也会分析),再拿到Statement(JDBC里的东西!!!)
@Override
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//核心方法
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
进入handler中的query方法:发现statment.execute
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
//核心!!!
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
最终在com.mysql.cj.jdbc.StatementImpl中我们找到了java操作数据库相关的代码,也就是说Mybatis其实就是对JDBC的封装,使得我们的开发更加的简便。
四、总结
本文讲述了Mybatis中的三个核心对象、SqlSession、MappedStatement、Executor并介绍了其各自的作用
SqlSession:面门,对外提供操作数据库的接口。
MappedStatement:封装了我们所编写的Mapper文件。
Executor:用于执行数据库操作,底层是通过Statement(属于JDBC)来实现。
最后附一张简易的流程图。
接下来会对剩下几个对象逐一分析,并且会进行一次详细的Debug。希望对你有所帮助。