Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)
mybatis是怎么拿sqlSession
在 上一篇的時候,我們的SqlSessionFactoryBuilder已經從xml文件中解析出了Configuration并且返回了sessionFactory。
然后我們要從session;中拿到sqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;@Overridepublic SqlSession openSession() {//默認情況下ExecutorType是ExecutorType.SIMPLE類型的return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);}private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {//獲取配置的環境信息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類的組成,其實新建的時候就只是把他的字段賦值而已 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為止,重點其實不是在上面這里,因為到上面為止,其實主要的功能只是將配置的信息解析成我們要的類,然后進行初始化賦值。
Mapper的實現原理
下面我們從SqlSession中拿到mapper,并執行方法其實才是,你感覺到mybatis框架開始幫我們做事的開始。
public static void main(String[] args) {//拿到SqlSessionSqlSession sqlsession = MybatisUtil.getSqlsession();//拿mapperTDemoMapper mapper = sqlsession.getMapper(TDemoMapper.class);//調用mapper的方法List<TDemo> all = mapper.getAll();for (TDemo item : all)System.out.println(item);}因為我們在項目中的TDemoMapper只是一個接口,并沒有實現這個接口方法,但是為什么我們在調用這個接口方法的時候就可以得到返回結果呢?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) { //根據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) {//根據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) {//根據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<?>>();//最終調用這個方法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.//通過這個builder來解析mapper的statement。(把mapper和mapper.xml文件相關聯,方法名與xml中的id相關聯,為了之后調用的時候能找到的語句)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語句相關聯)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();}具體的調用過程就不細跟了,無非就是獲取節點,獲取屬性值,(或者是獲取方法,然后獲取注解信息),巴拉巴拉……然后設置到configuration中。
上面要注意的點是,若既配置xml又配置注解的情況下,注解會覆蓋xml,原因非常簡單,源碼中注解解析在xml解析后面,然后覆蓋的情況是,他們有相同的namespace+id。
然后我們繼續我們的主線任務,就是mapper的設計架構。從上面我們可以知道,configuration中有一個MapperRegistry類型的字段mapperRegistry,其中有一個字段叫knownMappers,knownMappers里面存著key為接口類型,值為MapperProxyFactory的。
總結
我們通過sqlSession獲得mapper方法,而sqlSession從configuration中的mapperRegistry中獲取MapperProxyFactory對象,在通過MapperProxyFactory對象的newInstance方法得到MapperProxy的動態代理實例對象。
我們使用的mapper其實是通過MapperProxy動態代理,在運行時候生成的一個新的對象進行方法增強的,里面的接口方法都會通過下面2個語句進行數據庫的操作,以及后續對數據的處理
final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);12這兩條語句其實包含對訪問數據庫對象的創建,訪問數據庫到得到數據庫返回數據后的處理等內容,非常復雜,本篇就到此為止。
總結
以上是生活随笔為你收集整理的Mybatis源码分析之(三)mapper接口底层原理(为什么不用写方法体就能访问到数据库)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Debug Pytorch: Runti
- 下一篇: 制造工业中的机器学习应用:I概览