Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)
mybatis是怎么拿sqlSession
在 上一篇的時(shí)候,我們的SqlSessionFactoryBuilder已經(jīng)從xml文件中解析出了Configuration并且返回了sessionFactory。
然后我們要從session;中拿到sqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;@Overridepublic SqlSession openSession() {//默認(rèn)情況下ExecutorType是ExecutorType.SIMPLE類型的return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {//獲取配置的環(huán)境信息final Environment environment = configuration.getEnvironment();//獲取environment中的TransactionFactoryfinal TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//生成Transactiontx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//生成Executor(重要,之后的查詢都得靠它)final Executor executor = configuration.newExecutor(tx, execType);//返回DefaultSqlSessionreturn 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類的組成,其實(shí)新建的時(shí)候就只是把他的字段賦值而已 public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Executor executor;private boolean autoCommit;private boolean dirty;private List<Cursor<?>> cursorList; }public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {this.configuration = configuration;this.executor = executor;this.dirty = false;this.autoCommit = autoCommit;}上面分析的是到拿到sqlSession為止,重點(diǎn)其實(shí)不是在上面這里,因?yàn)榈缴厦鏋橹?#xff0c;其實(shí)主要的功能只是將配置的信息解析成我們要的類,然后進(jìn)行初始化賦值。
Mapper的實(shí)現(xiàn)原理
下面我們從SqlSession中拿到mapper,并執(zhí)行方法其實(shí)才是,你感覺到mybatis框架開始幫我們做事的開始。
public static void main(String[] args) {//拿到SqlSessionSqlSession sqlsession = MybatisUtil.getSqlsession();//拿mapperTDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);//調(diào)用mapper的方法List<TDemo> all = mapper.getAll();for (TDemo item : all)System.out.println(item);}因?yàn)槲覀冊(cè)陧?xiàng)目中的TDemoMapper只是一個(gè)接口,并沒有實(shí)現(xiàn)這個(gè)接口方法,但是為什么我們?cè)谡{(diào)用這個(gè)接口方法的時(shí)候就可以得到返回結(jié)果呢?mybatis究竟做了什么?
首先我們回到之前解析Mapper的語句
mapperElement(root.evalNode("mappers"));private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) { //根據(jù)resource解析ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {//根據(jù)url 解析ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {//根據(jù)mapperClass解析//首先通過mapperClass的路徑,生成mapperClass的接口類Class<?> mapperInterface = Resources.classForName(mapperClass);//降mapperClass加入到configuration中去configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}//Configuration類下public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}//MapperRegistry類下 public class MapperRegistry {private final Configuration config;private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();//最終調(diào)用這個(gè)方法public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {//將接口包裝成MapperProxyFactory類放入knownMappers中(knownMappers就是存放我們的mapper接口的)knownMappers.put(type, new MapperProxyFactory<T>(type));// It's important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won't try.//通過這個(gè)builder來解析mapper的statement。(把mapper和mapper.xml文件相關(guān)聯(lián),方法名與xml中的id相關(guān)聯(lián),為了之后調(diào)用的時(shí)候能找到的語句)MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//開始解析parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}} }//MapperAnnotationBuilder類中public void parse() {String resource = type.toString();if (!configuration.isResourceLoaded(resource)) {//通過xml文件解析loadXmlResource();configuration.addLoadedResource(resource);assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();//獲得接口的方法(為了獲取方法上的注解,通過注解的方式來讓方法于sql語句相關(guān)聯(lián))Method[] methods = type.getMethods();for (Method method : methods) {try {// issue #237if (!method.isBridge()) {//具體的解析過程parseStatement(method);}} catch (IncompleteElementException e) {configuration.addIncompleteMethod(new MethodResolver(this, method));}}}parsePendingMethods();}具體的調(diào)用過程就不細(xì)跟了,無非就是獲取節(jié)點(diǎn),獲取屬性值,(或者是獲取方法,然后獲取注解信息),巴拉巴拉……然后設(shè)置到configuration中。
上面要注意的點(diǎn)是,若既配置xml又配置注解的情況下,注解會(huì)覆蓋xml,原因非常簡(jiǎn)單,源碼中注解解析在xml解析后面,然后覆蓋的情況是,他們有相同的namespace+id。
然后我們繼續(xù)我們的主線任務(wù),就是mapper的設(shè)計(jì)架構(gòu)。從上面我們可以知道,configuration中有一個(gè)MapperRegistry類型的字段mapperRegistry,其中有一個(gè)字段叫knownMappers,knownMappers里面存著key為接口類型,值為MapperProxyFactory的。
總結(jié)
我們通過sqlSession獲得mapper方法,而sqlSession從configuration中的mapperRegistry中獲取MapperProxyFactory對(duì)象,在通過MapperProxyFactory對(duì)象的newInstance方法得到MapperProxy的動(dòng)態(tài)代理實(shí)例對(duì)象。
我們使用的mapper其實(shí)是通過MapperProxy動(dòng)態(tài)代理,在運(yùn)行時(shí)候生成的一個(gè)新的對(duì)象進(jìn)行方法增強(qiáng)的,里面的接口方法都會(huì)通過下面2個(gè)語句進(jìn)行數(shù)據(jù)庫的操作,以及后續(xù)對(duì)數(shù)據(jù)的處理
final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);12這兩條語句其實(shí)包含對(duì)訪問數(shù)據(jù)庫對(duì)象的創(chuàng)建,訪問數(shù)據(jù)庫到得到數(shù)據(jù)庫返回?cái)?shù)據(jù)后的處理等內(nèi)容,非常復(fù)雜,本篇就到此為止。
總結(jié)
以上是生活随笔為你收集整理的Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Debug Pytorch: Runti
- 下一篇: mysql中union 查询