注意:若不理解代理模式和jdk动态代理请先了解再接下去阅读,效果更好。
1、首先举个简单例子来说明
1.1先创建Mapper
比作mybatis的xxxMapper,就是Mapper.xml对应的接口类。
public interface Mapper {
String call();
}
1.2创建MapperXml
MapperXml相当于mybatis执行数据库操作的代理类,每一个方法对应一个MapperXml。
public class MapperXml {
public String call() {
return "xxxx";
}
}
1.3创建代理的Handler类
jdk代理的实现就是实现InvocationHandler接口,实现invoke方法,当调用Mapper的方法时,实际是调用了MapperHandler的invoke方法。而mybatis的Mapper接口不能有重载方法名,就是因为在初始化时方法名作为key值,不能存在相同的方法名。
public class MapperHandler implements InvocationHandler {
//根据方法名保存一份对应的操作类。
private static final Map mapperXmlMap = new HashMap<>();
static {
//初始化默认
mapperXmlMap.put("call", new MapperXml());
}
public void addMapperProxy(String method, MapperXml mapperXml) {
mapperXmlMap.put(method, mapperXml);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//根据方法名取到对应的实现类,然后调用对应的方法。
return mapperXmlMap.get(method.getName()).call();
}
}
1.4创建SqlSession
此SqlSession相当于mybatis的SqlSession,根据Mapper的类型获取到相应的代理类。
public class SqlSession {
public static T getMapper(Class clazz) {
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MapperHandler());
}
}
1.5测试例子
public static void main(String[] args) {
Mapper test = SqlSession.getMapper(Mapper.class);
System.out.println(test.call());
//输出xxxx
}
总结:mybatis调用mapper接口其实就是返回的代理类,由代理类去执行数据库操作。
2、mybatis对应的源码解释
当执行自己的Mapper接口时,实际调用的是MapperProxy的invoke方法,而MapperProxy相当于上面提到的MapperHandler,MapperMethod相当于MapperXml,下面是它的主要方法:
@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)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//根据方法名找到对应的操作类
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行操作
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
//看map是否存在这个类,若没有则新建
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
最后看看MapperMethod的execute方法,根据sql类型执行不同操作,最后都会调用sqlSession的方法,对数据库操作。若想了解更多可以看mybatis下的org.apache.ibatis.binding包,MapperProxyFactory主要创建Mapper的代理类,MapperRegistry主要缓存相应的代理对象,并提供查找添加等方法。
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);
}
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;
}