1.前言
前面<<Mybatis源码2>>分析了事务工厂的加载,数据源的加载,以及mapper的加载。那么在加载完成了以后,我们就开始后续的步骤。
这里再放一下mybatis查询的步骤:
LearnMybatisApplication application = new LearnMybatisApplication();
// 1.读取xml内容
@Cleanup InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2.读取配置,生成创建工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3.生成SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.获取Mapper对象
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
// 5.调用方法,查询数据库
List<User> users = mapper.list("jiang");
2.openSession
进入第三步的openSession
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);
//然后产生一个DefaultSqlSession
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();
}
}
这里最终是会生成一个DefaultSqlSession,主要是将TransactionFactory和Executor组装到里面,TransactionFactory的话,前面已经分析了,而Executor则先不分析这里面的具体实现,放一放,等后面介绍到Executor再分析Executor。
主要是知道我们获取到了一个DefaultSqlSession对象。
3.sqlSession.getMapper
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
这一步会去DefaultSqlSession对象里面获取IUserMapper的代理对象。
进入getMapper,可以看到调用的是configuration的getMapper
public <T> T getMapper(Class<T> type) {
//最后会去调用MapperRegistry.getMapper
return configuration.<T>getMapper(type, this);
}
再进入configuration的getMapper,发现是调用MapperRegistry.getMapper,那我们直接到MapperRegistry.getMapper
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);
}
}
这个knownMappers还记得吗,就是上篇文章中,根据IUserMapper类型存入MapperProxyFactory对象的Map,所以这里就会获取出MapperProxyFactory,然后调用它的newInstance生成代理对象MapperProxy。
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自带的动态代理生成映射器
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);
}
4.调用IUserMapper的list
在我们获取到了IUserMapper的代理对象MapperProxy,然后调用list
方法,所以自然会进MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行
return mapperMethod.execute(sqlSession, args);
}
首先如果是Object的方法,则直接调用,然后返回,不走下面的逻辑。
然后去获取MapperMethod这个对象,获取到了再调用execute,所以这个MapperMethod就是这次要分析的核心。
这里是如果缓存中有MapperMethod就从缓存中拿,没有就会自己创建。
5.MapperMethod
这个类的定义:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
//执行
public Object execute(SqlSession sqlSession, Object[] args) {
xxxxxx
}
// xxxxxx
可以看到也比较简单,就是两个成员变量SqlCommand和MethodSignature,这个类主要的作用就是:
1.解析我们传给sql需要的参数,变成一个Map,传递给后面的执行器
2.调用SqlSession的方法
3.对SqlSession方法返回的值简单的处理一下,用的较少
5.1 MapperMethod的创建
那么先看它的创建,也就是构造函数。
SqlCommand
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}
这个SqlCommand会保存着statementId,这里是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,这个后续也会被用到,用于定位是哪个MappedStatement?;褂斜4孀乓桓鰐ype就是这个sql是什么类型,比如这里就是SELECT。
里面的逻辑不复杂,就不看了。
MethodSignature
这个类定义:
//方法签名,静态内部类
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final SortedMap<Integer, String> params;
private final boolean hasNamedParameters;
// xxxxx
}
这个类主要作用:
1.初始化一些对SqlSession方法返回值处理的配置
2.解析@Param,获取出里面的值传递给后面的SqlSession
那我们分析这个是怎么去解析@Param的。
先看MethodSignature的构造函数
public MethodSignature(Configuration configuration, Method method) {
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray());
this.mapKey = getMapKey(method);
this.returnsMap = (this.mapKey != null);
// 判断是否有@Param
this.hasNamedParameters = hasNamedParams(method);
//以下重复循环2遍调用getUniqueParamIndex,是不是降低效率了
//记下RowBounds是第几个参数
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//记下ResultHandler是第几个参数
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 解析@Param
this.params = Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
}
看hasNamedParams(method)
的代码:
private boolean hasNamedParams(Method method) {
boolean hasNamedParams = false;
final Object[][] paramAnnos = method.getParameterAnnotations();
for (Object[] paramAnno : paramAnnos) {
for (Object aParamAnno : paramAnno) {
if (aParamAnno instanceof Param) {
//查找@Param,有一个则返回true
hasNamedParams = true;
break;
}
}
}
return hasNamedParams;
}
遍历这个方法上面的是否有@Param注解,有就返回true
然后到Collections.unmodifiableSortedMap(getParams(method, this.hasNamedParameters));
的getParams
:
//得到所有参数
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
//用一个TreeMap,这样就保证还是按参数的先后顺序
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
//是否不是RowBounds/ResultHandler类型的参数
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
//参数名字默认为0,1,2,这就是为什么xml里面可以用#{1}这样的写法来表示参数了
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
//还可以用注解@Param来重命名参数
paramName = getParamNameFromAnnotation(method, i, paramName);
}
params.put(i, paramName);
}
}
return params;
}
private String getParamNameFromAnnotation(Method method, int i, String paramName) {
final Object[] paramAnnos = method.getParameterAnnotations()[i];
for (Object paramAnno : paramAnnos) {
if (paramAnno instanceof Param) {
paramName = ((Param) paramAnno).value();
}
}
return paramName;
}
这里可以看到getParams
里面用了一个SorteMap来存放参数下标和参数名,这里有一点,如果我们的参数不使用@Param,那么存在SorteMap的Key就会是数字从0一直递增,值也是从0一直递增,如果指定了@Param,那value就会变成里面@Param里value的值。
举个例子:
如果我们的Mapper是这样写的:
public interface IUserMapper {
List<User> list(@Param(value = "name") String name);
}
那么存在SorteMap的就会是这样的Key为0,值为name。
如果没有@Param(value = "name")
,那么存在SorteMap的就会是这样的Key为0,值为0。
5.2 execute
在创建好了MapperMethod后,就开始调用execute方法了。
//执行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
这里就对应着增删改查操作,我们这里是select,并且返回的结果是List,那么就会进入executeForMany。
//多条记录
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
//代入RowBounds
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
这里关键就是:
1.MethodSignature的convertArgsToSqlCommandParam
2.sqlSession.selectList
那这个convertArgsToSqlCommandParam干了什么呢?
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
//如果没参数
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//如果只有一个参数
return args[params.keySet().iterator().next().intValue()];
} else {
//否则,返回一个ParamMap,修改参数名,参数名就是其位置
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//1.先加一个#{0},#{1},#{2}...参数
param.put(entry.getValue(), args[entry.getKey().intValue()]);
// issue #71, add param names as param1, param2...but ensure backward compatibility
final String genericParamName = "param" + String.valueOf(i + 1);
if (!param.containsKey(genericParamName)) {
//2.再加一个#{param1},#{param2}...参数
//你可以传递多个参数给一个映射器方法。如果你这样做了,
//默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
//如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
上面的代码就可以发现,它根据之前的params,也就是之前的SorteMap:
0 -> 'name'
遍历这个map,然后获取第一个传入的参数,由于我们是mapper.list("jiang");
调用的,所以这个参数就会是jiang
,
它首先将name -> jiang
加入,然后再加入一个param1 - jiang
,然后返回这个param。
所以我们得到一个这样的Map:
那么获取到了这个param后,就是我们的最后一步
result = sqlSession.<E>selectList(command.getName(), param);
这里的commond.getName就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,然后param就是我们刚刚convertArgsToSqlCommandParam的返回值。
这样MapperMethod就将我们传递的参数封装好,并且将statementId一并传入到SqlSession的selectList中,后面就可以获取到对应的MappedStatement,以及sql的参数,去Mysql中查询
6.总结
**1.DefaultSqlSession通过getMapper,去MapperRegistry的knownMappers中获取出了Mapper的代理对象工厂MapperProxyFactory,然后创建出了MapperProxy
2.MapperMethod对象初始化的时候构建了SqlCommand以及MethodSignature,分别是存储了statementId也就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,以及将传入的参数根据@Param看解析转成Map,例如:[name: "jiang"],这里用json表示一下
3.将com.learn.mybatis.learnmybatis.mapper.IUserMapper.list以及参数传给了sqlSession的selectList,然后开始后续的流程**