分享知识, 不咕自己.
前言
在我们实际开发中. 相比于数据的存储, 数据提取操作会更为频繁. 且会随着数据的体量变大和业务需求变更
而变得越来越复杂. 很多时候简单的查询操作已经不能满足业务的需求. 所以我们需要sql变得更加"聪明"
. 本章即将要学习的, 在Mapper.xml(映射文件)
中的动态sql
相关标签解决了我们这样的需求.
一、 准备
在开始学习之前. 我们需要对之前所创建的测试数据库mybatis_test库中的user表
进行修改. 增加两个字段gender(性别)和age(年龄)
. gender字段为int类型, 我们约定如果gender值为1我们把他视为男生, 如果值为2我们把她视为女生
. DDL代码如下:
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`gender` int(11) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
复制代码
修改完之后我们在user中插入两条数据作为测试数据
并且我们还需要在user表的映射实体类User.clss中添加gender和age属性并添加get/set方法
. 代码如下:
public class User {
private Integer id;
private String username;
private Integer gender;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
复制代码
完成准备之后我们就可以开始编码.
二、 动态Sql之if标签
动态sql中的if标签, 常用于我们常说的多条件查询中. 在实际开发中, 某个sql查询中可能会有不确定个数的查询条件
. 这时我们就可以使用if标签. 与我们java代码中的if代码同理. 它可以给定一个结果为boolean类型的表达式来判断是否将该条件拼接到查询sql中
. 接下来我们会对getListByCondition方法进行改进
. 代码如下:
<!-- 条件查询 -->
<select id="getListByCondition" resultType="User" parameterType="User">
select * from user
<where>
<if test="id != null"> id = #{id} </if>
<if test="username != null and username != ''"> and username = #{username} </if>
<if test="gender != null"> and gender = #{gender} </if>
<if test="age != null"> and age < #{age} </if>
</where>
</select>
复制代码
上面代码的意思是, 如果if标签中有一条或一条以上满足条件, 将会在sql中追加关键字where. if标签条件中的属性有值的情况下才会追加到sql语句中, 因为username使String类型所以必须满足两个条件
. 编写完代码之后, 先不着急测试. 我们先思考一个问题. 如果在执行上面sql的时候, 入参实体中只有username有值, 我们的方法执行会发生报错
, 最终生成的sql会变成select * from user where and username = #{username}
. 显然这样的语法是不正确的.
我们如何规避这样的错误呢. 其实很简单, 我们只需要一点点改动. 代码如下:
<!-- 条件查询 -->
<select id="getListByCondition" resultType="User" parameterType="User">
select * from user
<where>
1 = 1
<if test="id != null"> and id = #{id} </if>
<if test="username != null and username != ''"> and username = #{username} </if>
<if test="gender != null"> and gender = #{gender} </if>
<if test="age != null"> and age < #{age} </if>
</where>
</select>
复制代码
我们在where标签中加如了1 = 1
. 这样即使if标签中一个没有满足条件也不会有语法错误的情况.
注意: 如果在查询中需要范围查询, 那么不推荐直接使用 >、>=、<、<=这类符号. 它会导致XMl文件的解析错误. 所以我们应该改写成对应的转义符并以分号结尾
原符号 | > | < | >= | <= | & | ‘ | “ |
---|---|---|---|---|---|---|---|
转义符 | > | < | >= | <= | & | ' | " |
接下来我们来编写测试类. 假定我们要查找性别为男生并且年龄小于18
. 测试类代码为:
@Test
public void testGetListByCondition() throws IOException {
// 获取配置文件 转换为输入流
InputStream resourceAsStream =
Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory 后面细说
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取sqlSessin
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过JDK动态代理获取接口代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
// 构建入参对象
User user = new User();
user.setGender(1);
user.setAge(18);
// 执行方法
List<User> users = userDao.getListByCondition(user);
for (User user1 : users) {
System.out.println("id = " + user1.getId() +
", username = " + user1.getUsername() +
", gender = " + user1.getGender() +
", age = " +user1.getAge());
}
// 关闭sqlSession
sqlSession.close();
}
复制代码
输出结果:
其他查询情况作为练习, 大家可以自行模拟.
三、 动态查询之foreach标签
foreach标签的使用场景常见于基于批量查询和批量新增、修改、删除
情况中.
foreach可以作为where标签的子标签可以单独使用. foreach标签有多个属性. 属性含义分别为:
- collection: 方法的入参对象类型. List类型默认为list, 数组类型默认为array.
- index:在list和数组中,index是元素的序号,在map中,index是元素的key,参数不是必须.
- open: sql拼接的开始符号, 与close结合使用.
- close: sql拼接的结束符号, 与open结合使用.
- separator: 元素之间的分隔符, separator=”,”.
了解了上面概念后我们开始编写代码. 首先在UserDao接口中声明两个方法:
// 批量查询
List<User> selectByIds(int[] ids);
// 批量新增
int batchInsert(List<User> users);
复制代码
然后再UserMapper.xml文件中编写sql如下:
<!-- 批量查询 -->
<select id="selectByIds" resultType="user" parameterType="_int[]">
select * from user
<where>
<foreach collection="array" open="id in (" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</select>
<!-- 批量新增 -->
<insert id="batchInsert" parameterType="list">
insert into user(id,username,gender,age) values
<foreach collection="list" separator="," item="user">
(
#{user.id},
#{user.username},
#{user.gender},
#{user.age}
)
</foreach>
</insert>
复制代码
最后开始编写测试类. selectByIds:
@Test
public void TestSelectByIds() throws IOException {
// 获取配置文件 转换为输入流
InputStream resourceAsStream =
Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory 后面细说
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取sqlSessin
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过JDK动态代理获取接口代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
// 构建入参对象
int[] ids = new int[]{1,2};
List<User> users = userDao.selectByIds(ids);
for (User user : users) {
System.out.println("id = " + user.getId() +
", username = " + user.getUsername() +
", gender = " + user.getGender() +
", age = " +user.getAge());
}
// 关闭sqlSession
sqlSession.close();
}
复制代码
测试方法batchInsert:
@Test
public void TestBatchInsert() throws IOException {
// 获取配置文件 转换为输入流
InputStream resourceAsStream =
Resources.getResourceAsStream("mybatis-config.xml");
// 获取 SqlSessionFactory 后面细说
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(resourceAsStream);
// 获取sqlSessin
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过JDK动态代理获取接口代理对象
UserDao userDao = sqlSession.getMapper(UserDao.class);
// 构建入参对象
List<User> users = new ArrayList<>();
User user1 = new User();
user1.setId(3);
user1.setUsername("tom");
user1.setGender(1);
user1.setAge(20);
User user2 = new User();
user2.setId(4);
user2.setUsername("lisa");
user2.setGender(2);
user2.setAge(22);
users.add(user1);
users.add(user2);
int row = userDao.batchInsert(users);
System.out.println(row);
// 提交事务
sqlSession.commit();
// 关闭sqlSession
sqlSession.close();
}
复制代码
日志显示受影响行数2并且数据库也插入成功.
受篇幅影响批量修改与批量删除仅给出代码, 大家可以自行测试
int batchUpdate(List<User> users);
int deleteByIds(int[] ids);
复制代码
<!-- 批量修改 -->
<update id="batchUpdate" parameterType="list">
<foreach collection="list" item="user" separator=";">
update user
<set>
<if test="user.username != null and user.username != ''">
username = #{user.username},
</if>
<if test="user.gender != null">
gender = #{user.gender},
</if>
<if test="user.age != null">
age = #{user.age}
</if>
</set>
where id = #{user.id}
</foreach>
</update>
<!-- 批量删除 -->
<delete id="deleteByIds" parameterType="_int[]">
delete from user
<where>
<foreach collection="array" open="id in (" close=")" separator="," item="id">
#{id}
</foreach>
</where>
</delete>
复制代码
四、总结
好的设计总是给你了明确的边界又有足够的灵活度
, 就像今天学习的三个标签where, if, foreach
. 他们既可以单独使用也可以组合使用, 这样保证了我们代码的灵活度, 保证尽量可以满足我们开发需求. 初次学习MyBatis的同学可能会被一些太过灵活的设计
弄晕. 我们应该以更高的视角来学习. 从整个大的框架看待这些特性, 而不是钻入某个知识点纠结不已
. 提高思考的维度加上勤奋的练习, 驾驭这复杂性, 更好的利用工具
.
五、历史链接
如果有概念含糊不清或者错误的情况. 欢迎大家指出. 感谢.