mybatis源碼解析六(代理模式再分析)
前面幾期大概一起看了下maybatis的源碼,這一期,我們通過(guò)設(shè)計(jì)模式來(lái)分析下mybatis的,但是在分析之前,我們?cè)賮?lái)屢一下mybatis的執(zhí)行流程,做一個(gè)整體的講解,
我們都知道,當(dāng)我們寫了mapper接口后,通過(guò)mapper接口myabtis就可以執(zhí)行相應(yīng)的查詢,那馬這里mybatis到底是怎么做到的那,前面已經(jīng)分析關(guān)于一次了,但是比較籠統(tǒng),
今天我們?cè)僖黄鸱治鱿?我們都知道,在我們寫完mapper接口后,我們?cè)诿總€(gè)mapper接口上添加@Mapper注解或者在啟動(dòng)類上添加@MapperScan注解后,基本上剩下的工作就不用我們
在做了,mybatis會(huì)根據(jù)我們的xml文件中配置的sql語(yǔ)句,為我們返回結(jié)果,那到底mybatis是怎么通過(guò)接口就去生成實(shí)現(xiàn)類,來(lái)完成上述操作的那,我們自己也沒(méi)有寫實(shí)現(xiàn)類啊,
接下來(lái),我們從源碼中尋找答案
我寫了個(gè)計(jì)較簡(jiǎn)單的查詢查詢
@Test
@Transactional
public void test5(){
final RyxAccount ryxAccountByPrimaryKey = ryxAccountService.getRyxAccountByPrimaryKey(1);
System.out.println(ryxAccountByPrimaryKey);
final RyxAccount ryxAccountByPrimaryKey1 = ryxAccountService.getRyxAccountByPrimaryKey(1);
System.out.println(ryxAccountByPrimaryKey1);
}
mapper接口
public interface RyxAccountMapper<T,P> extends BaseMapper<T,P>{
/**
* 根據(jù)primaryKey更新
*/
public Integer updateByPrimaryKey(@Param("bean") T t, @Param("id") Integer id);
/**
* 根據(jù)primaryKey刪除
*/
public Integer deleteByPrimaryKey(@Param("id") Integer id);
/**
* 根據(jù)primaryKey獲取對(duì)象
*/
public T selectByPrimaryKey(@Param("id") Integer id);
List<Map<String,Object>> selectListPage(@Param("bindex") int bindex, @Param("num") int num);
public List<RyxAccount> selectTest(@Param("id")int id);
Cursor<RyxAccount> batchReader(@Param("id")int id);
Cursor<RyxAccount> selectCursorTest(@Param("beanP")RyxAccountCusorParam param);
}
接下來(lái),我們通過(guò)斷點(diǎn)的方式,一探究竟
首先,當(dāng)我們啟動(dòng)項(xiàng)目的時(shí)候,因?yàn)榕渲昧薂Mapperscan的原因,mybaits回去我們配置的掃描包下掃描接口(加載配置文件,獲取連接,這些,就不做具體分析了),
當(dāng)掃描到接口后,會(huì)進(jìn)入到mybatis的MapperRegistry
看看他的appMapper方法
public <T> void addMapper(Class<T> type) {
//分析是不是接口
if (type.isInterface()) {
//如果接口已經(jīng)存在,則拋出異常
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//不存在,則將掃描到的mapper接口添加到knowerMapper中,knowerMapper是一個(gè)map,key為具體的mapper類,value為mapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<T>(type));
//創(chuàng)建mapper注解解析器對(duì)象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//執(zhí)行paser方法
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//檢查資源名稱是否被加載過(guò)
loadXmlResource();
//設(shè)置當(dāng)前的命名空間
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析是否配置了二級(jí)緩存注解@CacheNameSpace(這里解釋下,和xml文件中配置了緩存是一個(gè)意思)
parseCache();
//解析二級(jí)緩存引用
parseCacheRef();
//獲取接口中的方法
Method[] methods = type.getMethods();
//遍歷此方法
for (Method method : methods) {
try {
// issue #237
//檢查是否是橋接方法(主要目的是泛型的兼容問(wèn)題)
if (!method.isBridge()) {
//解析語(yǔ)句
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
void parseStatement(Method method) {
//獲取mapper接口方法中的參數(shù)類型
Class<?> parameterTypeClass = getParameterType(method);
//獲取語(yǔ)言驅(qū)動(dòng)類
LanguageDriver languageDriver = getLanguageDriver(method);
//從注解獲取sql源信息類(就是說(shuō)我們?cè)诜椒ㄉ吓渲昧俗⒔庵?這里就是獲取方法上的注解的方法)
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
等這些做完之后那,我們繼續(xù)往下看,加載sqlSesson我們先不做分析,直接看MappperProxy,還記得我們之前的分析嘛,myBatis將掃描完的接口添加到knowMappers中
其中key是具體得接口類,value為mapperProxyFactory,再獲取到sqlSession后,最終會(huì)在MapperFactoryBean中調(diào)用getMapper
MapperFactoryBean,由于整合spring框架,這里不再調(diào)用mybatis的DefaultSqlSession,而是調(diào)用了SqlSessionTemplete
@Override
public T getObject() throws Exception {
//調(diào)用SqlSessionTemplete的getMapper方法
return getSqlSession().getMapper(this.mapperInterface);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//最終回到了MapperRegistry中的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//從knownMappers通過(guò)key獲取value,mapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//通過(guò)jdk動(dòng)態(tài)代理的方式生成代理類
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//代用jdk動(dòng)態(tài)代理
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最終看到了,我們實(shí)現(xiàn)代理的類,mapperProxy,我們看看這個(gè)類,實(shí)現(xiàn)了InvocationHandler接口,最終通過(guò)mapperMethod.execute方法去數(shù)據(jù)庫(kù)查詢數(shù)據(jù)并返回結(jié)果,
這路下來(lái),是不是感覺(jué)真的是踏破鐵鞋無(wú)覓處,柳暗花明又一村啊,真的是太難了,
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 {
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);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
可能這樣不太好看,我們仿照jdk的動(dòng)態(tài)代理自己先寫一個(gè)代理類,來(lái)模仿下.看看大神門的代碼精髓
我們先自定義一個(gè)接口.
public interface RyxMapper {
String save(String str);
}
自定義一個(gè)mapperProxy
public class MapperProxy<T> implements InvocationHandler {
private Class<T> proxyInterface;
public MapperProxy(Class<T> proxyInterface) {
this.proxyInterface = proxyInterface;
}
@SuppressWarnings("unchecked")
public T getProxy() {
return (T) Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我要開始執(zhí)行了....查詢數(shù)據(jù)庫(kù)操作了");
System.out.println("查庫(kù)操作已完成,返回結(jié)果啦");
return "數(shù)據(jù)庫(kù)結(jié)果集:"+"123";
}
}
寫一個(gè)測(cè)試類執(zhí)行下:
public static void main(String[] args) {
RyxMapper mapper = (RyxMapper)new MapperProxy(RyxMapper.class).getProxy();
final String ok = mapper.save("ok");
System.out.println(ok);
}
最終的執(zhí)行結(jié)果肯定是不言而喻的,沒(méi)有問(wèn)題,最終會(huì)打印出結(jié)果,真的是書上得來(lái)終覺(jué)淺,實(shí)踐是檢驗(yàn)真理的唯一途徑啊.
ok,咱們接著往下分析,現(xiàn)在通過(guò)代理,我們拿到了要執(zhí)行的sql語(yǔ)句和參數(shù)
接下來(lái),回去調(diào)用
mapperMethod.execute(sqlSession, args);這個(gè)方法
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;
}
這段源碼就不在贅述了,咱們直接看一個(gè)最簡(jiǎn)單的例子吧,
sqlSession.selectOne(command.getName(), param);
最終會(huì)走到DefautSqlSession中執(zhí)行查詢操作,這里,我們看到了,一個(gè)很警醒的注釋,DefaultSqlSession是非線程安全的,下一章我們分析下這個(gè)問(wèn)題
/**
- The default implementation for {@link SqlSession}.
- Note that this class is not Thread-Safe.
- @author Clinton Begin
*/
public class DefaultSqlSession implements SqlSession {
以上就是今天的內(nèi)容,Thanks