1.前言
前面<<Mybatis源碼2>>分析了事務(wù)工廠的加載,數(shù)據(jù)源的加載序芦,以及mapper的加載吞彤。那么在加載完成了以后檩淋,我們就開始后續(xù)的步驟拘央。
這里再放一下mybatis查詢的步驟:
LearnMybatisApplication application = new LearnMybatisApplication();
// 1.讀取xml內(nèi)容
@Cleanup InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 2.讀取配置,生成創(chuàng)建工廠
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 3.生成SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.獲取Mapper對象
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
// 5.調(diào)用方法挠羔,查詢數(shù)據(jù)庫
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);
//通過事務(wù)工廠來產(chǎn)生一個事務(wù)
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//生成一個執(zhí)行器(事務(wù)包含在執(zhí)行器里)
final Executor executor = configuration.newExecutor(tx, execType);
//然后產(chǎn)生一個DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
//如果打開事務(wù)出錯墓陈,則關(guān)閉它
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的話贡必,前面已經(jīng)分析了兔港,而Executor則先不分析這里面的具體實現(xiàn),放一放赊级,等后面介紹到Executor再分析Executor押框。
主要是知道我們獲取到了一個DefaultSqlSession對象。
3.sqlSession.getMapper
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
這一步會去DefaultSqlSession對象里面獲取IUserMapper的代理對象理逊。
進入getMapper橡伞,可以看到調(diào)用的是configuration的getMapper
public <T> T getMapper(Class<T> type) {
//最后會去調(diào)用MapperRegistry.getMapper
return configuration.<T>getMapper(type, this);
}
再進入configuration的getMapper,發(fā)現(xiàn)是調(diào)用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還記得嗎兑徘,就是上篇文章中,根據(jù)IUserMapper類型存入MapperProxyFactory對象的Map羡洛,所以這里就會獲取出MapperProxyFactory挂脑,然后調(diào)用它的newInstance生成代理對象MapperProxy。
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自帶的動態(tài)代理生成映射器
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.調(diào)用IUserMapper的list
在我們獲取到了IUserMapper的代理對象MapperProxy欲侮,然后調(diào)用list
方法崭闲,所以自然會進MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法調(diào)用時威蕉,都會調(diào)用這個invoke方法
//并不是任何一個方法都需要執(zhí)行調(diào)用代理對象進行執(zhí)行刁俭,如果這個方法是Object中通用的方法(toString、hashCode等)無需執(zhí)行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//這里優(yōu)化了韧涨,去緩存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//執(zhí)行
return mapperMethod.execute(sqlSession, args);
}
首先如果是Object的方法牍戚,則直接調(diào)用,然后返回虑粥,不走下面的邏輯如孝。
然后去獲取MapperMethod這個對象,獲取到了再調(diào)用execute娩贷,所以這個MapperMethod就是這次要分析的核心第晰。
這里是如果緩存中有MapperMethod就從緩存中拿,沒有就會自己創(chuàng)建。
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);
}
//執(zhí)行
public Object execute(SqlSession sqlSession, Object[] args) {
xxxxxx
}
// xxxxxx
可以看到也比較簡單茁瘦,就是兩個成員變量SqlCommand和MethodSignature罗岖,這個類主要的作用就是:
1.解析我們傳給sql需要的參數(shù),變成一個Map腹躁,傳遞給后面的執(zhí)行器
2.調(diào)用SqlSession的方法
3.對SqlSession方法返回的值簡單的處理一下桑包,用的較少
5.1 MapperMethod的創(chuàng)建
那么先看它的創(chuàng)建,也就是構(gòu)造函數(shù)纺非。
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,這個后續(xù)也會被用到烧颖,用于定位是哪個MappedStatement弱左。還有保存著一個type就是這個sql是什么類型,比如這里就是SELECT炕淮。
里面的邏輯不復(fù)雜拆火,就不看了。
MethodSignature
這個類定義:
//方法簽名涂圆,靜態(tài)內(nèi)部類
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的構(gòu)造函數(shù)
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);
//以下重復(fù)循環(huán)2遍調(diào)用getUniqueParamIndex润歉,是不是降低效率了
//記下RowBounds是第幾個參數(shù)
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
//記下ResultHandler是第幾個參數(shù)
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
:
//得到所有參數(shù)
private SortedMap<Integer, String> getParams(Method method, boolean hasNamedParameters) {
//用一個TreeMap,這樣就保證還是按參數(shù)的先后順序
final SortedMap<Integer, String> params = new TreeMap<Integer, String>();
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
//是否不是RowBounds/ResultHandler類型的參數(shù)
if (!RowBounds.class.isAssignableFrom(argTypes[i]) && !ResultHandler.class.isAssignableFrom(argTypes[i])) {
//參數(shù)名字默認(rèn)為0,1,2踩衩,這就是為什么xml里面可以用#{1}這樣的寫法來表示參數(shù)了
String paramName = String.valueOf(params.size());
if (hasNamedParameters) {
//還可以用注解@Param來重命名參數(shù)
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來存放參數(shù)下標(biāo)和參數(shù)名嚼鹉,這里有一點,如果我們的參數(shù)不使用@Param驱富,那么存在SorteMap的Key就會是數(shù)字從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
在創(chuàng)建好了MapperMethod后,就開始調(diào)用execute方法了蠢莺。
//執(zhí)行
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到執(zhí)行時就是4種情況寒匙,insert|update|delete|select,分別調(diào)用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()) {
//如果有結(jié)果處理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果結(jié)果有多條記錄
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果結(jié)果是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;
}
這里就對應(yīng)著增刪改查操作,我們這里是select锄弱,并且返回的結(jié)果是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;
}
這里關(guān)鍵就是:
1.MethodSignature的convertArgsToSqlCommandParam
2.sqlSession.selectList
那這個convertArgsToSqlCommandParam干了什么呢会宪?
public Object convertArgsToSqlCommandParam(Object[] args) {
final int paramCount = params.size();
if (args == null || paramCount == 0) {
//如果沒參數(shù)
return null;
} else if (!hasNamedParameters && paramCount == 1) {
//如果只有一個參數(shù)
return args[params.keySet().iterator().next().intValue()];
} else {
//否則肖卧,返回一個ParamMap,修改參數(shù)名掸鹅,參數(shù)名就是其位置
final Map<String, Object> param = new ParamMap<Object>();
int i = 0;
for (Map.Entry<Integer, String> entry : params.entrySet()) {
//1.先加一個#{0},#{1},#{2}...參數(shù)
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}...參數(shù)
//你可以傳遞多個參數(shù)給一個映射器方法塞帐。如果你這樣做了,
//默認(rèn)情況下它們將會以它們在參數(shù)列表中的位置來命名,比如:#{param1},#{param2}等。
//如果你想改變參數(shù)的名稱(只在多參數(shù)情況下) ,那么你可以在參數(shù)上使用@Param(“paramName”)注解巍沙。
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
上面的代碼就可以發(fā)現(xiàn)葵姥,它根據(jù)之前的params,也就是之前的SorteMap:
0 -> 'name'
遍歷這個map句携,然后獲取第一個傳入的參數(shù)榔幸,由于我們是mapper.list("jiang");
調(diào)用的,所以這個參數(shù)就會是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就將我們傳遞的參數(shù)封裝好奏黑,并且將statementId一并傳入到SqlSession的selectList中,后面就可以獲取到對應(yīng)的MappedStatement编矾,以及sql的參數(shù)熟史,去Mysql中查詢
6.總結(jié)
**1.DefaultSqlSession通過getMapper,去MapperRegistry的knownMappers中獲取出了Mapper的代理對象工廠MapperProxyFactory窄俏,然后創(chuàng)建出了MapperProxy
2.MapperMethod對象初始化的時候構(gòu)建了SqlCommand以及MethodSignature蹂匹,分別是存儲了statementId也就是com.learn.mybatis.learnmybatis.mapper.IUserMapper.list,以及將傳入的參數(shù)根據(jù)@Param看解析轉(zhuǎn)成Map凹蜈,例如:[name: "jiang"]限寞,這里用json表示一下
3.將com.learn.mybatis.learnmybatis.mapper.IUserMapper.list以及參數(shù)傳給了sqlSession的selectList,然后開始后續(xù)的流程**