掌握Mybatis动态映射,我可是下了功夫的
关注“Java后端技术全栈”
动态 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文件:
- <mapper namespace="com.tian.mybatis.mapper.UserMapper">
- <resultMap id="User" type="com.tian.mybatis.entity.User">
- <id column="id" property="id"/>
- <result column="name" property="userName"/>
- </resultMap>
- <select id="selectUserById" resultMap="User">
- select * from m_user
- <where>
- <if test="id != null">
- id = #{id}
- </if>
- <if test="name != null and name != ''">
- and `name` = #{name}
- </if>
- </where>
- </select>
- </mapper>
UserMapper.java内容:
- 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。
http://localhost:9002/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实现多条件查询
对前面的代码进行调整
- <select id="selectUsersByIds" resultMap="User">
- select * from m_user
- <trim prefix="where" prefixOverrides="and | or">
- <if test="idList != null">
- id in
- <foreach collection="idList" item="id" open="(" close=")" separator=",">
- #{id}
- </foreach>
- </if>
- <if test="gender != null and gender != 0">
- AND gender = #{gender}
- </if>
- </trim>
- </select>
UserMapper.java增加
List<User> selectUsersByIds(@Param("idList") List<Integer> idList, @Param("gender") Integer gender);
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);
- }
项目跑起来,访问
http://localhost:9002/users
输出:
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新增
- <update id="updateAuthorIfNecessary">
- update m_user
- <set>
- <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>
- </set>
- where id=#{id}
- </update>
UserMapper.java新增
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 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
换一种方式
- <trim prefix="SET" suffixOverrides=",">
- ...
- </trim>
我们把上面的xml中diam进行调整:
- <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新增方法:
- <select id="selectUsersByName" resultMap="User">
- select * from m_user where age = 19
- <choose>
- <when test="userName != null and userName != ''">
- and `name` = #{userName}
- </when>
- <otherwise>
- AND gender = 1
- </otherwise>
- </choose>
- </select>
controller新增方法:
- @GetMapping("/user/name")
- public List<User> selectUsersByName() {
- return userService.selectUsersByName("tian");
- }
返回:
SQL输出:
正确的输出。如果我们userName没有是null呢?
输出和上面正常,在看看SQL输出:
因为我们的userName的条件不满足的情况下,直接执行了gender。
上面<otherwise>
就类似于相当于我们java语法中switch中的default,当前面条件不满足的时候,执行default模块一样。
Bind
这种方式使用的不是很多,但是也是有用的。bind
元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如:
- <select id="selectUserByName" resultType="User">
- <bind name="pattern" value="'%' + userName + '%'" />
- select * from m_user
- WHERE `name` LIKE #{pattern}
- </select>
还有就是script,这个就没有必要在演示了,在工作中基本上用不上。它就是把SQL卸载Java代码中。比如
- @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
欢迎关注公众号:Java后端技术全栈