这一篇需要有第八篇的知识,只要你懂了那个,再看这个自然就明白了,因为这个底层还是要用到sqlSession对象去与数据库交互的
还是首先来看个使用例子
使用
public class SqlSessionTest {
public static void main(String[] args) throws Exception{
//获取SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = FactoryUtils.sqlSessionFactory();
//打开SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//执行接口
User user = userMapper.selectUser(1);
System.out.println(user);
}
}
FactoryUtils上一篇有,这里就不细说了,看这样的使用是不是很方便,但是有个使用规范就是接口的全类名要作为xml文件的namespace,方法名要跟xml里面每个操作项的id一致,要么你也可以通过注解在接口方法上标记要执行的语句,但是一般不这么用,因为sql过于复杂的时候比xml还要难理解
@Select("select * from user where id = #{id}")
User a(@Param("id") Integer id);
使用还是比较简单的,那么mybatis是怎么做的呢,肯定是需要借助代理对象的,下面就具体来看下源码是怎么做的,入口就在于 sqlSession.getMapper(UserMapper.class)
sqlSession.getMapper
最终会到这里来
org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//之前注册的时候就已经把它放到这个knownMappers Map中了
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);
}
}
这里还是比较简单的,就是从map中获取了一个代理对象创建工厂,然后再使用工厂创建代理对象,看下代理对象是怎么创建的
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用jdk进行动态处理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//创建代理对象MapperProxy实现了InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//创建代理
return newInstance(mapperProxy);
}
很明显就可以知道使用了jdk动态代理,那么核心就在于MapperProxy中了,我们下面再去看一些这个类的执行,因为代理过程中就会调到InvocationHandler接口的invoker方法中去
MapperProxy
该对象实现了InvocationHandler接口,并作为参数传到代理对象中了,那么当代理对象执行任意方法的时候就会到达这个类的invoke方法中,下面就来看看这个方法的实现
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 if (isDefaultMethod(method)) {
//如果是默认方法执行默认方法的逻辑,兼容java7方法?
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//根据方法获取MapperMethod并且进行缓存,不用重复生成
final MapperMethod mapperMethod = cachedMapperMethod(method);
//真正的方法执行入口
return mapperMethod.execute(sqlSession, args);
}
这里面核心就两个方法,我们都看一下
cachedMapperMethod
private MapperMethod cachedMapperMethod(Method method) {
//从缓存中获取
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//新创建一个MapperMethod对象,里面涉及到MethodSignature的创建,这里就不细说了
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
//放到缓存中
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
可以看到,这个逻辑还是比较简单的,除了MethodSignature的创建可能麻烦点,有兴趣的可以自己点进去看一看
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:
//方法没有返回值并且有ResultHandle,进行分支处理
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//多条返回值
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回类型未map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//返回cursor
result = executeForCursor(sqlSession, args);
} else {
//当条返回
Object param = method.convertArgsToSqlCommandParam(args);
//委托给sqlSession查询
result = sqlSession.selectOne(command.getName(), param);
}
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;
}
这里面逻辑就很清晰了,代理方法最终还是委托给了sqlSession对象进行处理,可以看看executeForMany
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
//转换参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
//是否需要绕过多少行
RowBounds rowBounds = method.extractRowBounds(args);
//委托sqlSession.selectList
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
//委托sqlSession.selectList
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
再看进去就没啥了,是上篇的逻辑了
这篇就比较简单了,因为底层的执行逻辑在上篇就说了
本文深入解析 MyBatis 中 SqlSession.getMapper 的工作原理,通过 Java 动态代理实现接口方法的调用,最终委托给 sqlSession 对象执行 SQL 操作。讲解了 MapperRegistry 如何获取代理对象,MapperProxy 如何执行方法并调用 sqlSession 的不同 CRUD 操作。

2272

被折叠的 条评论
为什么被折叠?



