前言
在我們日常工作中,使用MyBatis除了做一般的數據查詢之外,還有很多業務場景下需要我們針對不同條件從數據庫中獲取到滿足指定條件的數據,這時候我們應該如何來做呢?針對每種條件封裝一個方法來使用?這肯定是不科學的,這樣會導致項目中方法數量直線上升,大大增加了開發和維護的工作量。與之相反的就是把一些比較類似的查詢操作封裝爲一個方法,然後通過傳入條件不同來執行不同的SQL查詢操作,這就需要使用到MyBatis所提供的動態SQL了,本篇我們就着手講一下MyBatis動態SQL的相關知識。
準備工作
在開始講動態SQL之前,我們先把所需要數據提前準備好。
創建一張數據表t_student
CREATE TABLE t_student (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '自動編號',
stu_no VARCHAR(20) NOT NULL UNIQUE KEY COMMENT '學號',
stu_name VARCHAR(50) NOT NULL COMMENT '姓名',
gender VARCHAR(10) NOT NULL COMMENT '性別',
birthday DATE COMMENT '出生日期',
is_valid TINYINT(1) DEFAULT 1 COMMENT '是否有效1有效0無效'
);
然後在數據表中插入一部分數據
INSERT INTO t_student
(stu_no, stu_name, gender, birthday)
VALUES
('20140010001', '學生一', '男', '1988-01-01'),
('20140010002', '學生二', '女', '1989-01-01'),
('20140010003', '學生三', '男', '1988-01-01'),
('20140010004', '學生四', '男', '1988-11-04'),
('20140010005', '學生五', '女', '1987-01-01'),
('20140010006', '學生六', '男', '1988-01-08'),
('20140010007', '學生七', '男', '1986-01-21'),
('20140010008', '學生八', '女', '1990-01-01'),
('20140010009', '學生九', '男', '1989-01-06');
數據準備完成之後接下來我們就要使用MyBatis的動態SQL了,這裏我們主要講如下幾部分內容:if、choose、where、set、foreach。
動態SQL
if
if操作和Java中的if操作比較類似,具體使用如下(下面只列舉了部分代碼,全部的代碼我會放在後面供大家下載):
/**
*
*/
package com.mhy.dao;
import java.util.List;
import com.mhy.model.Student;
/**
* @author [email protected]
* @date 2014年12月29日
*/
public interface StudentDao {
/**
* 根據性別獲取學生列表
* @param gender
* @return
*/
public List<Student> queryStudentList(Student student);
}
此處我們創建了一個接口,在接口中定義了一個方法用以獲取滿足指定條件的學生信息。
接下來我們創建一個SQL映射文件,如下:
<?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.mhy.dao.StudentDao">
<resultMap type="com.mhy.model.Student" id="studentResult">
<id property="id" column="id"/>
<result column="stu_no" property="stuNo" />
<result column="stu_name" property="stuName"/>
<result column="gender" property="gender"/>
<result column="birthday" property="birthday"/>
</resultMap>
<sql id="queryStudent">
SELECT id, stu_no, stu_name, gender, birthday FROM t_student
</sql>
<select id="queryStudentList" parameterType="com.mhy.model.Student" resultMap="studentResult">
<include refid="queryStudent"/>
<if test="gender!=null">
WHERE gender=#{gender}
</if>
ORDER BY id ASC
</select>
</mapper>
如上所示,我們使用了if元素,其所代表的含義如下,如果傳對的參數中gender不爲空則獲取所有的學生列表,否則只查詢指定性別的學生,接下來我們以一個測試例子來看一下:
@Test
public void testIf(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
student.setGender("女");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
執行後輸出的結果如下:如果我們想查詢性別爲女並且學號爲20140010005的學生則可以修改if,變成如下:
<select id="queryStudentList" parameterType="com.mhy.model.Student" resultMap="studentResult">
<include refid="queryStudent"/>
where is_valid=1
<if test="gender!=null">
AND gender=#{gender}
</if>
<if test="stuNo!=null">
AND stu_no=#{stuNo}
</if>
ORDER BY id ASC
</select>
然後修改一下我們的測試用例
@Test
public void testIf(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
student.setGender("女");
student.setStuNo("20140010005");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
此處我們增加了stuNo="201400100005"條件,得到的結果如下:
可以看到就只有一條滿足條件的記錄了。
choose,when,otherwise
choose,when,otherwise很像我們Java中的switch操作,即在篩選條件中選擇某一部分進行執行,而不是所有滿足條件的都執行。比如上面的例子中,如果有性別的話我們就使用性別進行查詢,如果沒有性別我們再退而求其次使用姓名查詢(一般情況下我們是優先查姓名的,這裏只是了爲方便演示結果明顯才使用這種優先順序)。
接下來我們就以gender="男"、stuName="學生九"爲例來測試。
首先修改SQL映射文件
<select id="queryStudentList" parameterType="com.mhy.model.Student" resultMap="studentResult">
<include refid="queryStudent"/>
where is_valid=1
<choose>
<when test="gender!=null">
AND gender=#{gender}
</when>
<when test="stuName!=null">
AND stu_name=#{stuName}
</when>
<otherwise>
<![CDATA[
AND id < 6
]]>
</otherwise>
</choose>
ORDER BY id ASC
</select>
上面配置文件的意思就是,如果存在性別條件則優先使用性別查詢條件,如果性別條件不存在則退而求其次使用姓名查詢條件,如果都沒有的話則查詢id<6的學生,接下來我們測試一下:@Test
public void testChoose(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
student.setGender("女");
student.setStuName("學生九");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
查詢性別爲女的學生列表
如果沒有傳入性別,則使用姓名爲條件:
@Test
public void testChoose(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
// student.setGender("女");
student.setStuName("學生九");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
什麼都不傳入,查詢id<6的學生
@Test
public void testChoose(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
// student.setGender("女");
// student.setStuName("學生九");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
where
在前面的示例中,我們都顯示的在動態SQL條件之前使用了WHERE is_valid=1條件來避免一些問題,如果此時我們要把is_valid也作爲動態查詢條件的話,那麼我們前面的SQL語句就無法滿足要求了,此時就需要我們使用where子元素。where元素知道只有在一個及一個以上if條件滿足時纔會插入WHERE。如果最後的的內容如果是以AND或OR開頭的話where子元素也知道如何去掉它們,接下來我們就以一個例子來演示一下:
<select id="queryStudentList" parameterType="com.mhy.model.Student" resultMap="studentResult">
<include refid="queryStudent"/>
<where>
<if test="gender!=null">
gender=#{gender}
</if>
<if test="stuNo != null">
AND stu_no=#{stuNo}
</if>
</where>
ORDER BY id ASC
</select>
測試傳入性別和學號
@Test
public void testWhere(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
student.setGender("女");
student.setStuNo("20140010005");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
測試只有學號
@Test
public void testWhere(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
// student.setGender("女");
student.setStuNo("20140010005");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
測試不傳性別和學號
@Test
public void testWhere(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
Student student = new Student();
// student.setGender("女");
// student.setStuNo("20140010005");
List<Student> students = dao.queryStudentList(student);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
set
使用了前面的where之後,我們可能會想到如下問題,既然查詢條件能動態化了,那麼我們的修改操作是不是也可以動態化處理呢?比如:針對於某一條數據庫中的記錄,只修改傳入的參數的內容,不傳的字段內容則不做任何修改,此時我們就需要使用到set子元素了。接下來我們以修改學生信息爲例來說明。
首先在StudentDao.java中增加一個方法
/**
* 修改學生信息
* @param student
*/
void updateStudent(Student student);
然後在SQL映射文件中增加如下配置
<update id="updateStudent" parameterType="com.mhy.model.Student">
UPDATE t_student
<set>
<if test="stuName!=null">stu_name=#{stuName},</if>
<if test="gender!=null">gender=#{gender},</if>
<if test="birthday!=null">birthday=#{birthday}</if>
<if test="isValid!=null">is_valid=#{isValid}</if>
</set>
WHERE id=#{id}
</update>
接下來我們寫一個測試,測試中我們修改id=8的學生性別和是否有效信息。
修改之前的學生信息如下:
@Test
public void testSet(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
Student student = new Student();
student.setId(8);
student.setGender("男");
student.setIsValid(0);
StudentDao dao = s.getMapper(StudentDao.class);
dao.updateStudent(student);
s.commit(); //提交事務
} catch(Exception e) {
s.rollback(); //事務回滾
e.printStackTrace();
} finally {
s.close(); //關閉Session
}
}
執行後的學生信息:
執行完之後可以看到該學生只有性別和是否有效兩個字段值被修改了。
foreach
在日常工作中除了上面幾種常量的查詢情況以外,我們還經常使用IN形式的查詢,用來獲取滿足指定集合內元素的記錄集合,比如:查詢一部分姓名的學生、查詢一部分學號的學生。針對於這種情況我們可以在應該層把查詢的條件進行拼接,然後使用${params}傳給SQL映射文件來處理,另一種方式就是使用foreach。foreach用來對一個集合進行遍歷,接下來我們就演示一下foreach的用法。
首先我們在StudentDao.java中增加一個方法:
/**
* 查詢指定學號的學生列表信息
* @param stuNoList 學生學號列表
* @return
*/
List<Student> getStudentByStuNo(List<String> stuNoList);
在SQL映射文件中增加如下配置
<select id="getStudentByStuNo" parameterType="list" resultMap="studentResult">
<include refid="queryStudent"/>
WHERE stu_no in
<foreach collection="list" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</select>
@Test
public void testForeach(){
SqlSessionFactory sf = MyBatisUtil.getSessionFactory();
SqlSession s = sf.openSession();
try {
StudentDao dao = s.getMapper(StudentDao.class);
List<String> list = new ArrayList<String>();
list.add("20140010002");
list.add("20140010004");
list.add("20140010006");
list.add("20140010008");
List<Student> students = dao.getStudentByStuNo(list);
if(!students.isEmpty()){
for (Student stu : students) {
System.out.println(stu);
}
}
} finally {
s.close();
}
}
到此,MyBatis動態SQL常用的子元素就講完了。
在上面的例子中我們都看不到MyBatis所執行的SQL有哪些,如何來查看到MyBatis所執行的SQL語句到底是什麼呢?這在我們開發過程中尤其必要。其實很簡單,只要引入Log4j,然後把日誌級別調整爲DEBUG就能看到MyBatis所執行的SQL了。下面這張圖就是帶有MyBatis執行SQL日誌的截圖
前面爲了使截圖不佔太大的空間,我這把日誌級別調整爲info了,不過關於這些示例的源碼中已經調整日誌級別爲DEBUG了,有興趣的可以下載下來到本地運行。
下載地址:http://download.csdn.net/detail/u010397369/8311583
測試代碼都集中存放在:com.mhy.dao.DynamicSqlTest類下。
鑑於個人能力所限,難免有不足之處,如果有發現問題歡迎大家指正