文章目录
Mybatis认识
我们知道,JDBC有四个核心对象:
(1)DriverManager,用于注册数据库连接
(2)Connection,与数据库连接对象
(3)Statement/PrepareStatement,操作数据库SQL语句的对象
(4)ResultSet,结果集或一张虚拟表
而MyBatis也有四大核心对象:
(1)SqlSession对象,该对象中包含了执行SQL语句的所有方法。类似于JDBC里面的Connection。
(2)Executor接口,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。类似于JDBC里面的Statement/PrepareStatement。
(3)MappedStatement对象,该对象是对映射SQL的封装,用于存储要映射的SQL语句的id、参数等信息。
(4)ResultHandler对象,用于对返回的结果进行处理,最终得到自己想要的数据格式或类型。可以自定义返回类型。
Mybatis执行原理跟踪
1.项目启动时,读取配置
- 通过jdbc连接信息注入bean:SqlSessionFactory
- 将扫描到的mapper.java接口信息,将接口中的方法都转换成MapperStatement对象,用于记录这个Mapper.java接口方法的方法参数、返回值和sql语句等等信息
2.获取SqlSession
SqlSession定义了事务提交回滚sql执行等能力,并且有一个ExecutorType的参数,用于控制Executor的类型
// 批处理,并且不自动提交事务
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
M mapper = sqlSession.getMapper(mapperClass);
Executor:
- SimpleExecutor:默认类型,对于每一条执行的sql每执行一次都会重新预编译加载一次
- ReuseExecutor:执行相同的sql语句,不会重复编译加载
- BatchExecutor:批量执行策略,将批量的多条sql语句构成的Statement暂存到list中,flush的时候一次性提交到mysql服务器,避免重复的和mysql服务器建立连接交互
- CachingExecutor:二级缓存策略,其中维护了一个map来保存查询结果,二级缓存在mapper文件层生效
3.反向代理方法执行
getMapper方法跟进去,最终可以看到实际上返回的Mapper实例其实是一个MapperProxy的反向代理出来的实例对象,每当执行任何方法,都会从MapperProxy的invoke中过,判断该方法是否是Object的方法,是否是接口default方法,如果都不是的话则直接进入mybatis的执行流程,通过invoke的反射信息拿到Method对象,通过Method对象拿到MapperStatement的id,即类名全限定名+方法名(这里不区分方法签名,所以在mybatis中不支持方法重载)
MapperStatement中包含了我们要执行的Mapper.java接口的方法名,对应的sql,参数(参数最终都会注入到Map中,所以方法重载没有用),返回值,通过这些信息,生成StatementHandler对象用于构建sql,生成ParameterHandler用于处理参数,最终将参数和预编译的sql交由原生jdbc执行,最终拿到返回结果
4.ResultHandler结果集处理
在拿到返回数据后,通过ResultHandler对结果集进行封装,使用TypeHandler,使用反射中的set方法将属性注入POJO
实际上查询的结果封装由ResultSetHandler来处理,结合mapper.xml中的resultMap标签对应的对象ResultMap,将字段名称做映射,配合TypeHandlerRegister中注册的TypeHandler,按照数据库字段与TypeHandler映射的字段,将ResultSet中的结果先交给TypeHandler的getNullableResult方法处理成我们的理想java类型字段,再通过反射,使用标准java bean的set方法将字段设置到POJO中
MyBatis源码分析
示例代码
public void test () {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1L);
}
第一阶段
通过SqlSession获取Mapper代理实例
在上述的代码中,第一步,通过sqlSession获取UserMapper实例,因为UserMapper是我们定义的一个接口,所以真正使用时,使用的是mybatis为我们生成的UserMapper的代理实例,从这里入手看源码。
通过断点发现SqlSession的实例是DefaultSqlSession
通过如下调用栈跟踪源码
DefaultSqlSession.getMapper(Class)
Configuration.getMapper(Class, SqlSession)
MapperRegistry.getMapper(Class, SqlSession)
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // knownMappers实际上是项目启动时,mybatis能扫描到的需要被代理的mapper.java接口 // 这里拿到当前需要代理的接口的【代理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.newInstance(SqlSession)跟进去
mapperProxyFactory.newInstance(SqlSession)
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
通过这两个newInstance可以看出,最终生成的代理类是【
MapperProxy
】对象,
综上所述,最终通过getMapper(Class type),获取到的结果就是MapperProxy的代理对象,那么我们通过该对象执行的所有方法其实最终都是通过MapperProxy的invoke方法,下面我们对invoke方法进行解析
第二阶段
MapperProxy
对象的invoke
方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 假如MapperProxy对象是UserMapper的代理类,则此处判断我们调用的该方法属于哪个类,比如在我们没有重写toString方法时,调用toString方法,则toString方法属于Object.class,此处就是在判断我们调用的方法是否属于Object提供的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
// isDefault判断调用的方法是不是interface中的default默认实现的方法,此处不重要
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存map中获取到MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行MapperMethod对象的execute
return mapperMethod.execute(sqlSession, args);
}
MapperMethod
的execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
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);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
第三阶段
有空再补充