真正的Mybatis動態(tài)sql — MyBatis Dynamic SQL


現(xiàn)狀

MyBatis 的強(qiáng)大特性之一便是它的動態(tài) SQL娄柳。如果你有使用 JDBC 或其它類似框架的經(jīng)驗(yàn)寓辱,你就能體會到根據(jù)不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格赤拒,還要注意去掉列表最后一個列名的逗號秫筏。利用動態(tài) SQL 這一特性可以徹底擺脫這種痛苦。

提到Mybatis動態(tài)Sql挎挖,多數(shù)人瞬間想到的畫面是這樣的

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

或者像這樣

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

Mapper XML 中借助 動態(tài) SQL 元素 來實(shí)現(xiàn)SQL語句的條件拼接这敬。

不得不說Mybatis的這個實(shí)現(xiàn)已經(jīng)非常強(qiáng)大了,大大提高了我們拼接SQL的效率蕉朵。
但是對于程序員來說崔涂,在代碼中進(jìn)行條件判斷遠(yuǎn)比在XML中更自如,更靈活始衅,更有底氣冷蚂,我們更希望在享受 強(qiáng)大的 MyBatis 映射 的同時缭保,讓動態(tài)SQL的編寫更加簡潔、更加可控
—— 于是真正的Mybatis動態(tài)SQL —— MyBatis Dynamic SQL 應(yīng)運(yùn)而生蝙茶。


MyBatis Dynamic SQL

項目地址

https://github.com/mybatis/mybatis-dynamic-sql

官方文檔

https://mybatis.org/mybatis-dynamic-sql/docs/CHANGELOG.html

項目介紹

Initial Release - December 17, 2017
Last Published: 23 November 2019 | Version: 1.1.4

2017-12-17艺骂,該項目發(fā)布了第一版本,
最近一個版本是2019-11-23發(fā)布的1.1.4隆夯,該項目是Mybatis官方項目之一钳恕。

作者

Jeff Butler【https://github.com/jeffgbutler】,是 Mybatis組織 成員蹄衷,是 Mybatis 的主要貢獻(xiàn)者忧额,同時也是我們常用工具 MyBatis Generator 的作者。

Mybatis Generator

可想而知宦芦,Mybatis Dynamic SQLMybatis Genrator 很好的支持宙址,在Mybatis Dynamic SQL 最新版發(fā)布的第二天轴脐,2019-11-24 Mybatis Genrator 1.4.0 發(fā)布调卑,這個版本做了較大改動,主要是

  • New Runtime for Kotlin using MyBatis Dynamic SQL
  • New Runtime for Java using MyBatis Dynamic SQL
  • MyBatis Dynamic SQL is now the default runtime
  • Move to Java 8
  • Remove support for iBatis2

主要是移除對iBatis2的支持大咱,同時使用 MyBatis Dynamic SQL 作為默認(rèn)運(yùn)行時恬涧,這無疑給Mybatis用戶帶來了極大便利。在以后的文章會進(jìn)行詳細(xì)介紹碴巾。

簡介

這個項目是用來生成動態(tài)SQL語句的框架溯捆。可以將它看作是額外支持MyBatis3和Spring JDBC的類型安全的SQL模板庫厦瓢。

該庫將生成完整的DELETE提揍、INSERT、SELECT和UPDATE格式化語句煮仇,可由MyBatis或Spring使用劳跃。最普遍的使用情況是生成能夠被Mybatis直接使用的的語句和一組匹配參數(shù)。同時浙垫,它也能夠生成兼容Spring JDBC templates的語句和參數(shù)對象刨仑。

這個庫通過實(shí)現(xiàn)一個 類似于SQL領(lǐng)域?qū)S谜Z言 (domain-specific language,DSL)來工作夹姥,DSL能夠創(chuàng)建一個包含完整SQL語句以及語句所需要全部參數(shù)的對象杉武。這個對象能夠被Mybatis作為傳入Mapper方法的參數(shù)直接使用,正如我們平常所做的那樣辙售。

特性

該庫能生成如下類型的SQL語句(并不止于這些轻抱,最新信息可查看 Change Log):

  • 支持子查詢的靈活的WHERE子句
  • 帶有靈活WHERE子句的DELETE語句
  • 多種INSERT語句:
    • 單條記錄的插入語句并且將會插入null值(一個“完全”插入)
    • 單條記錄的插入語句并且將忽略輸入值為null的列(一個“選擇性”插入)
    • 帶有SELECT語句的插入
    • 多行插入(類似于<foreach/>)
    • 批量插入(Spring Batch 或 JDBC Batch)
    • 支持返回自增主鍵
  • 帶有靈活列表、靈活WHERE子句的SELECT語句旦部,支持distinct祈搜、count(distinct ...)封拧、group by、
    joins, unions, “order by”, 等等夭问。
  • 帶有靈活WHERE子句的UPDATE語句泽西,像插入語句一樣,也兩種更新:
    • “完全”更新
    • “選擇性”更新
目標(biāo)

這個庫的主要目標(biāo)是:

  • 類型安全 — 該庫將盡可能確保參數(shù)類型與數(shù)據(jù)庫列類型匹配
  • 富有表現(xiàn)力 — 語句以一種清晰地傳達(dá)其含義的方式構(gòu)建 (Hamcrest提供的一些靈感)
  • 靈活的 — where子句能夠使用and缰趋、or以及嵌套條件的任意組合來構(gòu)建
  • 可擴(kuò)展的 — 該庫將生成為MyBatis3捧杉、Spring JDBC Template或純JDBC工作的語句。它還可以擴(kuò)展為為其他框架生成子句秘血。如果沒有一個內(nèi)置的 條件語句 充分滿足您的需要味抖,你可以很容易的自定義(畢竟代碼的編寫就是你擅長的事)
  • 小 — 沒有依賴傳遞

提示

Java版本在快速的更新,但是直到今天灰粮,對于Java開發(fā)者影響最大的或許就是Java8仔涩,Java8為開發(fā)者帶了TYPE_USE Annotations,帶來了函數(shù)編程和Lamda粘舟、新的日期和時間處理庫熔脂、Optional等等,甚至有人說 —— Java8給開發(fā)者帶來了工具和機(jī)會【https://www.infoq.com/articles/Type-Annotations-in-Java-8/
比如:

同樣的晰骑,Java8促使了 MyBatis Dynamic SQL 的出現(xiàn)适秩,后續(xù)我們會看到,函數(shù)編程在 MyBatis Dynamic SQL 被運(yùn)用到極致硕舆。

所以秽荞,如果要使用 MyBatis Dynamic SQL ,請保證你的工作環(huán)境為:Java8+

快速使用

步驟

使用 MyBatis Dynamic SQL 需要下列步驟:

  • 創(chuàng)建Table和Column對象
  • (為Mybatis3)創(chuàng)建 Mappers (XML or Java Based)
  • 寫SQL并且去使用它

出于討論的目的抚官,首先我們展示出來用來執(zhí)行CRUD的表結(jié)構(gòu):

create table SimpleTable (
   id int not null,
   first_name varchar(30) not null,
   last_name varchar(30) not null,
   birth_date date not null, 
   employed varchar(3) not null,
   occupation varchar(30) null,
   primary key(id)
);

定義常量Tables和Columns

org.mybatis.dynamic.sql.SqlTable 類被用來定義一個Table扬跋。Table 的定義包含實(shí)際的表明(甚至是合適的schema 和 catalog )。如果期望耗式,Table的alias能夠用于SQL語句胁住。你的Table應(yīng)該繼承SqlTable 類。
org.mybatis.dynamic.sql.SqlColumn 類被用來定義在庫中使用的columns刊咳,SqlColumns 應(yīng)該基于SqlTable來創(chuàng)建缺猛。一個列的定義包含:

  • The Java type
  • The actual column name (an alias can be applied in a select statement)
  • The JDBC type (optional)
  • 如果不需要默認(rèn)的Type Handler剔桨,可以給出需要在Mybatis中使用的Type Handler的完全限定名睦柴。

(注意: 不同于PO(Persistent Object 持久化對象醉途,它跟持久層(通常是關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)結(jié)構(gòu)形成一一對應(yīng)的映射關(guān)系),在這里Table和Column對象,是對 以及 的嚴(yán)格意義的抽象酵镜,它們被定義為靜態(tài)常量碉碉,允許你在SQL語句構(gòu)建中不斷重用,就像是在命令行中手寫SQL語句那樣淮韭。

建議使用如下使用模式來提供最大的靈活性垢粮。這個模式允許你用“qualified” or “un-qualified” 的習(xí)慣來使用你的Tables和columns,就像在命令行中寫SQL語句那樣靠粪。例如蜡吧,可以將下面這個column寫為firstName或simpleTable.firstName。

package examples.simple;

import java.sql.JDBCType;
import java.util.Date;

import org.mybatis.dynamic.sql.SqlColumn;
import org.mybatis.dynamic.sql.SqlTable;

public final class SimpleTableDynamicSqlSupport {
    public static final SimpleTable simpleTable = new SimpleTable();
    public static final SqlColumn<Integer> id = simpleTable.id;
    public static final SqlColumn<String> firstName = simpleTable.firstName;
    public static final SqlColumn<String> lastName = simpleTable.lastName;
    public static final SqlColumn<Date> birthDate = simpleTable.birthDate;
    public static final SqlColumn<Boolean> employed = simpleTable.employed;
    public static final SqlColumn<String> occupation = simpleTable.occupation;

    public static final class SimpleTable extends SqlTable {
        public final SqlColumn<Integer> id = column("id", JDBCType.INTEGER);
        public final SqlColumn<String> firstName = column("first_name", JDBCType.VARCHAR);
        public final SqlColumn<String> lastName = column("last_name", JDBCType.VARCHAR);
        public final SqlColumn<Date> birthDate = column("birth_date", JDBCType.DATE);
        public final SqlColumn<Boolean> employed = column("employed", JDBCType.VARCHAR, "examples.simple.YesNoTypeHandler");
        public final SqlColumn<String> occupation = column("occupation", JDBCType.VARCHAR);

        public SimpleTable() {
            super("SimpleTable");
        }
    }
}

創(chuàng)建 MyBatis3 Mappers

這個庫能創(chuàng)建用作MyBatis mapper輸入的類占键。這些類包含生成的SQL以及匹配的參數(shù)集合昔善。兩者都是MyBatis所需要的。這些對象是MyBatis mapper方法的唯一參數(shù)畔乙。

(注意:MyBatis Dynamic SQL 不需要XML文件就能工作的很好君仆,但并不意味著不支持XML,畢竟 MyBatis 最初被設(shè)計為是一個 XML 驅(qū)動的框架牲距。當(dāng)你使用關(guān)聯(lián)查詢返咱,需要復(fù)雜的映射,那么使用XML <ResultMap> 與 MyBatis Dynamic SQL 結(jié)合起來或者是更好選擇嗅虏,你的XML或許只需要包含一些<ResultMap>)

例如洛姑,一個Mapper可以像下面這樣:

package examples.simple;

import java.util.List;

import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.ResultMap;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.SelectProvider;
import org.apache.ibatis.annotations.UpdateProvider;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.dynamic.sql.delete.render.DeleteStatementProvider;
import org.mybatis.dynamic.sql.insert.render.InsertStatementProvider;
import org.mybatis.dynamic.sql.select.render.SelectStatementProvider;
import org.mybatis.dynamic.sql.update.render.UpdateStatementProvider;
import org.mybatis.dynamic.sql.util.SqlProviderAdapter;

@Mapper
public interface SimpleTableAnnotatedMapper {

    @InsertProvider(type=SqlProviderAdapter.class, method="insert")
    int insert(InsertStatementProvider<SimpleTableRecord> insertStatement);

    @UpdateProvider(type=SqlProviderAdapter.class, method="update")
    int update(UpdateStatementProvider updateStatement);

    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @Results(id="SimpleTableResult", value= {
            @Result(column="A_ID", property="id", jdbcType=JdbcType.INTEGER, id=true),
            @Result(column="first_name", property="firstName", jdbcType=JdbcType.VARCHAR),
            @Result(column="last_name", property="lastName", jdbcType=JdbcType.VARCHAR),
            @Result(column="birth_date", property="birthDate", jdbcType=JdbcType.DATE),
            @Result(column="employed", property="employed", jdbcType=JdbcType.VARCHAR, typeHandler=YesNoTypeHandler.class),
            @Result(column="occupation", property="occupation", jdbcType=JdbcType.VARCHAR)
    })
    List<SimpleTableRecord> selectMany(SelectStatementProvider selectStatement);

    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    @ResultMap("SimpleTableResult")
    SimpleTableRecord selectOne(SelectStatementProvider selectStatement);

    @DeleteProvider(type=SqlProviderAdapter.class, method="delete")
    int delete(DeleteStatementProvider deleteStatement);

    @SelectProvider(type=SqlProviderAdapter.class, method="select")
    long count(SelectStatementProvider selectStatement);
}

用Mybatis3執(zhí)行SQL

在DAO或服務(wù)類中,可以使用生成的語句作為映射器方法的輸入皮服。下面是一個來自examples.simple.SimpleTableAnnotatedMapperTest 的示例:

    @Test
    public void testSelectByExample() {
        try (SqlSession session = sqlSessionFactory.openSession()) {
            SimpleTableAnnotatedMapper mapper = session.getMapper(SimpleTableAnnotatedMapper.class);
            
            SelectStatementProvider selectStatement = select(id.as("A_ID"), firstName, lastName, birthDate, employed, occupation)
                    .from(simpleTable)
                    .where(id, isEqualTo(1))
                    .or(occupation, isNull())
                    .build()
                    .render(RenderingStrategies.MYBATIS3);

            List<SimpleTableRecord> rows = mapper.selectMany(selectStatement);

            assertThat(rows.size()).isEqualTo(3);
        }
    }

最后

想要了解更多,Mybatis Dynamic SQL 高級用法参咙,工作原理龄广,實(shí)踐中的坑,以及如何更友好的使用 Mybatis Generator 可以關(guān)注 公眾號:流花鬼的博客 蕴侧,持續(xù)更新中择同。。净宵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敲才,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子择葡,更是在濱河造成了極大的恐慌紧武,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敏储,死亡現(xiàn)場離奇詭異阻星,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)已添,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門妥箕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滥酥,“玉大人,你說我怎么就攤上這事畦幢】参牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵宇葱,是天一觀的道長禾怠。 經(jīng)常有香客問我,道長贝搁,這世上最難降的妖魔是什么吗氏? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮雷逆,結(jié)果婚禮上弦讽,老公的妹妹穿的比我還像新娘。我一直安慰自己膀哲,他們只是感情好往产,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著某宪,像睡著了一般仿村。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上兴喂,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天蔼囊,我揣著相機(jī)與錄音,去河邊找鬼衣迷。 笑死畏鼓,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的壶谒。 我是一名探鬼主播云矫,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汗菜!你這毒婦竟也來了让禀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤陨界,失蹤者是張志新(化名)和其女友劉穎巡揍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體普碎,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吼肥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缀皱。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡斗这,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啤斗,到底是詐尸還是另有隱情表箭,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布钮莲,位于F島的核電站免钻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崔拥。R本人自食惡果不足惜极舔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望链瓦。 院中可真熱鬧拆魏,春花似錦、人聲如沸慈俯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽洋闽。三九已至,卻和暖如春氛琢,著一層夾襖步出監(jiān)牢的瞬間阳似,已是汗流浹背撮奏。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工畜吊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玲献,地道東北人瓢娜。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓眠砾,卻偏偏與公主長得像褒颈,于是被迫代替她去往敵國和親谷丸。 傳聞我的和親對象是個殘疾皇子刨疼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容