在我上大學的時候,最流行的JavaEE框架是 SSH (Struts+Spring+Hibernate),如今同窗們應該都在學 SSM(Spring+SpringMVC+MyBatis)了。從歷史演變來看,Spring是愈來愈強大,而MyBatis則是頂替了Hibernate的地位。今天的「主角」就是MyBatis。前端
咱們先聊一聊ORM(Object Relational Mapping),翻譯爲「對象關係映射」,就是經過實例對象的語法,完成關係型數據庫的操做的技術。ORM用於實現面向對象編程語言裏不一樣類型系統的數據之間的轉換,實際上是建立了一個可在編程語言裏使用的"虛擬對象數據庫"。java
ORM 把數據庫映射成對象:mysql
基於傳統ORM框架的產品有不少,其中就有耳熟能詳的Hibernate。ORM經過配置文件,使數據庫表和JavaBean類對應起來,提供簡便的操做方法,增、刪、改、查記錄,再也不拼寫字符串生成sql,編程效率大大提升,同時減小程序出錯機率,加強數據庫的移植性,方便測試。git
可是有些時候我仍是喜歡原生的JDBC,由於在某些特殊的應用場景中,對於sql的應用複雜性比較高,或者須要對sql的性能進行優化,這些ORM框架就顯得很笨重。Hibernate這類「全自動化」框架,對數據庫結構封裝的較爲完整,這種一站式的解決方案未必適用於全部的業務場景。程序員
幸運的是,不僅我一我的有這種感覺,好久以前你們開始關注一個叫 iBATIS 的開源項目,它相對傳統ORM框架而言更加的靈活,被定義爲「半自動化」的ORM框架。2010年,谷歌接管了iBATIS,MyBatis就隨之誕生了。雖然2010年我都還沒上大學,但很惋惜,MyBatis在國內的大火的比較晚,我在校園期間都沒有接觸過。github
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎全部的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可使用簡單的 XML 或註解來配置和映射原生類型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 對象)爲數據庫中的記錄。算法
MyBatis爲半自動化,須要本身書寫sql語句,須要本身定義映射。增長了程序員的一些操做,可是帶來了設計上的靈活。而且也是支持Hibernate的一些特性,如延遲加載,緩存和映射等,並且隨之SSM架構的成熟,MyBatis確定會被授予有愈來愈多新的特性。那麼接下來就開始 MyBatis 的實戰演練吧!spring
下面講解在SpringBoot 中,使用MyBatis的基本操做。sql
在SpringBoot中集成 MyBatis 的方式很簡單,只須要引用 MyBatis的starter包便可,不過針對不一樣的數據源,須要導入所依賴的驅動jar包(如:mysql(mysql-connector-java-x.jar)/oracle(ojdbcx.jar)/sql server(sqljdbcx.jar)等)數據庫
pom.xml(示例)
<!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.2</version> </dependency> <!--oracle jdbc--> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>6</version> </dependency> <!--druid 數據源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency>
對於相關數據源的鏈接信息,須要在application.properties中配置,一樣提供示例
# Oracle數據庫的鏈接信息 spring.datasource.url=jdbc:oracle:thin:@ip:port/instance spring.datasource.username=username spring.datasource.password=password spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver #mybatis 駝峯式命名映射,可將ResultMap返回值經過駝峯式映射給pojo mybatis.configuration.map-underscore-to-camel-case=true #mybatis xml文件路徑 mybatis.mapper-locations=classpath:mapper/*Mapper.xml #開啓mybatis dao層的日誌 logging.level.com.df.stage.tasktimer.mapper=debug
MyBatis3 以前,須要手動獲取SqlSession,並經過命名空間來調用MyBatis方法,比較麻煩。而MyBatis3 就開始支持接口的方式來調用方法,這也成爲當前即爲廣泛的用法,本文就以此爲例。
經過在Java中寫dao層的 Interface 類,而後與之對應寫一個 xml 文件,做爲 Interface 的實現,以下:
DfTimerTaskMapper.java
@Mapper public interface DfTimerTaskMapper { /** * 查詢 df_timer_task 表 * @param searchValue * @return */ public List<DfTimerTask> queryTask(@Param("searchValue") String searchValue); }
DfTimerTaskMapper.xml
<?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="com.df.stage.tasktimer.mapper.DfTimerTaskMapper"> <select id="queryTask" resultType="com.df.stage.tasktimer.pojo.DfTimerTask" parameterType="String"> select * from df_timer_task <where> <if test="searchValue!=null"> task_code like '%'||#{searchValue}||'%' or method_type =#{searchValue} or method_name like '%'||#{searchValue}||'%' or status =#{searchValue} </if> </where> </select> </mapper>
上一節中咱們在application.properties 文件中有配置MyBatis中 xml
配置文件的位置,在SpringBoot 項目啓動時則會掃描全部Mapper的xml文件,並經過 mapper的namespace 找到與之對應的dao層 Interface類,將其註冊爲Spring的Bean,那麼就能夠經過IOC,隨便調用 dao層的方法啦。
能夠看到我在示例中用到了 where、if 等標籤,正是這些標籤使得MyBatis更加具備靈活性。MyBatis的動態sql,避免了不少其餘框架拼接 SQL 語句的痛苦。
人老是趨向於懶惰的,我開始指望於jdbc的一些特性。如今寫一個dao層方法,還要在xml中寫對應的實現,能不能作到我只寫Java就能夠了?很幸運,我能想到的MyBatis都作到了。
Java中自定義註解類,就是自定義了想要規範輸入的元數據。就像MyBatis 的xml中那些標籤同樣,一樣能夠經過在Java接口中添加註解的方式,實現方法的sql。例如:
DfTimerTaskMapper.java
@Mapper public interface DfTimerTaskMapper { /** * 查詢已存在task_code 的數量 * @param taskCode * @return */ @Select("select count(1) from df_timer_task where task_code=#{taskCode}") public int countTask(@Param("taskCode")String taskCode); }
只須要經過在 Interface 的抽象方法上方,經過註解sql,就能實現dao層的方法,不須要再寫 Mapper的xml。
那麼在平常開發中,「xml配置」和「註解」這兩種方式咱們該作何選擇呢?個人偏向是簡單的sql經過註解方式實現。複雜的sql,例如須要用到動態sql,或者sql語句過長鬚要排版美化的,都經過xml配置的方式實現。固然,仁者見仁,智者見智。你怎麼喜歡就怎麼來,MyBatis做爲「半自動化」ORM框架,就是讓程序員能減小框架的束縛。
在爲前端報表數據查詢寫接口的時候,咱們常常須要分頁返回數據。例如:返回第 1~ 20行,或21~40行數據等。咱們不只須要返回指定行數區間的數據,還須要算出來該查詢條件下一共有多少行數據。我寫過不少數據庫的分頁sql:Oracle經過rownum,mysql經過 limit,sql server經過 top,等等。標準不同,當分頁的查詢多了,代碼寫起來很冗餘。網上和MyBatis完美結合的分頁插件,下面我推薦的是PageHelper。
先直接上使用的代碼吧,使用PageHelper插件僅須要經過pom.xml添加jar包
<!--分頁器 pagehelper--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.10</version> </dependency>
使用PageHelper的方式也很簡單,先執行PageHelper.startPage(pageIndex,pageSize,true)方法,傳入你定義的頁面碼pageIndex,和每頁的記錄數pageSize,而後緊跟着執行你自定義的查詢語句。最後根據查詢語句返回的對象列表,建立PageInfo的實例,PageInfo對象的屬性裏面就包含所需的:總記錄數、總頁數、查詢數據列表,等等。
PageHelper.startPage(pageIndex,pageSize,true); List<DfTimerTaskLogV> dfTimerTaskLogVList= dfTimerTaskLogMapper.queryLog(executeStatus, taskCode,methodType,methodName,fromBeginTime,toBeginTime,fromFinishTime,toFinishTime); PageInfo<DfTimerTaskLogV> pageInfo=new PageInfo<>(dfTimerTaskLogVList); // 分頁查詢的數據集 List<DfTimerTaskLogV> :pageInfo.getList(); //總記錄數 long:pageInfo.getTotal();
若是咱們打印出dao層的執行sql,會發現雖然咱們的的查詢語句中並無實現分頁,可是PageHelper已經替咱們加上了分頁的sql。PageHelper首先將前端傳遞的參數保存到Page這個對象中,接着將Page的副本存放入ThreadLoacl中,這樣能夠保證分頁的時候,參數互不影響,接着利用了MyBatis提供的攔截器,取得ThreadLocal的值,從新拼裝分頁SQL,完成分頁。
PageHelper針對分頁查詢返回的數據集提供了封裝類PageInfo,但團隊開發過程當中,PageInfo定義的屬性名不必定符合咱們的要求,那咱們能不能自定義返回的類類型呢?固然能夠。上節在分析PageInfo的實現原理時瞭解到,是經過Page對象存儲在ThreadLocal中實現,咱們只要獲取Page值就好了。下面提供我封裝的類
PageQueryResult.java
/** * 基於分頁的方法改造 * PageHelper -> PageInfo -> PageSerializable * @param <T> */ public class PageQueryResult<T> implements Serializable { private static final long serialVersionUID = 1L; protected long count; protected List<T> result; public PageQueryResult() { } public PageQueryResult(List<T> list) { this.result = list; if (list instanceof Page) { this.count = ((Page) list).getTotal(); } else { this.count = (long) list.size(); } } public static <T> PageQueryResult<T> of(List<T> list) { return new PageQueryResult(list); } public long getCount() { return this.count; } public void setCount(long total) { this.count = total; } public List<T> getResult() { return this.result; } public void setResult(List<T> list) { this.result = list; } public String toString() { return "PageQueryResult{count=" + this.count + ", result=" + this.result + '}'; } }
調用方式示例:
PageHelper.startPage(pageIndex,pageSize,true); PageQueryResult<DfTimerTaskLogV> pageQueryResult=new PageQueryResult<>(dfTimerTaskLogMapper.queryLog(executeStatus, taskCode,methodType,methodName,fromBeginTime,toBeginTime,fromFinishTime,toFinishTime)); return Response.ok().data(pageQueryResult);
使用緩存可使應用更快地獲取數據,避免頻繁的數據庫交互,尤爲是在查詢越多、緩存命中率越高的狀況下,使用緩存的做用就越明顯。MyBatis 做爲持久化框架,提供了很是強大的查詢緩存特性,能夠很是方便地配置和定製使用。通常提到 MyBatis 緩存的時候,都是指二級緩存。一級緩存(也叫本地緩存)默認會啓用,而且不能控制,所以不多會提到。
咱們先看看SqlSession的定義:在 MyBatis 中,你可使用 SqlSessionFactory 來建立 SqlSession。一旦你得到一個 session 以後,你可使用它來執行映射了的語句,提交或回滾鏈接,最後,當再也不須要它的時候,你能夠關閉 session。使用 MyBatis-Spring 以後,你再也不須要直接使用 SqlSessionFactory 了,由於你的 bean 能夠被注入一個線程安全的 SqlSession,它能基於 Spring 的事務配置來自動提交、回滾、關閉 session。咱們在使用MyBatis時是能夠手動建立和關閉SqlSession,但也能夠向本文同樣,經過接口的方式調用方法,將SqlSession交給Spring框架來接管。
一級緩存是默認開啓的。MyBatis提供了一級緩存的方案來優化在數據庫會話間重複查詢的問題。實現的方式是每個SqlSession中都持有了本身的緩存,一種是SESSION級別,即在一個MyBatis會話中執行的全部語句,都會共享這一個緩存。一種是STATEMENT級別,能夠理解爲緩存只對當前執行的這一個statement有效。
MyBatis一般和Spring進行整合開發。Spring將事務放到Service中管理,對於每個service中的sqlsession是不一樣的,這是經過mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer建立sqlsession自動注入到service中的。 每次查詢以後都要進行關閉sqlSession,關閉以後數據被清空。因此spring整合以後,若是沒有事務,一級緩存是沒有意義的。
二級緩存默認關閉,它是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。
例如:UserMapper有一個二級緩存區域(按namespace分),其它mapper也有本身的二級緩存區域(按namespace分)。每個namespace的mapper都有一個二級緩存區域,兩個mapper的namespace若是相同,這兩個mapper執行sql查詢到數據將存在相同的二級緩存區域中。
默認的二級緩存會有以下效果。
對於SpringBoot項目,開啓二級緩存須要在配置文件中加上@EnableCaching 的註解。並且二級緩存通常配合Redis之類的key-value 數據庫來使用,具體的實踐,本文將不作詳述。