通常拿到源碼會無從下手,個人基本思路通常就是根據一個基本的helloWorld Debug下去,把主線先大概理一遍,而後再具體分析細節,沒有必要一個類一個類細看,看了也會忘掉。本身理源碼的時候看不下去時,能夠結合網上的分析文章,一邊看別人的解析,一邊本身對照源碼。瞭解框架設計原理,之後項目中出了問題能夠更容易定位。再往上一層面,之後本身能夠根據需求擴展框架。java
先執行個HelloWorldmysql
去github上 clone Mybatis代碼,而後再其測試源碼裏添加以下代碼
git
示例代碼,裏面未貼出來的類自行補全。github
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(true); try { //後面介紹mybatis經過動態代理來避免手工調用session,直接調用dao接口; //BlogDao mapper = session.getMapper(BlogDao.class); //List<Blog> blogs= mapper.selectAll(); List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll"); System.out.println(blogs); } finally { session.close(); }
mybatis-config.xmlspring
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/BlogMapper.xml"/> </mappers> </configuration>
BlogMapper.xml
sql
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.apache.ibatis.robin.BlogDao"> <resultMap type="org.apache.ibatis.robin.Blog" id="blog"> <result property="id" column="id"/> <result property="context" column="context"/> <result property="dateCreate" column="date_create"/> </resultMap> <insert id="insert" parameterType="org.apache.ibatis.robin.Blog"> insert into blog(id,context,date_create) values (#{id},#{context},now()) </insert> <select id="selectAll" resultMap="blog" flushCache="true" > SELECT * from blog </select> </mapper>
注意xml文件放到項目的resource位置,能夠經過ide來設置,不然程序會獲取不到。 數據庫
從上面的代碼能夠看到mybatis重要的執行順序:輸入配置流,由SqlSessionFacotryBuilder來根據輸入的配置構建出來一個SqlSessionFactory。apache
//簡略代碼 //解析配置 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //根據配置返回SqlSessionFactory public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
獲取到SqlSessionFactory後,下一步確定是得到sqlSession。緩存
sqlSessionFactory.openSession(true); //這裏的true 表示是否自動commit
//debug 進去 這部分是openSession的大致過程; private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //先經過configuration獲取咱們再xml中配的environment,裏面包含事務和DataSource; final Environment environment = configuration.getEnvironment(); //這一步是獲取JDBC事務 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //打開Executor過程,見下段落的分析 final Executor executor = configuration.newExecutor(tx, execType); return 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(); } }
打開Executor的過程,Executor默認的類型是SIMPLEsession
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //若是在配置文件settings中打開 <setting name="cacheEnabled" value="true" /> // cacheEnabled 着爲true,開啓全局緩存,也就是二級緩存; //下面查詢具體分析CachingExecutor執行查詢過程 if (cacheEnabled) { executor = new CachingExecutor(executor); } //若是自定義了Plugin攔截器,在xml經過plugins配置後,這一步會經過JDK動態代理織入到executor中, //生成一個帶了攔截器方法功能的Executor executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
在根BaseExecutor中能夠看到 ,Executor不只包含了事務,還同時加入localCache ,也就是Session級別的緩存,也是你們常叫的一級緩存,這裏提一下一級緩存是默認的,若是非要去掉只能經過在select 語句配置中 flushCache="true"。
protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; }
Executor組裝好以後經過new DefaultSqlSession返回咱們的SqlSession;
new DefaultSqlSession(configuration, executor, autoCommit);
SqlSession是直接對數據庫發號施令的組件。經過發起下面一個SQL查詢,繼續Debug進去
List<Blog>blogs= session.selectList("org.apache.ibatis.robin.BlogDao.selectAll");
先mybatis自動補全一些默認參數(rowBounds主要是指代返回的行數限制)後,進去下面的代碼
@Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //MappedStatement是Mybatis的精髓,若是Bean對Spring是同樣的道理;這裏咱們要重點介紹下 //屬性MappedStatement 是一個SQL執行語句的包裝,裏面的屬性有SqlSource(指代SQL具體的語句); //屬性StatementType 若是是PREPARED代表執行預編譯執行,這樣不只防止SQL注入,並且還能避免SQL重複解析; //屬性id 指代咱們惟一肯定MappedStatement惟一標識; //還有ResultMap 和ParameterMap等等,這裏就不解釋來; //能夠說MappedStatement是發起SQL請求的所需的必備數據; MappedStatement ms = configuration.getMappedStatement(statement); //進行query查詢,轉下段分析 return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
@Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //獲取SQL BoundSql boundSql = ms.getBoundSql(parameterObject); //經過參數,sql,和rowBounds一塊兒拼裝出cacheKey CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } //進入到查詢 @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //先判斷是否有緩存中去取 Cache cache = ms.getCache(); if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } //緩存中取不到,直接執行query,這裏delegate指代咱們前面生成的SimpleExecutor; return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } //跳轉到下面的方法 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } //清掉cache if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //跳到下面到數據庫中去查詢數據 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } //從這個方法能夠明顯看出裏面的localCache,指代前面說的Session緩存,即一級緩存 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //這一步主要是獲取Connection,而後準備PreparedState,執行查詢返回結果 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } //把新結果緩存起來 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
至此咱們的查詢SQL就執行完了。
這一路涉及了 Configuration --->SqlSessionFactory------>SqlSession(裏面又包裝了 Executor,Cache,MappedStatement) 這幾個重要概念;
下節主要講Mybatis經過代理 把原先本身須要直接調用 sqlSession來執行 改爲只需調用相應dao接口類,在實際項目中省掉大量代碼,以及與spring結合的實現原理;