mybatis作为一个半自动化ORM框架,它可以让你清楚的知道执行的方法是使用什么sql执行的,可以方便的通过对sql的优化提升接口的性能。我们大体介绍一下它是如何工作的。
我们工作中一般的使用方式是,先声明一个Mapper接口,然后创建一个xml文件,文件中的节点名称都是接口中方法的名称,我们可以粗暴的认为,这个xml文件就是mapper接口的实现,只不过形式不是类,而是一个文件的形式,当时这个解释不标准,但是可以方便我们的记忆及使用。
他们是通过什么关联的呢?是通过类的全限定名。因为xml中有一个namespace的属性,他的值就是对应接口的全限定名。所以我们可以得出一个结论:当我们调用这个接口中的方法时,首先会根据这个接口全限定名定位到文件(确切的说,是解析xml后形成的对象),在根据方法名定位到要执行的sql。下面我们通过代码看一下这个过程。
在Configuration中有mappedStatements属性是一个map,里面的内容是从xml文件中解析出来的,key为namespace+方法名,value,就是关于这节点的关键信息,最为重要的就是原始sql,属性名称为sqlSource。另外还有一个处理Mapper Interface 中方法的关键属性mapperRegistry
Configuration.java
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
MappedStatement.java
private SqlSource sqlSource;
当调用Configuration中的getMapper方法时
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
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);
}
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里使用的是动态代理模式,根据传入的type,获得这个type的MapperProxyFactory,利用传入的sqlSession,创建这个mapper的代理,实际真正处理请求的是sqlSession.
看一下是如何代理的
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
根据Method创建MapperMethod ,执行MapperMethod 的execute方法,以查询为例
根据参数查询多条数据
执行sqlSession.selectList方法
根据statement(类全限定名+方法名),查询对应的MappedStatement,里面有原始sql
开始查库了!
查数据库了
剩下的就是返回结果的流程了,不做累述