今天就跟大家聊聊有關(guān)Mybatis中Mapper與接口綁定原理的源碼分析,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。
10年積累的做網(wǎng)站、網(wǎng)站制作經(jīng)驗(yàn),可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站設(shè)計后付款的網(wǎng)站建設(shè)流程,更有南明免費(fèi)網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
這里再來分析下,Mapper與接口綁定原理。
// 5.操作Mapper接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
public interface UserMapper { public UserEntity getUser(int id); }
為什么UserMapper是接口,沒用實(shí)現(xiàn)類,那么他是怎么初始化的?getMapper()方法為什么可以調(diào)用?
mapper接口是怎么初始化的?是反射?不是的,接口是不能反射初始化。揭秘:其實(shí)是代理設(shè)計模式【動態(tài)代理】,底層使用AOP實(shí)現(xiàn)。
另外MyBayis中最重要的是SqlSession:操縱SQL語句。
分析源碼前,我們先回顧下動態(tài)代理技術(shù),在我的這篇博客中詳細(xì)介紹了:淺談Java【代理設(shè)計模式】——看這篇文章就懂了。
思考問題:動態(tài)代理分為:jdk動態(tài)代理和CGLIB動態(tài)代理,那么Mybatis使用了那種代理設(shè)計模式?
答案:MyBatis采用的jdk動態(tài)代理,因?yàn)榇淼氖墙涌凇?/p>
1.創(chuàng)建被代理的接口和類;
2.實(shí)現(xiàn)InvocationHandler接口,對目標(biāo)接口中聲明的所有方法進(jìn)行統(tǒng)一處理;
3.調(diào)用Proxy的靜態(tài)方法,創(chuàng)建代理類并生成相應(yīng)的代理對象;
/** * 1.創(chuàng)建被代理的接口和類; */ public interface OrderService { public String add(); }
public class OrderServiceImpl implements OrderService { public String add() { System.out.println("OrderServiceImpl add。。。"); return "success"; } }
/** * 2.實(shí)現(xiàn)InvocationHandler接口,對目標(biāo)接口中聲明的所有方法進(jìn)行統(tǒng)一處理; */ public class JdkMapperProxy implements InvocationHandler { //目標(biāo)對象,被代理對象 private Object targect; public JdkMapperProxy(Object targect){ this.targect=targect; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("前置通知...在代理方法之前處理"); //目標(biāo)方法,目標(biāo)方法參數(shù) Object result = method.invoke(targect, args);//被執(zhí)行目標(biāo)方法,被代理的方法 System.out.println("后置通知...在代理方法之后處理"); return null; } }
/** * 3.調(diào)用Proxy的靜態(tài)方法,創(chuàng)建代理類并生成相應(yīng)的代理對象; */ public class TestMybatis02 { public static void main(String[] args) { System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader() , OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl())); orderService.add(); } }
運(yùn)行TestMybatis02 結(jié)果如下:
前置通知...在代理方法之前處理
OrderServiceImpl add。。。
后置通知...在代理方法之后處理
答案是肯定的,在那里進(jìn)行解析的呢?下面開始分析源碼:下面就是解析的地方
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace != null && !namespace.equals("")) { .... //進(jìn)入這里 this.buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } else { throw new BuilderException("Mapper's namespace cannot be empty"); } } catch (Exception var3) { throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3); } }
重點(diǎn)這段代碼:
this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(Listlist) { if (this.configuration.getDatabaseId() != null) { //會進(jìn)入到這里 this.buildStatementFromContext(list, this.configuration.getDatabaseId()); } this.buildStatementFromContext(list, (String)null); }
private void buildStatementFromContext(Listlist, String requiredDatabaseId) { Iterator i$ = list.iterator(); while(i$.hasNext()) { XNode context = (XNode)i$.next(); XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId); try { //進(jìn)入到這里 statementParser.parseStatementNode(); } catch (IncompleteElementException var7) { this.configuration.addIncompleteStatement(statementParser); } } }
public void parseStatementNode() { String id = this.context.getStringAttribute("id"); String databaseId = this.context.getStringAttribute("databaseId"); if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { .... if (this.configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = this.configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } //最終到這里了 this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); } }
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class> parameterType, String resultMap, Class> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (this.unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } else { ..... //進(jìn)入這里 this.configuration.addMappedStatement(statement); return statement; } }
public void addMappedStatement(MappedStatement ms) { //最終結(jié)果 this.mappedStatements.put(ms.getId(), ms); }
protected final MapmappedStatements;
this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");
protected static class StrictMapextends HashMap {
通過上面的代碼執(zhí)行流程,最終我們知道,mapper.xml中的配置文件里的每條sql語句是如何轉(zhuǎn)化為對象保存起來的。最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。
通過源碼可知:HadhMap被put了兩次
后面我們來分析getMapper()方法:默認(rèn)走的是DefaultSqlSession
// 5.操作Mapper接口 UserMapper mapper = sqlSession.getMapper(UserMapper.class);
publicT getMapper(Class type) { return this.configuration.getMapper(type, this); }
publicT getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }
publicT getMapper(Class type, SqlSession sqlSession) { MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
由上面代碼可知:通過configuration.getMapper()去查下我們之前有沒有注冊過mapper接口,沒有則會報:沒用綁定接口錯誤。
再看看上篇文章中介紹的mapperRegistery里面的東西:存放的是mapper接口,key為:接口,value為:MapperProxyFactory
這里我們mapper接口注冊過,會進(jìn)入else分支的這段代碼:使用mapperProxyFactory創(chuàng)建代理類:
return mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) { MapperProxymapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); }
對比:mybatis的jdk動態(tài)代理和我們自己實(shí)現(xiàn)的jdk動態(tài)代理:
public class MapperProxyimplements InvocationHandler, Serializable {//mybatis的實(shí)現(xiàn)
public class JdkMapperProxy implements InvocationHandler {//我們的實(shí)現(xiàn)
protected T newInstance(MapperProxymapperProxy) {//mybatis的實(shí)現(xiàn) return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }
OrderService orderService = (OrderService) Proxy.newProxyInstance(OrderServiceImpl.class.getClassLoader()//我們的實(shí)現(xiàn) , OrderServiceImpl.class.getInterfaces(), new JdkMapperProxy(new OrderServiceImpl()));
最后返回mapper信息如下:mapper為:我們通過:mapperProxyFactory創(chuàng)建的代理類MapperProxy
所以當(dāng)我們調(diào)用mapper的getUser()方法時候,就會執(zhí)行MapperProxy代理類的invoke()方法
UserEntity user = mapper.getUser(2);
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { //判斷mapper接口有沒有實(shí)現(xiàn)類,顯然我們mapper沒用實(shí)現(xiàn)類 try { return method.invoke(this, args); } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { //會執(zhí)行這個分支 MapperMethod mapperMethod = this.cachedMapperMethod(method); //緩存中獲取method return mapperMethod.execute(this.sqlSession, args); //執(zhí)行sql語句 } }
思考問題:Mybatis里面,mapper接口中有多個方法,每次調(diào)用會走同一個invoke()方法嗎?
答案:不會的,因?yàn)槟愕拿總€MapperRegistry里面的class為mapper接口,都有獨(dú)立的MapperProxyFactory,因?yàn)镸apperRegistry中key存放的是mapper接口,value為MapperProxyFactory。
我們使用MapperProxyFactory創(chuàng)建MapperProxy去創(chuàng)建的代理,所以每次調(diào)用getMapper()方法取到同一個mapper則會走同一個invoke()方法,反過來每次調(diào)用mapper時候,就會走不同invoke()方法。
一般我們把Mapper接口定義為全局,則會走同一個invoke()方法,除非設(shè)=設(shè)置為多例,就每次都會new 不同,走不同invoke()方法。
Mybatis是基于多個不同的mapper接口生產(chǎn)的代理類,不同的mapper接口走不同的invoke方法,如果是相同的mapper接口,不同的方法,肯定是走同一個invoke方法。
那么就有問題了,多個不同mapper接口會產(chǎn)生多個代理類( new MapperProxy()),占太多的內(nèi)存,后面會詳解。
MapperProxy
mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
上面我們把mapper接口看完了,執(zhí)行 mapper.getUser(2) 會走invoke(),下面看invoke()方法
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 var5) { throw ExceptionUtil.unwrapThrowable(var5); } } else { //進(jìn)入這里 MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } }
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); //去緩存中查看是否有method,我們這里是沒用的 if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); //會走到這里 this.methodCache.put(method, mapperMethod); } return mapperMethod; } }
public MapperMethod(Class> mapperInterface, Method method, Configuration config) { this.command = new MapperMethod.SqlCommand(config, mapperInterface, method); this.method = new MapperMethod.MethodSignature(config, method); }
先看下這塊
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
public enum SqlCommandType { UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
SqlCommandType 是和sql語句相關(guān)的
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) { String statementName = mapperInterface.getName() + "." + method.getName(); MappedStatement ms = null; if (configuration.hasStatement(statementName)) {//進(jìn)入這里 ms = configuration.getMappedStatement(statementName); } else if (!mapperInterface.equals(method.getDeclaringClass())) { String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName(); if (configuration.hasStatement(parentStatementName)) { ms = configuration.getMappedStatement(parentStatementName); } } if (ms == null) { if (method.getAnnotation(Flush.class) == null) { throw new BindingException("Invalid bound statement (not found): " + statementName); } this.name = null; this.type = SqlCommandType.FLUSH; } else { //ms不為null,則執(zhí)行到這里 this.name = ms.getId(); this.type = ms.getSqlCommandType(); if (this.type == SqlCommandType.UNKNOWN) { throw new BindingException("Unknown execution method for: " + this.name); } } }
configuration.hasStatement(statementName)
public boolean hasStatement(String statementName) { return this.hasStatement(statementName, true); }
getId()為namespace+id
將mapper.xml里面配置的sql語句和對應(yīng)的mapper接口方法進(jìn)行關(guān)聯(lián)并放入map緩存中,后期直接走緩存了。最后執(zhí)行execute()方法
public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; if (SqlCommandType.INSERT == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); } else if (SqlCommandType.UPDATE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); } else if (SqlCommandType.DELETE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); } else if (SqlCommandType.SELECT == this.command.getType()) { //select類型走這里 if (this.method.returnsVoid() && this.method.hasResultHandler()) { //判斷方法是否沒用返回結(jié)果的,不是 this.executeWithResultHandler(sqlSession, args); result = null; } else if (this.method.returnsMany()) { //判斷返回結(jié)果是不是返回多個結(jié)果集,不是 result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) { //是否返回map集合?不是 result = this.executeForMap(sqlSession, args); } else { //所以走這里 param = this.method.convertArgsToSqlCommandParam(args); //轉(zhuǎn)換參數(shù) result = sqlSession.selectOne(this.command.getName(), param); //重點(diǎn)在這:selectOne() } } else { if (SqlCommandType.FLUSH != this.command.getType()) { throw new BindingException("Unknown execution method for: " + this.command.getName()); } result = sqlSession.flushStatements(); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
publicT selectOne(String statement, Object parameter) { List list = this.selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }
通過源碼我們可以改成下面這樣:selectOne(),后面我們針對selectOne()進(jìn)行源碼分析
//UserEntity user = mapper.getUser(2); sqlSession.selectOne("com.mayikt.mapper.UserMapper.getUser",2);
1、mapper.xml中的配置文件里的每條sql語句,最終都是封裝成一個MappedStatement對象,再通過一個HashMap集合保存起來。
2、執(zhí)行g(shù)etMapper()方法,判斷是否注冊過mapper接口,注冊了就會使用mapperProxyFactory去生成代理類MapperProxy
3、執(zhí)行目標(biāo)方法時,會調(diào)用MapperProxy代理類的invoke()方法
4、將mapper.xml里面配置的sql語句和對應(yīng)的mapper接口方法進(jìn)行關(guān)聯(lián)并放入map緩存中,后期直接走緩存了。最后執(zhí)行execute()方法
5、執(zhí)行execute()方法最終調(diào)用selectOne()方法,執(zhí)行結(jié)果。
看完上述內(nèi)容,你們對Mybatis中Mapper與接口綁定原理的源碼分析有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝大家的支持。