本文讲SQL执行过程中的第一步 获取执行对象
先上一张总流程图
- SqlSessionFactory构造的SqlSession对象都是 DefaultSqlSession 类型的
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.从 DefaultSqlSession 开始获取对象
AccountMapper accountMapper = SqlSession.getMapper(AccountMapper.class);
跟着一步一步往下走,发现是从 MapperRegistry 的 knowMappers 中获取的 MapperProxyFactory 对象,不管这个工厂类,先问一句什么时候加载进去的,对 knowMappers 使用进行溯源,发现只有一处调用了 put 方法
而其调用方法 addMapper 只有两处,其对外的调用都是 Configuration.addMapper,沿着这条线往上追溯,发现都是在解析的时候调用的
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 {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
四种方式都会尝试加载一番
其中配置package-name和mapper-class的都是直接 调用configuration.addMapper
而另外两种配置 xml 方式的则需要一个条件才会加载
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
- 需要配置文件的 namespace 属性可以当做类路径加载成功,然后才会将这个接口加入 MapperRegistry 的 knowMappers 中。
总之,这个属性就是在解析配置文件构造 Configuration 时加载进去的。
- 构造 MapperProxyFactory
private final Map methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
在这个 MapperProxyFactory 中我们发现了一个很眼熟的东西
Proxy.newProxyInstance(ClassLoader, Class>[], InvocationHandler)
这不是一个典型的JDK动态代理吗? 那么 MapperProxy 显然也实现了 InvocationHandler 接口
public class MapperProxy implements InvocationHandler, Serializable {}
所以 MapperProxy 是 Mybatis 通过JDK动态代理生成的一个代理对象,用于SQL的执行。
- 区别
上一篇我们讲过了代理模式,再来回顾一下这张图
我们发现 MapperProxy 的代理模式跟上面有一个很大的区别
请问你见过 Mybatis 接口类的 *Mapper.java 的实现类吗?
是根本就不存在这种实现类。
所以 MapperProxy 重写的 invoke 方法就需要多做点事了。
这也告诉我们,JDK动态代理模式虽然一定要接口但不一定要有实现类。