在文前中已经提到过, 除了使用XML配置的方式, 还可以使用包扫描和接口的方式配置mapper. 其中包扫描的方式底层也是封装了接口配置方式实现的. 今天就一起看接口配置方式是如何实现的?
一. mapper接口添加
mapper接口也是添加configuration配置类中, 由全局配置统一管理. 对configuration不了解的, 可以参考一分钟进入mybatis的世界.
代码语言:javascript复制configuration.addMapper(CountryMapper.class);
1. mapper注册, configuration添加mapper时, 会通过MapperRegistry进行记录和解析.
mapper接口会被封装成MapperProxyFactory并保存knownMappers集合中; MapperAnnotationBuilder会解析mapper并生成MappedStatement; 这里留一个问题,mapper为什么是封装成工厂(MapperProxyFactory)而不是具体代理(MapperProxy)呢?
代码语言:javascript复制public <T> void addMapper(Class<T> type) {
// ...
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
// ...
}
二. mapper执行
与XML方式类似也是从SqlSession中获取mapper代理对象
代码语言:javascript复制SqlSession sqlSession = sqlSessionFactory.openSession();
CountryMapper mapper = sqlSession.getMapper(CountryMapper.class);
1. 从knownMappers集合中获得MapperProxyFactory对象, 并根据当前SqlSession创建mapperProxy对象.
代码语言:javascript复制public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// ...
return mapperProxyFactory.newInstance(sqlSession);
}
2. 创建mapper代理对象MapperProxy 到这里也就可以回答上面的问题了, 为什么初始化时mapper封装的是MapperProxyFactory, 而不是mapperProxy? 原因就在于, 每个mapper在执行时是要绑定具体SqlSession, 处理事务,SQL连接等操作的, 所以每次需要根据上下文创建新的mapperProxy.
代码语言:javascript复制public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
3. 代理对象MapperProxy, 实现InvocationHandler代理接口
代码语言:javascript复制public class MapperProxy<T> implements InvocationHandler, Serializable {
// ...
}
4. 查看代理对象MapperProxy的invoke()方法, SQL执行逻辑在MapperMethod类中.
代码语言:javascript复制public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
5. 跟进MapperMethod对象的执行方法, 可以发现最后还是执行了sqlSession.selectList()方法, 获取MappedStatement对象执行的. 和前篇文章分析的mybatis执行方式是一样的.
代码语言:javascript复制private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
// ...
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
// ...
return result;
}
总结
通过注解方式定义mapper接口, 通过代理工厂(MapperProxyFactory)实现动态代理, 每次使用时,会根据SqlSession创建代理对象(mapperProxy), 最终还是会通过MappedStatement对象执行SQL.