動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
使用動態 SQL 並非一件易事,但借助可用於任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
如果你之前用過 JSTL 或任何基於類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基於 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
if:利用if實現簡單的條件選擇。
choose(when,otherwise):相當於java中的switch語句,通常與when和otherwise搭配。
set:解決動態更新語句。
trim:靈活的去除多余的關鍵字。
foreach:迭代一個集合,通常用於in條件。
實際工作中很多時候,這幾個標簽都是組合着使用。
今天的演示使用的是Spring-Boot+Mybatis進行演示,對於Spring-Boot整合Mybatis推薦:
if+where實現多條件查詢
創建一種昂數據庫表:
CREATE TABLE m_user
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(255) DEFAULT NULL,
age
int(11) DEFAULT NULL,
gender
int(11) DEFAULT NULL COMMENT '0:女生 1:男生',
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
初始化幾條數據:
先來看UserMapper.xml文件:
import com.tian.mybatis.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper {
User selectUserById(@Param("name") String userName, @Param("id") Integer id);
}
UserService.java內容:
public interface UserService {
User selectUserById(String userName, Integer id);
}
UserServiceImpl.java內容:
import com.tian.mybatis.entity.User;
import com.tian.mybatis.mapper.UserMapper;
import com.tian.mybatis.service.UserService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Override
public User selectUserById(String userName, Integer id) {
return userMapper.selectUserById(userName, id);
}
}
UserController.java內容:
import com.tian.mybatis.entity.User;
import com.tian.mybatis.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class UserController {
@Resource
private UserService userService;
@GetMapping("/test")
public User selectUserById() {
return userService.selectUserById("tian", 1);
}
}
Application.java內容:
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.tian.mybatis.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
把項目啟動起來,然后進行訪問/test。
返回:
上面的這個案例也是我們工作中的代碼案例,我們工作但部分都使用這種方式。
下面的所有演示都是基於上面這些代碼進行調整而成的。
回到正題。
上面的案例中使用了where+if。案例中貌似有個問題:
如果id=null,豈不是多了個and嗎?
我們修改controller中的代碼
@GetMapping("/test")
public User selectUserById() {
return userService.selectUserById("tian", null);
}
為了能讓sql輸出,我們在配置文件添加了一個配置項:
logging:
level:
com:
tian:
mybatis:
mapper: debug
再次執行,輸出和前面是一樣的。控制台輸出的sql中並沒有and。這就是所謂的動態映射的強大功能之一。
如果我們不使用動態映射標簽,在處理or或者and的時候很有可能出問題。
where元素可以智能的處理and 和 or 的多余問題, 不需擔心多余關鍵字導致語法錯誤。
if元素的test用於判斷表達式是否符合,符合則繼續拼接SQL語句。
建議
建議使用這種動態標簽,不要使用原生態,因為有時候總有意想不到的判斷導致多了一個and或者or,於是就出現bug,嚴重的可能導致線上某個功能不可能用。
if+trim+foreach實現多條件查詢
對前面的代碼進行調整
List
controller新增方法:
@GetMapping("/users")
public List<User> selectUsersByIds() {
List<Integer> idList = new ArrayList<>();
idList.add(1);
idList.add(2);
idList.add(3);
idList.add(4);
idList.add(5);
return userService.selectUsersByIds(idList, null);
}
項目跑起來,訪問
輸出:
sql輸出:
對上面相關屬性進行說明
trim的屬性
prefix:前綴: 作用是通過自動識別是否有返回值后,在trim包含的內容上加上前綴,如上述示例的where。
suffix:后綴: 作用是在trim包含的內容上加上后綴。
prefixOverrides::對於trim包含內容的首部進行指定內容,(如上述示例的 and | or) 的忽略(去余);
suffixOverrides::對於trim包含內容的首位部進行指定內容的忽略。
foreach的屬性
item:表示集合中每一個元素進行迭代時的別名。
index::指定一個名稱,表示在迭代的過程中,每次迭代到的位置。
open:表示該語句以什么開始(既然是in條件語句,必然是 ' ( ' 開始)
separator::表示每次進行迭代的時候以什么符號作為分隔符(既然是in條件語句,必然是 ' , ' 分隔)
close::表示該語句以什么結束(既然是in條件語句,必然是 ' ) ' 結束)
collection:最關鍵,並且最容易出錯的屬性。需注意,該屬性必須指定,不同情況下,該屬性值是不同的,
主要有三種情況:
@Param是Mybatis中的注解,寫的時候別引用錯了,@Param("name"),這里的name就是我們在Mapper.xml中使用的名稱。
在項目中我見過很多人這么干,就是當where語句后面不太確定能有條件出現時,使用
slect ...from...where 1=1
看看你的代碼是否也有?
set
set元素可以用於動態包含需要更新的列,忽略其它不更新的列。
UserMapper.xml新增
int updateAuthorIfNecessary(User user);
controller新增
@PostMapping("/updateUser")
public String update() {
User user = new User();
user.setAge(18);
user.setUserName("田哥");
user.setId(1);
return userService.updateAuthorIfNecessary(user) == 1 ? "success" : "fail";
}
重啟項目,訪問
http://localhost:9002/updateUser
輸出:success
數據庫表中數據已經修改成功:
SQL輸出
這個例子中,set 元素會動態地在行首插入 SET 關鍵字,並會刪掉額外的逗號(這些逗號是在使用條件語句給列賦值時引入的)。
換一種方式
<update id="updateAuthorIfNecessary">
update m_user
<trim prefix="SET" suffixOverrides=",">
<if test="userName != null and userName != ''">
`name` = #{userName},
</if>
<if test="gender != null and gender != 0">
gender = #{gender},
</if>
<if test="age != null and age != 0">
age = #{age},
</if>
</trim>
where id=#{id}
</update>
controller修改:
@PostMapping("/updateUser")
public String update() {
User user = new User();
user.setAge(19);
user.setUserName("tian");
user.setId(1);
return userService.updateAuthorIfNecessary(user) == 1 ? "success" : "fail";
}
最后看看SQL輸出:
自動給我們加上了SET關鍵字。並且數據庫修改成功。
choose
相當於Java中的switch語句,通常與when和otherwise搭配。
有時候,我們不想使用所有的條件,而只是想從多個條件中選擇一個使用。針對這種情況,MyBatis 提供了 choose 元素,它有點像 Java 中的 switch 語句。
下面我們繼續使用上面的案例代碼進行演示。
UserMapper.xml新增方法:
controller新增方法:@GetMapping("/user/name")
public List
return userService.selectUsersByName("tian");
}
返回:
SQL輸出:
正確的輸出。如果我們userName沒有是null呢?
輸出和上面正常,在看看SQL輸出:
因為我們的userName的條件不滿足的情況下,直接執行了gender。
上面
Bind
這種方式使用的不是很多,但是也是有用的。bind 元素允許你在 OGNL 表達式以外創建一個變量,並將其綁定到當前的上下文。比如:
@Update({"<script>",
"update m_user",
" <set>",
" <if test='username != null'>`name`=#{username},</if>",
" <if test='gender != null and gender != 0'>gender=#{gender},</if>",
" </set>",
"where id=#{id}",
"</script>"})
void updateUserValues(User user);
總結
文章中部分知識為了演示,可能有些代碼不是很規范,尤其是sql部分,我們在開發中,針對使用Mybatis開發,我個人總結了幾個點:
表中是否已經有索引,有索引的時候我們的SQL中是否有用上。
返回字段盡量不要寫星號*,建議寫成需要的字段。
關鍵字建議都寫成大寫,更好的區別非關鍵字。
遇到表中字段和數據庫關鍵一樣的時候,記得單引號。
使用@Param注解注意一定要使用Mybatis中的注解。
使用不管是一個參數還是多個參數時,使用注解@Param指定名稱,方便日后需要再次添加字段。
強烈建議使用動態標簽,避免出現多出and或者or關鍵字的SQL錯誤,同時也不用再寫where 1=1