本篇爲原創文章,如需轉載,請標明原創地址。java
mapper.xmlmysql
<mapper namespace="com.example.demo1.mybatis.ArticleMapper"> <select id="selectById" resultType="com.example.demo1.mybatis.Article" parameterType="java.lang.Long"> select <include refid="baseColumns"/> from article where 1= 1 and id = #{id} </select> <sql id="baseColumns"> id,title </sql> </mapper>
實體類sql
@Data public class Article { private Long id; private String title; }
測試類數據庫
public class MybatisTest { public static void main(String[] args) throws IOException { // sqlSessionFactory是一個複雜對象,一般建立一個複雜對象會使用建造器來構建,這裏首先建立建造器 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); // configuration對象對應mybatis的config文件,爲了測試簡便,我這裏直接建立Configuration對象而不經過xml解析得到 Configuration configuration = new Configuration(); configuration.setEnvironment(buildEnvironment()); // 解析一個mapper.xml爲MappedStatement並加入到configuration中 InputStream inputStream = Resources.getResourceAsStream("mybatis/Article.xml"); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, "mybatis/Article.xml", configuration.getSqlFragments()); mapperParser.parse(); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(configuration); // 建立一個sqlSession,這裏使用的是簡單工廠設計模式 SqlSession sqlSession = sqlSessionFactory.openSession(); // 執行最終的sql,查詢文章id爲1的文章 Article article = sqlSession.selectOne("com.example.demo1.mybatis.ArticleMapper.selectById",1L); // 打印文件的標題 System.out.println(article.getTitle()); // sqlSession默認不會自動關閉,咱們須要手動關閉 sqlSession.close(); } private static Environment buildEnvironment() { return new Environment.Builder("test") .transactionFactory(getTransactionFactory()) .dataSource(getDataSource()).build(); } private static DataSource getDataSource() { String url = "url"; String user = "user"; String password = "password"; Properties properties = new Properties(); properties.setProperty("url", url); properties.setProperty("username", user); properties.setProperty("password", password); properties.setProperty("driver", "com.mysql.jdbc.Driver"); properties.setProperty("driver.encoding", "UTF-8"); PooledDataSourceFactory factory = new PooledDataSourceFactory(); factory.setProperties(properties); DataSource dataSource = factory.getDataSource(); return dataSource; } private static TransactionFactory getTransactionFactory() { return new JdbcTransactionFactory(); }
分析sqlSession.selectOne("com.example.demo1.mybatis.ArticleMapper.selectById",1L);設計模式
public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>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; } }
經過查看源碼,咱們發現無論是查詢一條數據仍是查詢多條數據都是執行的selectList方法,查詢一條的時候只要取list的第一條數據便可。緩存
public <E> List<E> selectList(String statement, Object parameter) { return this.selectList(statement, parameter, RowBounds.DEFAULT); }
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { /* 根據statement id找到對應的MappedStatement,而statement id對應的就是mapper的namespace+crud操做的id 在本例中就是com.example.demo1.mybatis.ArticleMapper.selectById */ MappedStatement ms = configuration.getMappedStatement(statement); // 委託執行器來執行查詢操做 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(); } }
使用執行器來執行查詢操做,簡單的執行器只會執行sql,並將結果放入到一級緩存中,帶二級緩存的執行器會增長一層緩存讀寫操做,這裏先只討論簡單執行器的執行mybatis
========================================================================app
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //獲得綁定sql BoundSql boundSql = ms.getBoundSql(parameter); //建立緩存Key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //查詢 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); /* 新建一個StatementHandler StatementHandler的做用主要有如下幾個: 1.從sqlSource獲取最終須要執行的sql 2.建立jdbc的statement對象 3.給statement對象賦值 4.執行statement.execute方法執行賦值的sql 5.經過resultHandler對resultSet結果集進行處理收集,得到最終的結果 6.返回最終的結果 */ StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 經過jdbc鏈接建立一個全新的prepareStatement,並對其進行賦值,對應上面步驟的2,3 stmt = prepareStatement(handler, ms.getStatementLog()); // 執行賦值後的sql並對結果進行處理收集,對應上面步驟的4,5 return handler.<E>query(stmt, resultHandler); } finally { // 執行statement.close方法 closeStatement(stmt); } }
建立statementHandler對象框架
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { /* 建立一個路由statementHandler 根據statementType進行路由,根據jdbc的基本知識咱們知道經常使用的statementType有三種: 1.STATEMENT 硬編碼的語句,有sql注入風險 2.PREPARED 預編譯sql的語句,通常狀況下都使用這個 3.CALLABLE 執行存儲過程的語句 一般咱們使用的都是preparedStatement */ StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); /* 經過上一個步驟statementHandler已經被建立好,preparedStatement也已被初始化 咱們獲得了一個sql爲select id ,title from article where id = ? 的preparedStatement 想想,若是咱們的sql爲 select id,title from article 返回多條記錄,咱們要分頁的話怎麼辦? 一種方法是咱們在mapper.xml中在sql語句尾部手動添加limit *,*來進行分頁(以mysql舉例) 另外一種方法咱們能夠經過插件的方式來實現,像經常使用的PageHelper插件就是基於此來實現的分頁功能。 使用插件的好處是將分頁功能和sql語句分離,達到去耦合的目的。這樣咱們切換數據庫的時候sql語句並不須要改動。 * */ statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
/** * 這個方法沒有什麼好分析的,就是執行sql語句,並對結果resultSet進行處理。 * 默認的結果集處理器就是DefaultResultSetHandler,其處理方案就是遍歷resultSet集合, * 將全部的數據追加到List<Article>集合中去。 */ @Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
該對象是mapper.xml在對象中的體現,是整個mybatis框架中最爲核心的對象,咱們也能夠沒必要經過xml文件來構建該對象,能夠直接經過編碼方式構建,像最經常使用的簡單的增刪改查操做徹底能夠手動構建mappedStatement對象並加入到mybatis容器中,這樣咱們就不須要在xml文件中手寫CRUD操做了,mybatis-plus框架設計的思想就是鑑於此。ide
其實Mybatis執行一條sql,底層仍是用的最基本的jdbc操做,只不過將事物,數據源,參數的設置,結果的收集轉換都封裝了起來,讓咱們在開發中專一於sql自己,而忽略那些與業務不相關的步驟(結果對象的映射,開啓事物,關閉事物等等操做),提升了項目的內聚性。