一直在使用Mybatis這個ORM框架,都是使用mybatis里的一些常用功能。今天在項目開發中有個業務是需要限制各個用戶對某些表里的字段查詢以及某些字段是否顯示,如某張表的某些字段不讓用戶查詢到。這種情況下,就需要構建sql來動態傳入表名、字段名了。現在對解決方法進行下總結,希望對遇到同樣問題的伙伴有些幫助。
動態SQL是mybatis的強大特性之一,mybatis在對sql語句進行預編譯之前,會對sql進行動態解析,解析為一個BoundSql對象,也是在此處對動態sql進行處理。下面讓我們先來熟悉下mybatis里#{}與${}的用法:
在動態sql解析過程,#{}與${}的效果是不一樣的:
#{ } 解析為一個 JDBC 預編譯語句(prepared statement)的參數標記符。
如以下sql語句
select * from user where name = #{name};
會被解析為:
select * from user where name = ?;
可以看到#{}被解析為一個參數占位符?。
${ } 僅僅為一個純碎的 string 替換,在動態 SQL 解析階段將會進行變量替換
如以下sql語句:
select * from user where name = ${name};
當我們傳遞參數“sprite”時,sql會解析為:
select * from user where name = "sprite";
可以看到預編譯之前的sql語句已經不包含變量name了。
綜上所得, ${ } 的變量的替換階段是在動態 SQL 解析階段,而 #{ }的變量的替換是在 DBMS 中。 |
#{}與${}的區別可以簡單總結如下:
- #{}將傳入的參數當成一個字符串,會給傳入的參數加一個雙引號
- ${}將傳入的參數直接顯示生成在sql中,不會添加引號
- #{}能夠很大程度上防止sql注入,${}無法防止sql注入
${}在預編譯之前已經被變量替換了,這會存在sql注入的風險。如下sql
select * from ${tableName} where name = ${name}
如果傳入的參數tableName為user; delete user; --,那么sql動態解析之后,預編譯之前的sql將變為:
select * from user; delete user; -- where name = ?;
--之后的語句將作為注釋不起作用,頓時我和我的小伙伴驚呆了!!!看到沒,本來的查詢語句,竟然偷偷的包含了一個刪除表數據的sql,是刪除,刪除,刪除!!!重要的事情說三遍,可想而知,這個風險是有多大。
- ${}一般用於傳輸數據庫的表名、字段名等
- 能用#{}的地方盡量別用${}
進入正題,通過上面的分析,相信大家可能已經對如何動態調用表名和字段名有些思路了。示例如下:
<select id="getUser" resultType="java.util.Map" parameterType="java.lang.String" statementType="STATEMENT"> select ${columns} from ${tableName} where COMPANY_REMARK = ${company} </select>
要實現動態調用表名和字段名,就不能使用預編譯了,需添加statementType="STATEMENT"" 。
statementType:STATEMENT(非預編譯),PREPARED(預編譯)或CALLABLE中的任意一個,這就告訴 MyBatis 分別使用Statement,PreparedStatement或者CallableStatement。默認:PREPARED。這里顯然不能使用預編譯,要改成非預編譯。
其次,sql里的變量取值是${xxx},不是#{xxx}。
因為${}是將傳入的參數直接顯示生成sql,如${xxx}傳入的參數為字符串數據,需在參數傳入前加上引號,如:
String name = "sprite"; name = "'" + name + "'";
mybatis動態調用表名和字段名,還可以應用於日志的收集上,如數據庫的日志表,每隔一個月動態建一個日志表,表名前綴相同(如log_201610,log_201611等),這樣實現日志的分月分表存儲,方便日志的分析。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
用了mybatis很長一段時間了,但是感覺用的都是比較基本的功能,很多mybatis相對ibatis的新功能都沒怎么用過。比如其內置的注解功能之類的。這次遇到了一個問題,每次我們在配置mybaits時,需要在mapping.sql.xml文件中寫對應的執行sql腳本。這時我們一般會先定義實體類來作為sql的返回類型或者執行sql的參數類型。比如如下代碼
- <select id="queryApplyStatusNum" parameterType="ApplyCriteria"
- resultType="ApplyStatusNumDto">
- select
- o.statecode as statusCode,count(*) as statusNum
- from
- TM_ConsultationApply o
- where
- <if test="doctorCode != null and doctorCode != ''"> o.DoctorCode = #{doctorCode} and </if>
- <if test="beginDate != null and endDate != null">
- ((trunc(o.AppointBeginDate )<=
- to_date(#{endDate},'yyyy/mm/dd')) and
- (to_date(#{beginDate},'yyyy/mm/dd')
- <=o.AppointEndDate)) and </if>
- 1=1
- group by o.statecode
- </select>
這是個很簡單的查詢,其中parameterType為對應的參數實體類,這些類中的字段將會替換sql語句中的類似#{}的語句,使之成為完整的sql 語句。同樣的,resultType同樣對應的是返回值的實體類。這里可以細說一下,其實在mybatis中,無論你指定還是不指定返回類型,mybatis都會默認的先將查詢回的值放入一個hashMap中(如果返回的值不止一條就是一個包含hashMap的list)。這其中的區別在於,如果你指定了返回類型,mybatis將會根據返回類型的實體類來從hashMap中獲取值並set到這個實體類中。如果不指定就默認返回一個HashMap<String,Object>(List<HashMap<String,Object>>)。
okay~大概講解到此,說說這次的問題。以前都是指定了實體類,然后寫好sql語句直接套用就可以了。但是現在有個問題,萬一你的物理模型不確定,也即是你的表結構不確定,甚至連表名字都不確定該怎么辦呢?我這次遇到了這個問題。我們有個需求,事先定義好了很多數據集的信息模型,針對這些信息模型生成物理模型。而我們需要針對這些物理模型進行操作。而這些數據集一旦更新,信息模型以及物理模型都要變動,所以事先不可能完全確定物理表結構等等信息。此時應該怎么在mybatis中進行處理呢?
這里在說一下mybatis中一個屬性:statementType。這個屬性的作用是告訴mybatis我們寫的這個sql到底是預編譯(PRESTATEMENT)還是非預編譯(STATEMENT)的。有什么區別呢?如果是預編譯的,那么系統在初始化時就會讀取這段sql代碼,將指定的實體類中的字段替換了類似#{}這樣的語句,就是形成了類似這樣的語句:
"select * from tableName where code=?" 這個時候你在系統運行時再想向這句sql中替換tableName或者code,結果可想而知。如果是非預編譯呢,結果剛好相反,他會在系統運行時才會去生成這樣類似的語句。此時就可以去替換這些動態的字段或者表名之類。這樣在結合之前所講的返回類型的設置,我們的問題就解決了
。我們可以不用設定參數和返回類型的實體類,只需要形成一個動態的表名和字段名的列表類。就可以動態對那些未知的物理模型進行操作.如下代碼可作參考:
- <select id="queryMetaList" resultType="Map" statementType="STATEMENT">
- select * from ${tableName} t where
- <foreach item="item" index="index" collection="field" open=" "
- separator="and" close=" ">
- <choose>
- <when test="item.fieldType == 'DATE' and item.dateQueryFlag == 0">
- ${item.fieldCode} between
- to_date('${item.fieldValue}','yyyy-mm-dd
- hh24:mi:ss')
- </when>
- <when test="item.fieldType == 'DATE' and item.dateQueryFlag == 1">
- to_date('${item.fieldValue}','yyyy-mm-dd
- hh24:mi:ss')
- </when>
- <when test="item.fieldItemCode != null and item.fieldItemCode != ''">
- ${item.fieldCode} =
- '${item.fieldItemCode}'
- </when>
- <otherwise>
- ${item.fieldCode} =
- '${item.fieldValue}'
- </otherwise>
- </choose>
- </foreach>
- </select>
對了,漏了一句,如果是非預編譯的話,最好使用${}而不是#{}
-----------------------------------------------------------------------------------------------------