Mybatis执行器
SQL执行流程
SqlSession
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 先获取Mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(2);
System.out.println(user);
// 直接调用sqlSession的方法进行查询
User user1 = sqlSession.selectOne("selectById", 2);
System.out.println(user1);
在我们使用mybatis的过程中,我们是先使用
SqlSessionFactoryBuilder
根据mybatis核心配置文件构建一个SqlSessionFactory
,然后通过SqlSessionFactory
获取SqlSession
。对数据库的操作,对于使用方来说,直接使用
SqlSession
进行增删改查
。我们可以直接调用SqlSession
的方法,也可以获取到定义的Mapper对象之后,调用其方法。实际上
sqlSession.getMapper()
的底层还是调用的SqlSession
的增删改查方法。SqlSession
是一个接口,具体的实现是在实现类中。对于一般的查询来说,是在DefaultSqlSession
。image-20221223175801356 image-20221223175938917 image-20221223180031500 image-20221223180122876
SqlSession查询流程分析
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 第一个参数是statement(Mapper接口里面定义的方法名称),第二个是parameter
User user1 = sqlSession.selectOne("selectById", 2);
System.out.println(user1);
}
1.程序进入到
DefaultSqlSession
里面的selectOne()
方法

最终会调用DefaultSqlSession
里面的selectList()
方法:
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用具体的执行器进行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.进入到
CachingExecutor
的query()方法

进行一些处理之后,调用CachingExecutor
的query()方法
:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取二级缓存:如果mybatis没有开启二级缓存的话,cache就为null
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存里面获取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 缓存没有命中,调用其他Executor查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 查询到的结果放入二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
3.进入到
BaseExecutor
的()方法

queryFromDatabase
方法:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 执行查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
其中doQuery
是BaseExecutor
内部定义的一个抽象方法,由子类进行实现:
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
mybatis默认的配置,实现是SimpleExecutor
。
4.进入到
SimpleExecutor
的doQuery()
方法
@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);
// 获取Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
prepareStatement()
方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连接
Connection connection = getConnection(statementLog);
// 通过连接获取Statement
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}

我们都知道mybatis是对jdbc的封装,到这里我们可以看到jdbc的影子了。先拿到Connection
(编写JDBC的时候,是通过驱动获取的),然后通过Connection
获取PreparedStatement
,通过PreparedStatement
先进行sql和参数填充,然后执行查询,最后获取结果集。
// 1.注册驱动
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver= (Driver) aClass.newInstance();
DriverManager.registerDriver(driver);
// 2.获取连接
String url = "jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, username, password);
// 3.预处理sql
String sql = "select * from user where id = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);
// 4.执行查询
ResultSet resultSet = preparedStatement.executeQuery();
getMapper动态代理查询分析
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取Mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectById(2);
System.out.println(user);
}
- 在使用Mybatis的时候,我们只要定义一个接口,然后再对应的mapper文件中进行SQL的编写。然后就可以通过
SqlSession
获取的Mapper对象,执行查询。 - 这里有个问题,我们定义的是接口,是不能直接调用的。而接口要使用,就必须要有实现类。我们没有定义实现类,那么肯定就是mybatis为我们提供了实现类。关键就在
sqlSession.getMapper
这行代码了。通过debug去发现mybatis究竟是怎么为我们创建一个实现类的。 - 这里还有一个问题,假设mybatis内部实现了我们定义的接口,就可以返回实现类。观察
sqlSession.getMapper(UserMapper.class)
,发现传入参数是一个Mapper接口的Class对象。通过反射可以拿到UserMapper
的接口信息。但是我们定义的接口很多,如果mybatis每个都去实现的话,比较麻烦且不显示。而且每个接口的操作都是类似的,根据参数查询数据库,也会有许多重复的逻辑
。这时我们可能想到了动态代理
,动态代理可以对接口进行拦截
,并且可以对方法进行增强,返回数据等。既然Mapper接口需要一个实现类,那么我们就用动态代理生成一个实现类,并实现接口定义的查询逻辑,最后把查询结果返回。这样对于用户来说,只需要定义接口和编写mapper文件,就可实现与JDBC相同的功能。但是不需要去考虑SQL的拼接,参数的封装等等。这些重复且复杂的逻辑,mybatis使用动态代理来进行统一处理了。
获取Mapper对象
1.进入
DefaultSqlSession
的getMapper()
方法

2.进入
Configuration
的getMapper()
方法

3.进入
MapperRegistry
的getMapper()
方法

4.进入
MapperProxyFactory
的newInstance()
方法

使用了JDK的动态代理,代理逻辑在最后一个参数,这个对象必须实现InvocationHandler
接口,处理代理逻辑。
查看MapperProxy
类,发现确实实现了InvocationHandler
接口。

invoke()
方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
由此可以知道的是,Mybatis底层使用了JDK的动态代理,代理的是我们定义的Mapper接口。Mybatis为我们的接口返回了一个代理对象。所以我们可以直接调用接口里面的方法。
执行查询
获取到了Mapper对象,接下来看看具体的查询逻辑是怎么实现的。

1.进入
MapperProxy
中。当我们调用Mapper代理接口的方法时,会进行代理类中的invoke()
方法。这是动态代理的知识。

进入MapperProxy
自定义的invoke()
方法:

2.进入
MapperMethod
的execute()
方法:

这里进行增删改查的判断,我们使用的是查询,会进行SELECT:

我们可以看一下,返回结果是多个的情况:
executeForMany()方法
:

executeForMap()方法
:

而且还可以发现,对于插入、修改、删除
也是这样:

由此可以得出
sqlSession.getMapper(Class对象)
的执行逻辑:
- 该方法通过JDK动态代理拦截传入的Mapper接口,返回一个Mapper代理对象。具体的代理逻辑在
MapperProxy
中,该类实现了InvocationHandler
接口,代理逻辑在invoke方法中。- 当我们调用Mapper接口中的方法时,会被代理类
MapperProxy
拦截。最终会使用MapperMethod
类的execute()
方法进行查询。MapperMethod
类的execute()
方法首先会进行增删改查的判断。对于增删改查,都是调用的SqlSession
中的方法。
Mybatis执行器概览图
