使用 SqlSession 的接口查询比较麻烦,MappedStatement 的 id 也是字符串容易出错,也不符合面向接口的编程方式。所以 mybatis 也支持使用 mapper 接口的方法来简化操作
初始化
前面分析初始化过程的时候有说到 MappedStatement 的维护,这一步是在解析映射文件的时候完成的,mapper 的注册也是在这一步,看下关键源码:
// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 就会走这里了
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
...
}
}
}
}
@MapperScan 注解会扫描所有的 mapper 接口文件,是根据配置的包名来扫描的,关键方法就是 addMappers()
,看下这个方法:
// org.apache.ibatis.session.Configuration#addMapper
public void addMappers(String packageName, Class<?> superType) {
// 维护 configuration.mapperRegistry
mapperRegistry.addMappers(packageName, superType);
}
// org.apache.ibatis.binding.MapperRegistry#addMappers
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 调用了 addMapper 方法
addMapper(mapperClass);
}
}
// org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 维护 configuration.mapperRegistry.knownMappers 属性(创建代理对象的包装工厂)
// SqlSession.getMapper(type) 获取一个包装工厂发,再根据包装工厂生成代理对象
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 主要是这里
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
parser.parse()
做了什么
// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 这个方法会读取映射文件,维护 configuration.mappedStatements,原理和前面分析的一样
loadXmlResource();
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
parseCache();
parseCacheRef();
// 遍历 mapper 接口中的方法,type 就是接口的 class
for (Method method : type.getMethods()) {
if (!canHaveStatement(method)) {
continue;
}
if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
&& method.getAnnotation(ResultMap.class) == null) {
parseResultMap(method);
}
try {
parseStatement(method);
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
至此 configuration.mappedStatements、configuration.mapperRegistry 都维护好了
工作流程
先看 SqlSession.getMapper(xxxMapper.class)
底层
// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取 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);
}
}
怎么生成代理对象?
// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.session.SqlSession)
public T newInstance(SqlSession sqlSession) {
// 拿到工厂对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用下面的方法生成代理对象
return newInstance(mapperProxy);
}
// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
对象生成好了,就是调用方法了,其实调用的是代理对象的方法,显而易见会通过反射执行 invoke 方法
// org.apache.ibatis.binding.MapperProxy#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 先判断执行的方法是不是 Object 类的方法,比如tostring,hashcode 等方法,如果是,直接执行
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
// 判断执行的方法是否为默认方法(jdk8的新东西),如果是则直接执行反射执行
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 利用接口、方法等信息,构造 mapperMethod 并进行缓存
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 最终通过 MapperMethod 代理执行具体的数据库操作
return mapperMethod.execute(sqlSession, args);
}
再看下 execute 方法源码:
// org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result; // 返回结果
if (SqlCommandType.INSERT == command.getType()) { // INSERT 操作
// 处理参数
Object param = method.convertArgsToSqlCommandParam(args);
// 调用 sqlSession 的 insert 方法
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) { // UPDATE 操作
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) { // DELETE 操作
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) { // SELECT 操作
// 如果返回 void 并且参数有 resultHandler,调用 sqlSession.select()
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 如果返回不是 void,是多个结果,就调用 sqlSession.selectList()
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 如果返回类型是 MAP 则调用 executeForMap 方法
result = executeForMap(sqlSession, args);
} else {
// 否则就是查询单个对象
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
// 接口方法没有和sql命令绑定
throw new BindingException("Unknown execution method for: " + command.getName());
}
// 如果返回值为空 并且方法返回值类型是基础类型 并且不是 VOID 则抛出异常
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 来操作的,感觉是饶了一下,SqlSession 可以完成的工作,多饶了一圈 mapper,最终还是使用 SqlSession,SqlSession 的工作原理前面分析过,这里就不分析了
总结
- 根据配置扫描 mapper 所在的包,根据 configuration.addMappers() 方法来维护 configuration.mappedStatements、configuration.mapperRegistry 属性
- mappedStatements 就是 MappedStatement 对象集合
- mapperRegistry 属性里面维护了一个 knownMappers 属性,knownMappers 是个 map,key 是 接口,value 是创建接口代理对象的工厂对象
- sqlSession.getMapper() 就是获取 configuration.mapperRegistry.knownMappers 的值,然后创建一个代理对象
- mapper.method() 就是执行代理对象的方法,底层还是调用了 sqlSession 的方法
- sqlSession 又到 configuration.mappedStatements 找到具体的 MappedStatement
- 然后又是 Executor 根据 MappedStatement 查询数据(缓存、处理参数、创建连接、映射结果)