Mapper配置动态SQL:if,choose和foreach元素

本文介绍了MyBatis中用于动态SQL配置的if、choose和foreach元素,以及<where>标签的使用。通过示例展示了如何在Mapper配置文件中利用这些元素进行条件判断和集合遍历,以减少代码量和提高SQL语句的灵活性。内容包括:SQL语句拼接的避免方法,if标签的基本用法和条件判断,<where>标签的特殊处理,choose标签的多选一功能,以及foreach标签在遍历集合中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SQL语句拼接

在前面的配置中,无论是基本的增删查改,还是关联嵌套,集合嵌套查询,使用的SQL语句都是很基础的,表关系之间的数据操作。翻回自己大一时写的代码,从一开始的SQL语句直接拼接,如:

String sql = "Update user Set username = " + username + ", gender = " + gender + " Where id = " + id;

这样拼接SQL语句,难免会因为粗心而出现漏逗号等不必要的错误,到后来使用string.Format格式化来拼接SQL语句,语句可读性好了很多:

string.Format("Update FoodItem Set FoodName='{0}',FoodStyle='{1}',Price='{2}' where FoodID='{3}'", FoodName, FoodStyle, Price, FoodId);

传统的JDBC除了拼接语句时有那么一点点麻烦外,在根据不同条件动态配置SQL语句方面,一样不太方便。假如我们想查询一张用户表中,性别为“男”,且姓名为“张三”的用户信息,我们很容易写出这样的查询语句:

SELECT * FROM user WHERE gender = '男' AND username = '张三';

这时如果我们想要查询性别为“男”,且城市字段为“广州”的用户信息,我们就不得不重新写一条查询语句:

SELECT * FROM user WHERE gender = '男' AND city = '广州市';

显然这样的代码量会变得很大,如果你不确定一些查询需求的参数,那么就可能要写很多的查询语句来应对不同的查询。

     在配置Mapper(SQL映射配置文件)中有较好的解决办法来减少代码量(例如其映射语句的配置据说最多能减少95%的代码量)。因为MyBatis提供了动态配置SQL语句的一些元素,配置过程中依旧踩了很多坑- -、,但是总算是成功配置并完美运行了!这篇日志总结下自己对if,choose以及foreach三个元素的配置,还有<where>标签,都十分的强大!

 

if 标签 – 选择查找文本

基本用法

看到“if”元素,大家肯定都猜到它的作用是用来做判断了,把“if”标签放到最前面来讲,因为它是最基本,也是程序员们最熟悉的元素之一。没错,它的作用就是条件判断,选择不同的SQL语句执行,来看各例子:

用到的JavaBean,下面的其他示例都是用这个实体类:

public class User implements Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private int id;
	private String username;
	private String password;
	private String gender;
	private String email;
	private String province;
	private String city;
	private Date birthday;
	private int age;
	
	public User() {
		System.out.println("进入目标类无参数构造方法....");
	}
	// 构造方法初始化
	public User(String username, String password, String gender, String email, String province,
			String city, Date birthday) {
		super();
		System.out.println("进入目标类有参数构造方法....");
		this.username = username;
		this.password = password;
		this.gender = gender;
		this.email = email;
		this.province = province;
		this.city = city;
		this.birthday = birthday;
	}
    // 省略get()和set()方法
}

SQL映射配置文件:

<select id="UserActiveSearch_if" parameterType="User" resultType="User">
		SELECT 
			U.username,
			U.gender,
			U.email,
			U.city
		FROM User U WHERE gender = '男'
		<if test="username != null">
			AND U.username like #{username}
		</if>
	</select>

上面的SQL语句配置中,传入参数和映射类型都是User实体类,可以看到,我们要查询表中性别为“男”的用户的信息(用户名,性别,电子邮件和城市),且当传入的参数中的username熟悉不为空时,我们把这个username也作为查询条件之一,也就是我们变成查询数据表中,性别为“男”且姓名为传入的“username”的用户信息。如果传入参数User类实例对象中username为null,则不把if标签中的条件作为查询条件,可以写个简单的测试用例看看运行的结果:

@Test
	// if表达式
	public void TestActiveIf() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		StringBuffer result = new StringBuffer();
		User newUser = new User();
		newUser.setUsername("张三");
		List<User> resultList = sqlSession.selectList("UserActiveSearch_if", newUser);
		
		for(int i=0; i<resultList.size(); i++) {
			User user = resultList.get(i);
			result.append("用户名: " + user.getUsername() + "\r\n");
			result.append("性别: " + user.getGender() + "\r\n");
			result.append("电子邮箱: " + user.getEmail() + "\r\n");
			result.append("城市: " + user.getCity() + "\r\n");
			
			System.out.println(result.toString());
			result.setLength(0);
		}

		sqlSession.close();
	}

测试用例中,我们实例化一个User实例newUser,并给它添加username属性为“张三”,接着实例对象作为参数传入查询。按照SQL映射配置文件中的select语句配置,我们可以查询得到用户“张三”的部分信息,如电子邮箱,城市等,结果也是这样的:

成功查询得到用户“张三”的部分信息。

     如上面所说,那如果我们传入的User实例对象中,username为空,那么if标签里的条件便不成立,查询出来的会是什么样的结果,来看看:

@Test
	// if表达式
	public void TestActiveIf() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		StringBuffer result = new StringBuffer();
		User newUser = new User();
//		newUser.setUsername("张三");
		List<User> resultList = sqlSession.selectList("UserActiveSearch_if", newUser);
		
		for(int i=0; i<resultList.size(); i++) {
			User user = resultList.get(i);
			result.append("用户名: " + user.getUsername() + "\r\n");
			result.append("性别: " + user.getGender() + "\r\n");
			result.append("电子邮箱: " + user.getEmail() + "\r\n");
			result.append("城市: " + user.getCity() + "\r\n");
			
			System.out.println(result.toString());
			result.setLength(0);
		}

		sqlSession.close();
	}

在上面的测试用例中,我们把设置username为“张三”这一句给注释掉,也就是说传入的User实例对象中,所有属性的值都为null,再来看看运行结果:

从查询结果可以看到,由于没有进入if标签中的条件查询,所以整个查询语句帮我们查询出了数据表中所有的gender为“男”的信息。

     当然,我们也可以在SQL语句配置一个select标签对中配置多个if条件判断,如果哪一个if条件不满足,则不会把它作为查询条件。

使用<where>标签对

上面我们说道,可以在一个select标签对中,也就是一条select语句中,配置多个if条件判断,例如这样:

<select id="UserActiveSearch_if2" parameterType="User" resultType="User">
		SELECT 
			U.username,
			U.gender,
			U.email,
			U.city
		FROM User U WHERE
		<if test="city != null">
			U.city = #{city}
		</if>
		<if test="username != null">
			AND U.username = #{username}
		</if>
	</select>

这样的配置,即使username为空导致其中一个if条件不成立,但另一个if条件成立,如传入的city属性为“广州市”,查询语句也可以正确查询出所有city为“广州市”的用户:

这没问题。接下来我们看一个特殊情况,假设我们在这条SQL配置语句查询中,传入一个里面所有属性都是null的User类实例对象,会出现什么样的情况?

     结果是程序会出错!为什么,因为如果我们传入的对象里所有属性都为null,意味着select标签中配置的查询语句,所有的if条件都不会被作为查询条件,那么整条SQL语句就变为:

SELECT U.username, U.gender, U.email, U.city FROM User U WHERE;

没错就是这样,这样的SQL语句肯定是查询错误的。还有另一个不易被发现的隐含危险错误是,假如我们第一个if条件“city != null“不成立,但第二个if条件成立,查询同样会出错,为什么?我们来看看如果第一个if不成立但第二个if成立,整条SQL语句会变成怎样:

SELECT U.username, U.gender, U.email, U.city FROM User U WHERE AND U.username = username;

这样的SQL语句,“WHERE AND”连在了一起,一样是错误的,所以会导致查询失败。那怎么办?解决的方法是使用<where>标签对,来包裹住我们需要用到的if条件判断:

<select id="UserActiveSearch_if" parameterType="User" resultType="User">
		SELECT 
			U.username,
			U.gender,
			U.email,
			U.city
		FROM User U 
		<where>
			<if test="city != null">
				U.city = #{city}
			</if>
			<if test="username != null">
				AND U.username like #{username}
			</if>
		</where>
	</select>

包裹后,只有当<where>标签对中,至少有一个子元素if标签对判断成立,才会向SQL语句中插入“where”子句,并且自动去掉if标签对中开头的“AND”和“OR”(否则拼接出来的SQL语句又是错误的)。来看看使用<where>标签对后的测试结果,首先是传入参数只给了用户名,看<where>是否帮我们忽略掉了子句前面的“AND”:

结果如我们所料,接着到测试传入一个个属性为null的对象,<where>标签对中没有一个if条件判断成立:

由于没有一个if标签成立,“where”没有被拼接到SQL语句中,整条SQL语句变为:

SELECT U.username, U.gender, U.email, U.city FROM User U

也就是帮我们查询出了数据表中所有的使用户数据。

 

choose标签 – 多选一

上面的<where>,<if>标签对,配置简单,功能强大,可以完成多个条件的查询,只要是满足条件的<if>标签对,都会被拼接到SQL语句中。接下来这个标签对:choose,when,otherwise。作用是在给出的多个when标签条件语句中,选择其中一个符合的条件作为查询条件,拼接到SQL语句中。它和switch语句类似,choose对应switch,when对应case,otherwise则对应default。来看看它的配置方式:

<select id="UserActiveSearch_choose" parameterType="User" resultMap="UserActiveResultMap">
		SELECT 
			U.username as user_name,
			U.gender as user_gender,
			U.email as user_email,
			U.city as user_city
		FROM User U 
		<where>
			<choose>
				<when test="username != null and username != ''">
					username = #{username}
				</when>
				<when test="email != null and email != ''">
					AND email = #{email}
				</when>
				<otherwise>
					AND id = 6
				</otherwise>
			</choose>
		</where>
	</select>

每一个when标签给出一个条件判断,按照这个例子,如果username不为空,则选择它为查询条件,choose标签对里的when标签,在成功匹配了一个条件后,就会退出choose标签。如果两个when标签中的查询条件不符合,则选择otherwise中的条件作为查询条件。可以写个简单的测试用例看看它的执行:

第一个测试中,给出了username,这没问题,choose会选择它作为查询条件拼接到SQL语句中。接下来看第二个,假如我们给出username和email,且这个email属性不属于该username的用户,看看choose标签会怎么帮我们查询:

可以看到,它放弃了第二个条件查询,在选择了第一个when标签条件username后,下面的email条件被放弃了,正如上面所说,choose是在多个条件中选择其中一个执行。

     第三个测试是传入一个个属性为空的实例对象,按照配置,两个when标签中的条件都不符合,那么最后选择otherwise标签中的条件执行,即查询id=6的用户信息:

结果也正是这样的。

 

foreach标签 – 遍历集合

在C#和Java都中接触过foreach循环语句,对比于普通for循环的优点是,普通for循环必须确定循环执行的次数,而foreach循环则不需要,适合于循环次数不确定的循环遍历。在MyBatis中也有foreach标签,它的功能是便利一个集合,假设我们有这样一个例子,需要查询数据表中,id为1,3和5的用户信息,SQL语句可以这么写:

SELECT * FROM User WHERE id in(1,3,5);

或者

SELECT * FROM User WHERE id=1 or id=3 or id=5;

这么做是可以,不过有一点小缺点是,我们把这些id“集合”硬编码进了SQL语句中,假如我们下次想查询另一组id的用户信息,则不得不改动SQL语句,且如果id较多,SQL语句会变得非常的长。解决的办法就是使用foreach标签,把一组id放到集合中,再把集合作为传入参数,用foreach标签来遍历这个集合,把其中的元素一个一个拿出来作为查询条件,即可大大减少代码量。来看看foreach标签对的配置:

<!-- 拼接  WHERE (id=1 or id=3 or id=5) -->
	<select id="UserActive_foreach1" resultMap="UserActiveResultMap">
		SELECT
			U.id as user_id,
			U.username as user_name,
			U.gender as user_gender,
			U.email as user_email,
			U.city as user_city
		FROM User U 
		<where>
			<foreach collection="list" item="user_id" open="(" separator="or" close=")">
				<!-- 每次遍历时拼接上去的字符串 -->
				id=#{user_id}
			</foreach>
		</where>
	</select>

这个示例中,我们要用foreach标签配置以下的SQL语句:

SELECT * FROM User WHERE id in(1,3,5);

在foreach标签对中,指定集合的类型collection=“list”,item是指定每次遍历得到的集合项的对象名。接下来三个属性很重要!open指定拼接前缀,这里等于“(”;separator指定两个遍历对象之间的拼接字符串,例如id1和3之间应该拼接一个都好“,”;而close指定结束遍历时要拼接的字符串。最后在标签里指定每次遍历要做的事情,是id=遍历对象,即以此遍历id=1、id=3,id=5。

     SQL映射配置文件配置好后,就可以写个简单的测试用例测试下结果了:

@Test
	// foreach表达式
	public void TestActiveForeach() throws IOException {
		SqlSession sqlSession = dataConn.getSqlSession();
		StringBuffer result = new StringBuffer();
		
		List<Integer> idNum = new ArrayList<Integer>();
		for(int j=1; j<6; j+=2) {
			idNum.add(j);
		}
		
		List<User> resultList = sqlSession.selectList("UserActive_foreach1", idNum);
		
		for(int i=0; i<resultList.size(); i++) {
			User user = resultList.get(i);
			result.append("用户id: " + user.getId() + "\r\n");
			result.append("用户名: " + user.getUsername() + "\r\n");
			result.append("性别: " + user.getGender() + "\r\n");
			result.append("电子邮箱: " + user.getEmail() + "\r\n");
			result.append("城市: " + user.getCity() + "\r\n");
			
			System.out.println(result.toString());
			result.setLength(0);
		}

		sqlSession.close();
	}

可以看到,我们初始化了一个Integer泛型集合idNum,然后作为参数传入查询中。

测试结果如我们所愿。掌握foreach标签对,关键是配置好open,separator和close三个属性,我们可以用它配置上面的另一条遍历语句:

SELECT * FROM User WHERE id=1 or id=3 or id=5;

<!-- 拼接  WHERE id in (1,3,5) -->
	<select id="UserActive_foreach2" resultMap="UserActiveResultMap">
		SELECT
			U.id as user_id,
			U.username as user_name,
			U.gender as user_gender,
			U.email as user_email,
			U.city as user_city
		FROM User U 
		<where>
			<foreach collection="list" item="user_id" open="id in (" separator="," close=")">
				<!-- 每次遍历时拼接上去的字符串 -->
				#{user_id}
			</foreach>
		</where>
	</select>

很简单,只需把遍历对象之间的拼接字符串改为“or”即可。

 

完整代码已上传GitHub:

https://github.com/justinzengtm/SSM-Framework

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值