前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis动态SQL

MyBatis动态SQL

作者头像
用户10358987
发布2024-04-23 19:50:14
970
发布2024-04-23 19:50:14
举报
文章被收录于专栏:javajava

1 什么是MyBatis?

MyBatis 是一个可以自定义 SQL、存储过程和高级映射的持久层框架。

  • Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
  • MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  • 通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

特点

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

2 定义SQL语句

2.1 select标签

属性

  • id :唯一的标识符.
  • parameterType:传给此语句的参数的全路径名或别名 例:com.test.poso.User或user
  • resultType :语句返回值类型或别名。注意:如果是集合,那么这里填写的是集合的泛型,而不是集合本身(resultType与resultMap 不能并用)
代码语言:javascript
复制
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="Object">
    select * from student where id=#{id}
</select>
2.2 insert标签

属性

  • id :唯一的标识符
  • parameterType:传给此语句的参数的全路径名或别名 例:com.test.poso.User
代码语言:javascript
复制
<insert id="insert" parameterType="Object">
    insert into student
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="name != null"> NAME, </if>
    </trim>
    <trim prefix="values(" suffix=")" suffixOverrides=",">
        <if test="name != null"> #{name}, </if>
     </trim>
</insert>
2.3 delete标签

属性

  • id :唯一的标识符
  • parameterType:传给此语句的参数的全路径名或别名 例:com.test.poso.User
代码语言:javascript
复制
<delete id="deleteByPrimaryKey" parameterType="Object">
    delete from student where id=#{id}
</delete>
2.4 update标签

属性

  • id :唯一的标识符
  • parameterType:传给此语句的参数的全路径名或别名 例:com.test.poso.User

3 动态SQL讲解

传统的使用JDBC的方法,相信大家在组合复杂的的SQL语句的时候,需要去拼接,稍不注意哪怕少了个空格,都会导致错误。MyBatis的动态SQL功能正是为了解决这种问题, 其通过 if、choose、when、otherwise、trim、where、set、foreach和bind等9种标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。

MyBatis动态SQL是做什么的?

  • MyBatis动态SQL可以让我们在 Xml 映射文件内,以标签的形式编写动态SQL,完成逻辑 判断和动态拼接SQL的功能。

动态SQL的执行原理

  • 使用OGNL从SQL参数对象中计算表达式的值,根据表达式的值动态拼接SQL,以此来完成动态SQL的功能。
3.1 if标签

if 标签通常用于 WHERE 语句、UPDATE 语句、INSERT 语句中,通过判断参数值来决定是否使用某个查询条件、判断是否更新某一个字段、判断是否插入某个字段的值。

代码语言:javascript
复制
<if test="name != null and name != ''">
    and NAME = #{name}
</if>
3.2 choose标签

有时候我们并不想应用所有的条件,而只是想从多个选项中选择一个。MyBatis提供了 choose 元素,按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when的条件都不满则时,则执行 otherwise 中的 sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default。

if 是与(and)的关系,而 choose 是或(or)的关系。

代码语言:javascript
复制
<select id="getStudentListChoose" parameterType="Student" resultMap="BaseResultMap">
    SELECT * from STUDENT WHERE 1=1
    <where>
        <choose>
            <when test="Name!=null and student!='' ">
                AND name LIKE CONCAT(CONCAT('%', #{student}),'%')
            </when>
            <when test="hobby!= null and hobby!= '' ">
                AND hobby = #{hobby}
            </when>
            <otherwise>
                AND AGE = 15
            </otherwise>
        </choose>
    </where>
</select>
3.3 when标签

参见3.2,choose、when、otherwise这三个标签需要组合在一起使用,类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件。

3.4 otherwise标签

参见3.2。

3.5 trim标签

trim标记是一个格式化的标记,主要用于拼接sql的条件语句(前缀或后缀的添加或忽略),可以完成set或者是where标记的功能。

属性

  • prefix:在trim标签内sql语句加上前缀
  • suffix:在trim标签内sql语句加上后缀
  • prefixOverrides:指定去除多余的前缀内容,如:prefixOverrides=“AND | OR”,去除trim标签内sql语句多余的前缀"and"或者"or"。
  • suffixOverrides:指定去除多余的后缀内容。
3.5.1 在update中
代码语言:javascript
复制
<update id="updateByPrimaryKey" parameterType="Object">
	update student set 
	<trim  suffixOverrides=",">
		<if test="name != null">
		    NAME=#{name},
		</if>
		<if test="hobby != null">
		    HOBBY=#{hobby},
		</if>
	</trim> 
	where id=#{id}
</update>

如果name和hobby的值都不为空的话,会执行如下语句:

代码语言:javascript
复制
update student set NAME='XX',HOBBY='XX' /*,*/ where id='XX'

会忽略最后一个“,” 。

3.5.2 在select中
代码语言:javascript
复制
<select id="selectByNameOrHobby" resultMap="BaseResultMap">
	select * from student 
	<trim prefix="WHERE" prefixOverrides="AND | OR">
		<if test="name != null and name.length()>0"> 
			AND name=#{name}
		</if>
		<if test="hobby != null and hobby.length()>0">
			AND hobby=#{hobby}
		</if>
	</trim>
</select>

如果name和hobby的值都不为空的话,会执行如下语句:

代码语言:javascript
复制
select*fromuserWHERE /*and*/ name = ‘xx’ and hobby= ‘xx’

会为片段添加 “WHERE” 前缀,并忽略第一个 “and” 。

当然,避免出现“WHERE AND”还有其他方法,如下:

代码语言:javascript
复制
<!--将where提取出来,并加上“1=1”的查询条件 -->
select * from student where 1=1
	<trim suffixOverrides=",">
		<if test="name != null and name != ''">
			and NAME = #{name}
		</if>
		<if test="hobby != null and hobby != ''">
			and HOBBY = #{hobby}
		</if>
	</trim>
3.5.3 在insert中
代码语言:javascript
复制
<insert id="insert" parameterType="Object">
    insert into student 
	<trim prefix="(" suffix=")" suffixOverrides=",">
		<if test="name != null">
			NAME,
		</if>
		<if test="hobby != null  ">
			HOBBY,
		</if>
	</trim>
	<trim prefix="values(" suffix=")" suffixOverrides=",">
		<if test="name != null  ">
			#{name},
		</if>
		<if test="hobby != null  ">
			#{hobby},
		</if>
	</trim>
</insert>
3.6 where标签

当 if 标签较多时,这样的组合可能会导致错误。 如下:

代码语言:javascript
复制
<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
    SELECT * from STUDENT WHERE
    <if test="name!=null and name!='' ">
        NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
    </if>
    <if test="hobby!= null and hobby!= '' ">
        AND hobby = #{hobby}
    </if>
</select>

当 name 值为 null 时,查询语句会出现 “WHERE AND” 的情况,解决该情况除了将"WHERE"改为“WHERE 1=1”之外,还可以利用 where标签。这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以 AND 或 OR 开头的,则它会剔除掉。

代码语言:javascript
复制
<select id="getStudentListWhere" parameterType="Object" resultMap="BaseResultMap">
    SELECT * from STUDENT
    <where>
        <if test="name!=null and name!='' ">
            NAME LIKE CONCAT(CONCAT('%', #{name}),'%')
        </if>
        <if test="hobby!= null and hobby!= '' ">
            AND hobby = #{hobby}
        </if>
    </where>
</select>
3.7 set标签

没有使用 if 标签时,如果有一个参数为 null,都会导致错误。当在 update 语句中使用 if 标签时,如果最后的 if 没有执行,则或导致逗号多余错误。使用 set 标签可以将动态的配置 set 关键字,和剔除追加到条件末尾的任何不相关的逗号。

代码语言:javascript
复制
<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT
    SET NAME = #{name},
    MAJOR = #{major},
    HOBBY = #{hobby}
    WHERE ID = #{id};
</update>

<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT SET
    <if test="name!=null and name!='' ">
        NAME = #{name},
    </if>
    <if test="hobby!=null and hobby!='' ">
        MAJOR = #{major},
    </if>
    <if test="hobby!=null and hobby!='' ">
        HOBBY = #{hobby}
    </if>
    WHERE ID = #{id};
</update>

使用 set+if 标签修改后,如果某项为 null 则不进行更新,而是保持数据库原值。

代码语言:javascript
复制
<update id="updateStudent" parameterType="Object">
    UPDATE STUDENT
    <set>
        <if test="name!=null and name!='' ">
            NAME = #{name},
        </if>
        <if test="hobby!=null and hobby!='' ">
            MAJOR = #{major},
        </if>
        <if test="hobby!=null and hobby!='' ">
            HOBBY = #{hobby}
        </if>
    </set>
    WHERE ID = #{id};
</update>
3.8 foreach标签

foreach 标签主要用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作中。

代码语言:javascript
复制
<!-- in查询所有,不分页 -->
<select id="selectIn" resultMap="BaseResultMap">
    select name,hobby from student where id in
    <foreach item="item" index="index" collection="list" open="(" separator="," close=")">
        #{item}
    </foreach>
</select>

属性

  • collection:collection 属性的值有三个分别是 list、array、map三种,分别对应的参数类型为:List、数组、map 集合。
  • item:表示在迭代过程中每一个元素的别名
  • index:表示在迭代过程中每次迭代到的位置(下标)
  • open:前缀
  • close :后缀
  • separator:分隔符,表示迭代时每个元素之间以什么分隔

在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:

  1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
  2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
  3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key

下面分别来看看上述三种情况的示例代码:

3.8.1 单参数List的类型
代码语言:javascript
复制
<select id="dynamicForeachTest" resultType="Blog">  
    select * from t_blog where id in  
    <foreach collection="list" index="index" item="item" open="(" separator="," close=")">  
        #{item}  
    </foreach>  
</select>  

测试代码:

代码语言:javascript
复制
@Test  
public void dynamicForeachTest() {  
    SqlSession session = Util.getSqlSessionFactory().openSession();  
    BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
    List<Integer> ids = new ArrayList<Integer>();  
    ids.add(1);  
    ids.add(3);  
    ids.add(6);  
    List<Blog> blogs = blogMapper.dynamicForeachTest(ids);  
    for (Blog blog : blogs)  
        System.out.println(blog);  
    session.close();  
}

上述collection的值为list,对应的Mapper是这样的:

代码语言:javascript
复制
public List<Blog> dynamicForeachTest(List<Integer> ids);
3.8.2 单参数array数组的类型
代码语言:javascript
复制
<select id="dynamicForeach2Test" resultType="Blog">  
    select * from t_blog where id in  
    <foreach collection="array" index="index" item="item" open="(" separator="," close=")">  
        #{item}  
    </foreach>  
</select>  

上述collection为array,对应的Mapper代码:

代码语言:javascript
复制
public List<Blog> dynamicForeach2Test(int[] ids);

测试代码:

代码语言:javascript
复制
@Test  
public void dynamicForeach2Test() {  
    SqlSession session = Util.getSqlSessionFactory().openSession();  
    BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
    int[] ids = new int[] {1,3,6,9};  
    List<Blog> blogs = blogMapper.dynamicForeach2Test(ids);  
    for (Blog blog : blogs)  
        System.out.println(blog);  
    session.close();  
}  
3.8.3 自己把参数封装成Map的类型
代码语言:javascript
复制
<select id="dynamicForeach3Test" resultType="Blog">  
    select * from t_blog where title like "%"#"%" and id in  
    <foreach collection="ids" index="index" item="item" open="(" separator="," close=")">  
        #{item}  
    </foreach>  
</select>  

上述collection的值为ids,是传入的参数Map的key,对应的Mapper代码:

代码语言:javascript
复制
public List<Blog> dynamicForeach3Test(Map<String, Object> params); 

测试代码:

代码语言:javascript
复制
@Test  
public void dynamicForeach3Test() {  
    SqlSession session = Util.getSqlSessionFactory().openSession();  
    BlogMapper blogMapper = session.getMapper(BlogMapper.class);  
    final List<Integer> ids = new ArrayList<Integer>();  
    ids.add(1);  
    ids.add(2);  
    ids.add(3);  
    ids.add(6);  
    ids.add(7);  
    ids.add(9);  
    Map<String, Object> params = new HashMap<String, Object>();  
    params.put("ids", ids);  
    params.put("title", "中国");  
    List<Blog> blogs = blogMapper.dynamicForeach3Test(params);  
    for (Blog blog : blogs)  
        System.out.println(blog);  
    session.close();  
}
3.9 bind标签

标签可以使用ONGL(Object-Graph Navigation Language, 对象图形化导航语言)表达式创建一个变量并将其绑定到上下文中。如以下代码:

代码语言:javascript
复制
<if test="userName != null and userName != ''">
	and user_name like concat('%', #{userName} ,'%') 
</if>

由于不同的数据库,concat函数的用法不一样,Mysql的有3个参数,而oracle的只有2个。这就造成了这条SQL在不同环境中运行可能会出错。为了避免这种错误,可以用标签,如下:

代码语言:javascript
复制
<if test="userName != null and userName != ''">
	<bind name="nameLike" value="'%' + userName + '%'"/>
	and user_name like #{nameLike} 
</if>

标签的两个属性都是必选的,name为绑定到上下文的变量名,value为OGNL表达式。创建了标签的变量后,就可以在下面直接使用了。如下例子,先写一个静态方法,然后在Mapper.xml中调用该方法设置默认值:

代码语言:javascript
复制
package ex.mybatis.rbac.mapper;
public class BindTest {
	public static String setName() {
		return "test";
	}
}

Mapper.xml

代码语言:javascript
复制
<!-- 动态根据输入的用户名和id查询用户信息<choose>标签用法 -->
<select id="selectByIdOrName" resultMap="BaseResultMap">
	<!-- 使用<bind>标签调用的方法拿出来的默认值 -->
  	<bind name="name" value="@ex.mybatis.rbac.mapper.BindTest@setName()"/>
  	
    select id, user_name, user_password, user_email, create_time, user_info, head_img
    from sys_user
    <!-- 加上1=1是为了防止2个查询条件都为空的时候,SQL不会报错。学了where标签后可以去掉 -->
    where 1=1 
    <choose>
    	<when test="id != null">
    		and id = #{id}
    	</when>
    	<when test="userName != null and userName != ''">
    		and user_name like concat('%', #{userName} ,'%') 
    	</when>
    	<otherwise>
    		<!-- 当所有条件都空时,使用<bind>标签调用的方法拿出来的默认值 -->
    		and user_name=#{name}
    	</otherwise>
    </choose>
</select>

使用标签打印入参信息,如下:

先定义静态方法print

代码语言:javascript
复制
package ex.mybatis.rbac.mapper;
public class BindTest {
	public static void print(Object param) {
		System.out.println(param);
	}
}

在Mapper.xml中使用标签,调用该方法

代码语言:javascript
复制
<!-- <foreach>标签实现更新,参数为map -->
<update id="updateByMap">
  	<bind name="print" value="@ex.mybatis.rbac.mapper.BindTest@print(_parameter)"/>
	update sys_user 
	set 
	<foreach collection="_parameter" item="val" index="key" separator=",">
		${key} = #{val}
	</foreach>
	where id = #{id}
</update>

updateByMap参数打印结果

代码语言:javascript
复制
{user_email=test2@mybatis.ex, user_password=1111333, id=1001}

4 #{}和${}的区别是什么?

  • #{}是预编译处理,${}是字符串替换。
  • Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值。
  • Mybatis 在处理 {}时,就是把{}替换成变量的值。
  • 使用#{}可以有效的防止SQL注入,提高系统安全性。
本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-04-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 什么是MyBatis?
  • 2 定义SQL语句
    • 2.1 select标签
      • 2.2 insert标签
        • 2.3 delete标签
          • 2.4 update标签
          • 3 动态SQL讲解
            • 3.1 if标签
              • 3.2 choose标签
                • 3.3 when标签
                  • 3.4 otherwise标签
                    • 3.5 trim标签
                      • 3.5.1 在update中
                      • 3.5.2 在select中
                      • 3.5.3 在insert中
                    • 3.6 where标签
                      • 3.7 set标签
                        • 3.8 foreach标签
                          • 3.8.1 单参数List的类型
                          • 3.8.2 单参数array数组的类型
                          • 3.8.3 自己把参数封装成Map的类型
                        • 3.9 bind标签
                        • 4 #{}和${}的区别是什么?
                        相关产品与服务
                        数据库
                        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                        http://www.vxiaotou.com