怎么进行MyBatis源码分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
What is MyBatis?
官方描述:
MyBatis是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
首先来一个简单的基础的mybatis工程
<dependencies>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties" />
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.username}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ch.mybatis.mapper.UserMapper">
<select id="findById" resultType="com.ch.mybatis.pojo.User">
select * from tb_user where id = #{id}
</select>
</mapper>
public interface UserMapper {
User findById(Integer id);
}
public static void main(String[] args) throws Exception {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.findById(20);
}
开始分析
// 点击.getMapper 进去
UserMapper userMapper = session.getMapper(UserMapper.class);
-------------------------------------
MapperRegistry.class:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 分析这里
// MapperProxyFactory: 看名字先猜测是一个 Mapper的代理工厂类
//
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 点这里进去
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
MapperProxyFactory.class:
public T newInstance(SqlSession sqlSession) {
// MapperProxy: 点这个进去
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
// 实现了 InvocationHandler 所以这个类是动态代理类
// 代理了谁?
// 前面的代码: UserMapper userMapper = session.getMapper(UserMapper.class);
// 所以是代理了 UserMapper
// 问题? UserMapper是一个抽象类,而且没有实现
// 看起来就是一个动态代理模式,但是jdk动态代理抽象接口必须要有实现类
// 所以这里什么情况?
public class MapperProxy<T> implements InvocationHandler, Serializable { }
JDK动态代理 和 这里的mybatis"动态代理?" 对比
## Jdk正宗版本:
IProxyInterface InvocationHandler
| |
ProxyImpl <- ProxyDemoProxy
---------------------
## Mybatis版本:
UserMapper InvocationHandler
| |
??? <- MapperProxy
接着分析
// 上面的流程我们知道 这里是通过动态代理拿到的一个 UserMapper 的实例
// 打断点可以看到这里的 userMapper 的实例化内存地址是:
// org.apache.ibatis.binding.MapperProxy@3f197a46
UserMapper userMapper = session.getMapper(UserMapper.class);
// 接着怎么使用的呢?
// MapperProxy类实现了InvocationHandler,所以会执行里面的 invoke 方法
// 然后我们看看 invoke 里面做了什么
// MapperProxy.class
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 这里考虑一下 是走 if 还是走 else ?
// 因为上面的分析得出 mybatis不是一个纯正的动态代理
// 所以这里一般会走 else
// 既然不会走这里为什么还有这行代码? 不能因为不是它就不要它(有点绕口)
if (Object.class.equals(method.getDeclaringClass())) {
// 什么时候会进这里?
// UserMapper userMapper = session.getMapper(UserMapper.class);
// userMapper.toString(); 这种情况会进这里
return method.invoke(this, args);
}
else {
// 接着我们看看 else 里面做了什么? 点 invoke 进去看
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
// 这个抽象类 有两个实现类 PlainMethodInvoker 和 DefaultMethodInvoker
// 进哪个?
// 看上面的 return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
// 发现最后的走到了 return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
// 所以看 PlainMethodInvoker
interface MapperMethodInvoker {
// 抽象类,快捷键进入实现
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
// 直接看 invoke 方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 在看 execute
return mapperMethod.execute(sqlSession, args);
}
}
// MapperMethod.class
// 上面我们调用的是 findById 是一个 select 语句,所以直接看 case SELECT:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
// 断点走到了这里,再看 selectOne
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
}
return result;
}
// DefaultSqlSession.class
public <T> T selectOne(String statement, Object parameter) {
// 看 selectList 里面
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// executor: 顶层接口,定义了执行器的一些基本操作
// BaseExecutor:实现了Executor接口,实现了执行器的基本功能。
// 具体使用哪一个Executor则是可以在 mybatis 的 config.xml 中进行配置的。默认为SimpleExecutor;
// <settings>
// <setting name="defaultExecutorType" value="SIMPLE"/> <!--SIMPLE、REUSE、BATCH-->
// </settings>
// 看 BaseExecutor
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();
}
}
// BaseExecutor.class
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建一级缓存的键对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用下面的 query 方法
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
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--;
}
if (queryStack == 0) {
for (BaseExecutor.DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
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 {
// 到了这里,点下去看
// 点进去是个抽象的,看实现,上面提到的默认使用的 SimpleExecutor
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;
}
// SimpleExecutor.class
// 注意这里面的逻辑
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();
// RoutingStatementHandler.delegate = PreparedStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// com.mysql.jdbc.JDBC42PreparedStatement@757acd7b: select * from tb_user where id = 20
stmt = prepareStatement(handler, ms.getStatementLog());
// handler = PreparedStatementHandler
// 点进去继续看
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// PreparedStatementHandler.class
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 这几行代码是不是很熟悉?
// 就是jdbc的执行代码
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// resultSetHandler 结果集处理器
return resultSetHandler.handleResultSets(ps);
}
总结
1.分析得出mybatis的最底层还是封装的jdbc代码。
2.看起来很像动态代理,
其实是根据 UserMapper的抽象方法名去对应的在mybatis的xml配置文件中查找对应的id(<select id="xxx">)而获得到sql语句
(为什么UserMapper.xml中的 <mapper> 标签中的 namespace 属性的作用就在这里)
到到了sql语句,最底层用jdbc代码执行sql。
3.下面这两行代码在build的时候就把xml里面的sql语句解析出来出来放在
DefaultSqlSessionFactory(SqlSessionFactory)中了 (build最后返回了 new DefaultSqlSessionFactory(config))
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
关于怎么进行MyBatis源码分析问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/chen0000/blog/4483423