sqlSession代表与数据库的一次会话,在这次会话中可以多次执行查询等sql操作。从前面可以看到SqlSession对象是从SqlSessionFactory对象中获得的。sqlSession本身就定义了一系列的update select delete insert等方法,在旧版本的mybatis中是直接调用这些方法的,但是在mybatis3中先通过getMapper()获取到mapper对象(一个代理对象),然后通过mapper来调用相应的sql。如下面所示:
//通过SqlSession对象获取BranchMapper接口的代理对象,并通过代理对象执行sql获取结果
BranchMapper branchMapper=sqlSession.getMapper(BranchMapper.class);
branchMapper.getBranchByNameAndCity("test","bj");
正如上面注释所示,本质上是通过sqlSession的getMapper()方法拿到了接口的代理对象,然后通过这个代理对象去执行的相应的sql。下面从源码来看下代理对象的构建过程。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
可以看到生成代理对象调用的是Proxy.newProxyInstance()方法,而真正的代理对象是MapperProxy类。下面来看下MapperProxy类的源码,下面是部分源码:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@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);
}
//cachedMapperMethod为method生成MapperMethod对象,并且还会将生成的对象缓存到methodCache中,这样下次再调用同样的
//方法就不需要重新生成了
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
首先来看下里面的几个属性:
sqlSession: 本次执行的数据库会话,即sqlSession.getMapper(BranchMapper.class)
中的sqlSession。
mapperInterface: sqlSession.getMapper(BranchMapper.class)
传入的BranchMapper.class,即需要被代理的Class对象。
methodCache:是一个Map对象:Map
,key是BranchMapper接口中的method,value是一个代理方法MapperMethod对象,为接口中的每个方法都缓存了一个代理方法对象。
通过MapperMethod类的源码可以看到其有两个属性:
private final SqlCommand command;
private final MethodSignature method;
MethodSignature里面存储的是传入方法的一些信息像返回值 传入参数等信息。SqlCommand的源码如下:
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
final String methodName = method.getName();
final Class> declaringClass = method.getDeclaringClass();
//通过mapperInterface的全限定类名和传入的方法名组合成的key,可以从configuration中获取到对应的MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
可以看到里面最重要的逻辑就是获取到当前接口方法对应的MappedStatement对象,然后将这个对象的id作为SqlCommand的名称;这样设置之后,就可以通过Method对应MappedMethod中的SqlCommand对象的name属性获取到关联的MappedStatement对象。methodCache不是在初始化的时候进行填充的,而是在真正调用接口的方法时在invoke()方法里进行初始化的,MappedMethod中的MethodSignature对象(描述方法的入参 返回值 解析方法参数名(包括@Param注解的参数名都是这个对象实现的))也是在invoke()方法里实现的。
通过sqlSession.getMapper()方法拿到的是mapper是个代理对象,执行代理对象的相关方法的时候,比如执行branchMapper.getBranchByNameAndCity(“test”,”bj”);会先调用代理类的invoke()方法。在上面的MapperProxy的invoke()方法中,先判断传入的method是不是属于一个类或者是不是默认的方法,如果不是,那么就为当前的method生成一个MappedMethod对象,然后执行其execute()方法,将当前的sqlSession和方法参数传入。下面来看下execute()内部的逻辑:
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;
}
可以看到在这个方法里面会根据传入的查询类型和返回结果的不同,分别调用不同的方法来执行sql(使用了命令模式)。但这些方法里都是调用了sqlSession相应的方法进行的查询,比如说最常用的返回多个值的查询:
//返回多个结果的查询
else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
下面是executeForMany()方法的源码,可以看到里面其实就是调用了sqlSession的selectList()方法完成的查询:
private Object executeForMany(SqlSession sqlSession, Object[] args) {
List result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
然后再看下sqlSession.
的内部实现,它里面调用的是下面这个方法:
@Override
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
传入的参数statement是MapperMethod中SqlCommand对象的name即Mapper接口方法对应的MappedStatement的id,parameter是MappedMethod中通过组合传入的Method中参数名称(@Param注解的参数名等)信息 参数值信息等得到的参数对象。这个方法里会通过Configuration通过id拿到MappedStatement对象,然后通过与sqlSession关联的executor.query()方法来执行查询。
这里有通过wrapCollection对传入的parameter对象再进行一次处理,下面是具体的处理过程:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap
主要针对方法的参数类型是Collection List Array这三种情况并且没有使用@Param注解的时候,提供默认的名称值;这样设置之后,可以在mapper.xml中直接通过#{list} #{collection} #{array}等方式进行直接引用。
现在来简单总结下,前面的一系列过程完成了传入参数的转换:将入参数组args[]结合method对象中关于参数的信息得到了parameter对象,通过传入method对象的全限定类名加方法名取得了关联的MappedStatement对象(完成了接口方法和mapper.xml配置sql的管理)。完成了以上两步之后就完成了sql运行前的准备工作,接下来就是通过SqlSession中executor来执行sql获取并转化查询结果了。