前言
這段時間項目中需要寫一些api,我負責其中的幾個,也是第一次真正意義上的使用mybatis,所以我認爲有必要作一個總結來反映我學習的成果。
首先我們應該都知道,mybatis的sql語句要麼是寫在xml文件中,要麼直接使用@select
註解在mapper類中寫。但我個人偏向於寫在xml文件中,因爲有時候sql語句比較長、比較複雜,直接用註解的話可能一眼看過去並不知道是實現了什麼功能,相反xml文件中使用特有的標籤能幫助我們理解語句完成的功能。
1. 動態sql
mybatis寫動態sql真的非常非常方便簡潔了,好用到想爲它瘋狂打call!!!
例如有三個可選的條件,那麼sql語句可以這麼寫:
SELECT
A.authorName,A.country,B.bookName
FROM author AS A
LEFT OUTER JOIN book AS B ON (A.author_id = B.author_id AND A.book_id = B.book_id)
<where>
<if test="authorName!=null"></if>
A.author_name = #{authorName}
<if test="bookName!=null">
AND B.bookName = #{bookName}
</if>
<if test="publicTime!=null">
AND B.public_time = #{publicTime}
</if>
</where>
GROUP BY A.authorName
在這個例子中,需要使用到幾個知識點:
a.表與表的連接
b.動態條件判斷語句的使用
c.<if>
元素中,如果包括變量,不需要寫成#{varName}的形式,而是直接寫它的變量名就可以了。(我就是在這個地方吃了虧的,調試很久才發現,希望看到的讀者不要踩這個雷。)
2. 多張表連接的寫法
SELECT A.id,A.app_id,A.app_name,B.menu ,C.author_name
FROM user_collection_app AS A
LEFT OUTER JOIN app AS B ON A.app_id = B.app_id
LEFT OUTER JOIN auth_user AS C ON B.author_id = C.author_id
WHERE A.user_id = #{userId} GROUP BY A.app_id
3. 可以定義一個公共的接口實現基本的增刪改查
如果經常需要使用基本的增刪改查方法,爲了減少每次書寫語句的工作量,可以定義一個通用接口,讓其他的接口去繼承它,那麼每次要使用的時候,就能直接去調用了,這可以節約不少時間跟精力。
例如:
public interface MyService<T> {
void save(T model);//持久化
void save(List<T> models);//批量持久化
void deleteById(Integer id);//通過主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通過ID查找
T findById(Long id);//通過ID查找
List<T> findBy(T record);
T findBy(String fieldName, Object value) throws TooManyResultsException; //通過Model中某個成員變量名稱(非數據表中column的名稱)查找,value需符合unique約束
List<T> findByIds(String ids);//通過多個ID查找//eg:ids -> “1,2,3,4”
List<T> findByCondition(Example condition);//根據條件查找
List<T> findAll();//獲取所有
}
4. xml文件中特殊字符的寫法
在sql語句中,難免會用到類似”<“、”>“這樣的符號,但是xml文件卻不能識別。所以要用的時候,必須進行轉義。
< <
> >
& &
' '
" "
也可以使用<![CDATA[ ]]>符號進行說明,格式爲<![CDATA[ sql語句 ]]>
參考博客:mybatis的一些特殊符號標識(大於,小於,等於,不等於)
5. 在數據庫篩選datetime類型的字段
除了select* fromtable where datetime between datetime1 and datetime2;
這種寫法以外,還可以寫成:
select * from table where unix_timestamp(datetime) > unix_timestamp(datetime1) and unix_timestamp(datetime) < unix_
timestamp(datetime2);
這種形式,unix_timestamp函數是將字符型的時間,轉成unix時間戳。因爲不轉換的話雖然結果不報錯,但是並不準確。
6. 關於多表的連接可能會遇到的小問題
兩張表連接後(不管是左連接還是交叉連接),字段爲null的屬性,會被直接略掉,爲了避免這種情況的發生,有兩種方法:
a. 在sql語句中爲可能出現null的屬性添加ifnull函數,例如:
`select ifnull(status,0) AS status from table1 left join table2;`
當status爲null時,自動設爲默認值0,但是必須要爲它設置一個別名,否則有可能識別失敗。
當然如果設計的兩張表邏輯上比較簡單,字段並不是很多的時候,可以使用上面的方法。
但如果涉及兩張表以上的連接,並且字段較多的時候,明顯使用ifnull()函數就不太合適了,
這個時候我會更喜歡使用下面這種方式。
b. 用mybatis查詢返回的類型與entity聯繫起來,這個entity應在xml文件中作好映射
示例代碼:
<!--xml文件-->
<resultMap id="DevelopersResultMap" type="com.acloudapp.entity.developers.Developer">
<id column="id" jdbcType="INTEGER" property="id"></id>
<result column="user_id" jdbcType="VARCHAR" property="developerId" />
<result column="real_name" jdbcType="VARCHAR" property="developerName" />
<result column="nick_name" jdbcType="VARCHAR" property="nickName"/>
<result column="head_portrait_img" jdbcType="VARCHAR" property="headPortrait"/>
<result column="school_name" jdbcType="VARCHAR" property="school"/>
<result column="mobile" jdbcType="VARCHAR" property="phoneNumber"/>
<result column="create_time" jdbcType="TIMESTAMP" property="time" />
<result column="type" jdbcType="VARCHAR" property="type" />
<result column="province" jdbcType="VARCHAR" property="province" />
<result column="city" jdbcType="VARCHAR" property="city" />
</resultMap>
//Developer實體
public class Developer {
private String developerId;
private String developerName;
private String nickName;
private String headPortrait;
private String school;
private String phoneNumber;
private Date time;
private String type;
private String province;
private String city;
//省略getter和setter方法
}
<!--sql語句-->
SELECT
auth_user.user_id,
auth_user.real_name,
auth_user.create_time,
auth_user.nick_name,
auth_user.head_portrait_img,
auth_user.mobile,
auth_user.school_name,
auth_user.type,
base_districts.city AS city,
base_districts1.province AS province,
#{recommendationId} AS recommendationId,
#{position} AS position,
#{title} AS title
FROM auth_user
LEFT JOIN base_districts ON base_districts.adcode=auth_user.adcode
WHERE auth_user.user_id in (
SELECT developers_id
FROM user_quality_developers
WHERE user_quality_developers.recommendation_id = #{recommendationId}
)
ORDER BY auth_user.create_time DESC
類似這種sql語句,比較複雜,且字段比較多的,我們就需要用一個實體與其進行映射。其中有的字段,如recommendationId、position,由於它們並沒有使用到表的字段,因此不需要寫入映射。
7. 內連接和外連接
8. 一段邏輯很複雜的sql語句
爲了寫這個查詢語句,簡直是絞盡腦汁,茶不思飯不想,找了許多資料(甚至去翻以前數據庫原理課的ppt),最後終於得到了相要的結果!!!喜大普奔啊!!!儘管知道邏輯複雜,肯定要耗費一些時間去查,只不過我已經儘量將語句精簡,希望在效率上不要太拖後腿。
這個語句用到了:子查詢、表連接、嵌套查詢、case-when句式、動態sql(喪心病狂啊有沒有???),難點是:我需要返回一個自定義的字段,該字段爲布爾類型的,因此我要用case-when進行邏輯判斷以確定返回的是1還是0;另外,在此基礎上,我還需要使用該自定義字段作爲外層查詢的條件進行篩選。。。總之我有點語無倫次了。直接po代碼- -。
SELECT*
FROM(
SELECT
A.operate_type AS appMenu,
A.time AS time,
A.operate_obj_name AS appName,
B.icon,
B.app_id AS appId,
CASE
when (SELECT COUNT(*) FROM comment
WHERE comment.user_id = #{userId} AND comment.app_id = A.operate_obj_id) > 0 then 1
else 0 END AS isEvaluate
FROM auth_user_log AS A
LEFT OUTER JOIN app AS B ON A.operate_obj_id = B.app_id
<where>
<if test="userId!=null"></if>
A.user_id = #{userId}
<if test="startTime!=null">
AND unix_timestamp(A.time) > unix_timestamp(#{startTime})
</if>
<if test="endTime!=null">
AND unix_timestamp(A.time) < unix_timestamp(#{endTime})
</if>
</where>
ORDER BY A.time DESC
)C
<where>
<if test="isEvaluate!=null">
isEvaluate = #{isEvaluate}
</if>
</where>
可以明顯看到,isEvaluate字段在外層查詢中被當作一個查詢的條件了,另外case-when裏的查詢語句的功能是判斷comment表中是否存在匹配的記錄(存在說明已評價,不存在則說明未評價),還有另一種簡單的寫法:
SELECT 1 FROM comment
WHERE comment.user_id = '10089' AND comment.app_id = A.operate_obj_id limit 1
它返回的要麼就是1,要麼就是0,十分易於判斷。特別解釋下limit 1,mysql在找到一條記錄後就不會往下繼續找了。性能提升很多。
參考博客:
- Mysql判斷記錄是否存在
- mysql 中case when 的用法
- Mysql判斷記錄是否存在
-
寫完這段sql語句,感覺收穫良多,以前學的數據庫查詢,有模糊不清的,現在也感覺變清晰了。總之,多寫、多練總是沒錯的。
9. xml文件中
#
和$
的區別
#
將傳入的數據都當成一個字符串,會對自動傳入的數據加一個雙引號。而$
將傳入的數據直接顯示生成在sql中,可理解爲字符串替換。
結語
使用mybatis倆仨月來,深刻體會到了它的輕便與魅力,如果還回去使用老舊的sql拼接,就真的太落後了。使用之餘,進行了一番感嘆,
希望能夠將mybatis學的更透徹。