年轻的时候,遇见了一个人,便以为余生再没有江湖,后来,才懂,她才是江湖的起源。
上一章简单介绍了MyBatis的XxxMapper.xml 映射器的详解(六),如果没有看过,请观看上一章。
一. 动态Sql 查询
在数据库的持久化操作的过程中,最复杂最常用的就是查询 select 语句, 主要是后面跟着各种各样的 条件判断语句。而MyBatis很好的提供了这一点。 体现了Mybatis的灵活性,高度可配置性和可维护性。
具有的sql动态元素有:
- if 判断 单分支的语句
- choose (when otherwise) 多条件分支
- trim 去除前缀和后缀的第一个符号
- where 辅助元素, 代替 sql中的 1=1
- set 更新元素
- foreach 循环判断
- bind 绑定元素
下面 老蝴蝶 会讲解这些元素的使用。
二. if元素
二.一 以前不带参数的查询
老蝴蝶 以前常写的 查询全部:
接口:
User getById(int id);
List<User> findAllF1();
sql 语句配置:
<!-- 定义map形式 -->
<resultMap type="user" id="userResultMap">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<result property="description" column="description"/>
</resultMap>
<select id="getById" parameterType="int" resultType="user">
<!-- 设置别名 -->
select * from user where id=#{id}
</select>
<select id="findAllF1" resultMap="userResultMap">
select * from user t
</select>
测试方法:
@Test
public void findAllF1Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<User> allList=userMapper.findAllF1();
allList.forEach(n ->System.out.println(n));
}
二.二 带参数的简单查询
上面的都是没有传入条件的,下面传入条件。 按照 名称进行查询,其中,名称可填可不填。 即,如果名称有值,则按照名称进行查询,如果没有传入名称值,则查询全部。
接口:
List<User> findAllF2(User user);
sql 元素: 用mybatis提供的 if 元素 进行单分支判断。
<select id="findAllF2" parameterType="user" resultMap="userResultMap">
select * from user t where
<if test="name!=null and name!=''">
t.name like concat('%',#{name},'%')
</if>
</select>
测试方法:
@Test
public void findAllF2Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=new User();
user.setName("蝴蝶飞");
List<User> allList=userMapper.findAllF2(user);
allList.forEach(n ->System.out.println(n));
}
可以填写查询的值为 蝴蝶飞, 可以查询出结果。
但如果此时 没有传入 name值呢?
即将 user.setName(“蝴蝶飞”); 注释掉:
是错误的。
二.三 添加1=1 避免sql语句组装不正确
所以,常见的写法就是在where 后面添加一个 1=1, 避免在没有传入参数时,sql 语句组装不正确。
<select id="findAllF2" parameterType="user" resultMap="userResultMap">
select * from user t where 1=1
<if test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</if>
</select>
当然,也可以传入多个参数, 如按照name 和age 进行查询。 接口是不变的。
sql 语句也按照 上面 if 的写法。 后面加上1=1 就避免了sql语句出现错误
<select id="findAllF2" parameterType="user" resultMap="userResultMap">
select * from user t where 1=1
<if test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</if>
<if test="age!=null">
and t.age>#{age}
</if>
</select>
测试方法:
@Test
public void findAllF2Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=new User();
//两个条件时
user.setName("蝴蝶飞");
user.setAge(24);
List<User> allList=userMapper.findAllF2(user);
allList.forEach(n ->System.out.println(n));
}
传入一个name 参数,传入一个age 参数, 不传参数, 都是正确的, 只是拼接的sql 语句不一样而已。
三. where 语句
在if 中 写的 1=1 虽然避免了错误,但总是不合理的, 应该直接对参数进行相应的解析。如果没有传入值,应该是 select * from user, 而不是 后面再跟上 where 1=1 , 故可以用where 语句进行。 where 语句中条件正确成立后,才会加上 where 语句。
故 接口为:
List<User> findAllF3(User user);
sql语句:
<select id="findAllF3" parameterType="user" resultMap="userResultMap">
select * from user t
<where>
<if test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</if>
<if test="age!=null">
and t.age>#{age}
</if>
</where>
</select>
测试方法:
@Test
public void findAllF3Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=new User();
//user.setName("蝴蝶飞"); //填写时, 不填写时不又其作为查询条件。
//两个条件时
user.setName("蝴蝶飞");
user.setAge(24);
List<User> allList=userMapper.findAllF3(user);
allList.forEach(n ->System.out.println(n));
}
传入两个参数是:
传入一个 name 参数为:
传入一个 age 参数为:
不传入参数为:
这样,就可以达到 去除 1=1 的效果了。
四. trim() 去除前后缀
<select id="findAllF3" parameterType="user" resultMap="userResultMap">
select * from user t <!-- 去掉第一个and -->
<trim prefix="where" prefixOverrides="and">
<if test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</if>
<if test="age!=null">
and t.age>#{age}
</if>
</trim>
</select>
前缀 prefix 是 where , 去除前缀注解 prefixOverrides 的and , 即去除where 后的第一个and 关键字, 这样就可以在每一个 语句后面跟上 and 而不出错了。
除了前缀之外,还有后缀 suffix 和对应的suffixOverrides 。
常见的是前缀, 去除的是 and 或者是 or.
五. choose (when,otherwise) 多分支选择
在前面的两个参数 name,age 时 查询,是多条件查询, name 和sex 是没有先后顺序的。 而在业务当中,常常是有先后顺序的。 如 name 有值,则查询name, name 无值 则去查询age, 如果age 仍然没有值,则保证 description 描述不能为空, 查询的条件之间是有明显的先后顺序的。 这个时候,就需要用 choose 了。
接口:
//有顺序的查询
List<User> findAllF4(User user);
sql 语句: (先用以前的 1=1 模式)
<select id="findAllF4" parameterType="user" resultMap="userResultMap">
select * from user t where 1=1
<!--总的选择开头-->
<choose>
<!--对每一种情况进行判断-->
<when test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</when>
<when test="age!=null">
and t.age>#{age}
</when>
<!--所有条件都不符合,执行 otherwise -->
<otherwise>
and t.description is not null
</otherwise>
</choose>
</select>
测试方法:
@Test
public void findAllF4Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=new User();
//user.setName("蝴蝶飞"); //填写时, 不填写时不又其作为查询条件。
//两个条件时 有一定的先后顺序。
user.setName("蝴蝶飞");
user.setAge(24);
List<User> allList=userMapper.findAllF4(user);
allList.forEach(n ->System.out.println(n));
}
如果name 有值的话:
如果 name 无值, age 有值的话:
如果 name 和age 均没有值的话:
这样,就达到了 有先后顺序的查询了。
转换成 where 语句为:
<select id="findAllF4" parameterType="user" resultMap="userResultMap">
select * from user t
<where>
<choose>
<when test="name!=null and name!=''">
and t.name like concat('%',#{name},'%')
</when>
<when test="age!=null">
and t.age>#{age}
</when>
<otherwise>
and t.description is not null
</otherwise>
</choose>
</where>
</select>
六. set 更新部分字段
关于update 语句的用法,可以看第三章, 那个时候,更新都是更新全部的字段,无法进行部分字段的更新。 而以前所讲的Hibernate 只能进行全部字段的更新,这也是Hibernate 效率低的原因之一。
<update id="updateUser" parameterType="user">
update user set name=#{name},sex=#{sex},age=#{age},description=#{description}
where id=#{id}
</update>
而MyBatis 通过 set 语句,很好的解决了这一点。
接口:
int updateF5(User user);
sql 语句:
<update id="updateF5" parameterType="user">
update user
<set>
<if test="name!=null and name!=''">
name=#{name},
</if>
<if test="age!=null">
age=#{age},
</if>
<if test="description !=null">
description=#{description}
</if>
</set>
where id=#{id}
</update>
六.一 查询数据库更新
@Test
public void findAllF51Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=userMapper.getById(1);
user.setName("部分更新字段123");
user.setDescription("只修改部分字段");
userMapper.updateF5(user);
sqlSession.commit();
}
会将数据库中查询出来的值进行判断,如果有值,那么就进行更新,没有值,就不更新。 如果将此时的 age字段在数据库中设置为null 的话.
再次运行的话:
发现,这个时候 就不会更新 age 字段了。
六.二 设置对象bean 更新
@Test
public void findAllF52Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
User user=new User();
user.setId(1);
user.setName("部分更新字段");
user.setDescription("只修改部分字段");
userMapper.updateF5(user);
sqlSession.commit();
}
这个时候,运行的话,是不会更新其他的字段的。
七. foreach 查询
在数据库中查询的话, 常常会查询id 的集合,进行批量的查询。 常见的形式有 数组 和List 集合的形式。 遍历查询,用的是 foreach .
属性有:
collection 为集合, item 为循环的变量, index 为索引, open 为开始, close 为结束,separator 为分隔符。
如果是单个的数组, 那么可以 collection=“array”,
如果是单个的集合,那么可以 collection=“list”
如果是多个的话,那么可以用Map 或者是@Param 注解进行传参。
七.一 数组形式
接口:
//数组形式
List<User> findAllF6(Integer[] ids);
sql语句:
<select id="findAllF6" parameterType="list" resultMap="userResultMap">
select * from user t
<where>
id in
<foreach collection="array" item="id" index="index" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
测试方法:
@Test
public void findAllF61Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<User> allList=userMapper.findAllF6(new Integer[]{1,3,4,5});
allList.forEach(n ->System.out.println(n));
}
七.二 集合形式
接口:
List<User> findAllF6(List<Integer> ids);
sql 语句:
<select id="findAllF6" parameterType="list" resultMap="userResultMap">
select * from user t
<where>
id in
<foreach collection="list" item="id" index="index" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
测试方法:
@Test
public void findAllF6Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<Integer> ids=Arrays.asList(1,3,4);
List<User> allList=userMapper.findAllF6(ids);
allList.forEach(n ->System.out.println(n));
}
七.三 多值时 用@Param 注解
接口:
List<User> findAllF6(@Param(value="ids") List<Integer> ids,
@Param(value="sexs") List<String> sexs);
sql 语句:
<select id="findAllF6" parameterType="list" resultMap="userResultMap">
select * from user t
<where>
id in
<foreach collection="ids" item="id" index="index" open="(" separator="," close=")">
#{id}
</foreach>
and sex in
<foreach collection="sexs" item="sex" index="index" open="(" separator="," close=")">
#{sex}
</foreach>
</where>
</select>
测试方法:
@Test
public void findAllF62Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<Integer> ids=Arrays.asList(1,3,4);
List<String> sexs=Arrays.asList("男","女");
List<User> allList=userMapper.findAllF6(ids,sexs);
allList.forEach(n ->System.out.println(n));
}
foreach 的用法很重要。
八. bind 绑定
通过OGNL 表达式定义一个上下文变量,方便我们使用。 如字符串拼接时, mysql 中拼接是 concat() 函数,而oracle 是 || 进行拼接, 这样可以在用 like 语句时要根据不同的数据库来更新语句。 而 bind 语句则解决了这种问题。
接口:
List<User> findAllF7(@Param(value="name") String name,@Param(value="description") String description);
sql 语句:
<select id="findAllF7" parameterType="list" resultMap="userResultMap">
select * from user t
<where>
<!-- 先定义变量 pattern_name 和pattern_description -->
<bind name="pattern_name" value="'%'+name+'%'"/>
<bind name="pattern_description" value="'%'+description+'%'"/>
<if test="name!=null and name!=''">
<!-- 引入变量 pattern_name -->
and name like #{pattern_name}
</if>
<if test="description!=null and description!=''">
<!-- 引入变量 pattern_description -->
and description like #{pattern_description}
</if>
</where>
</select>
测试方法:
@Test
public void findAllF7Test(){
SqlSession sqlSession=SqlSessionFactoryUtils.getSession();
UserMapper userMapper=sqlSession.getMapper(UserMapper.class);
List<User> allList=userMapper.findAllF7("蝴蝶","蝴蝶");
allList.forEach(n ->System.out.println(n));
}
这就是 bind 的常见用法。
谢谢!!!