title: javaEE(SSM)学习笔记4:动态SQL
动态SQL引入
【主要作用】:根据不同条件,或者不同数据,拼接SQL子句,在XML文件中实现类似编程的效果。
【主要元素(标记)】:
1.条件查询操作
1.1<if>元素
根据条件拼接SQL语句,满足条件即包含SQL子句。
(1)语法
<if test="条件表达式">
SQL子句
</if>
(2)示例1:模糊匹配
【需求】根据输入的姓名或职业模糊查询客户表中的客户信息:
1.如果姓名不为空,则按姓名查询;
2.如果职业不为空,则按职业查询;
3.如果两者都不为空,则必须满足指定姓名和职业的条件;
4.如果两者都为空,则显示全部数据;
【分析】:该需求适合以下各种情况
条件1:select * from customer
条件2:select * from customer where username like concat('%',#{username},'%')
条件3:select * from customer where jobs like concat('%',#{jobs},'%')
条件4:select * from customer where username like concat('%',#{username},'%')
AND jobs like concat('%',#{jobs},'%')
【具体实现】:
CustomerMapper.xml内容
<select id="getCustomer" parameterType="customer" resultType="customer">
select * from customer where 1=1
<if test="username!=null and username!=''">
and username like concat('%',#{username},'%')
</if>
<if test="jobs!=null and jobs!=''">
and jobs like concat('%',#{jobs},'%')
</if>
</select>
【注意】参数类型是pojo类,test中直接使用其属性来判断
【测试核心代码】:
//1参数为null :显示全部数据
//sqlSession.selectList("getCustomer");
//2.姓名不为空
cust.setUsername("张三");
sqlSession.selectList("getCustomer",cust);
//3物业不为空
cust.setUsername("");
cust.setJobs("物业");
sqlSession.selectList("getCustomer",cust);
//4.两者不为空
cust.setUsername("张三");
cust.setJobs("老板");
sqlSession.selectList("getCustomer",cust);
【运行结果,类似如下】:
DEBUG [main] - ==> Preparing: select * from customer where 1=1 and username like concat('%',?,'%')
DEBUG [main] - ==> Parameters: 张三(String)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 58, 测试张三, 物业, 136, 2022-01-09 09:42:57
TRACE [main] - <== Row: 59, 测试张三, 工人, 136, 2022-01-16 19:57:16
DEBUG [main] - <== Total: 2
DEBUG [main] - ==> Preparing: select * from customer where 1=1 and jobs like concat('%',?,'%')
DEBUG [main] - ==> Parameters: 物业(String)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 58, 测试张三, 物业, 136, 2022-01-09 09:42:57
DEBUG [main] - <== Total: 1
DEBUG [main] - ==> Preparing: select * from customer where 1=1 and username like concat('%',?,'%') and jobs like concat('%',?,'%')
DEBUG [main] - ==> Parameters: 张三(String), 老板(String)
DEBUG [main] - <== Total: 0(无数据)
(3)示例2:test运算符使用 和sql运算符
【需求】:输入id,如果id<20,那么选择id<5的数据输出
【注意】:在test中的使用,以及在xml中的如何使用运算符
CustomerMapper.xml内容
<!-- 如果id参数不为空(null) ,且小于等于20,那么选择5-该值的数据 -->
<select id="getCustomerById" parameterType="int" resultType="customer">
select * from customer where 1=1
<if test="_parameter !=null and _parameter lte 20">
and id >= 5 and id <= #{id}
</if>
</select>
【测试结果,类似如下】:
DEBUG [main]==> Preparing: select * from customer where 1=1 and id >= 5 and id <= ?
DEBUG [main] - ==> Parameters: 20(Integer)
TRACE [main] - <== Columns: id, username, jobs, phone
TRACE [main] - <== Row: 8, 孙六六, 管理员, 133333334444
TRACE [main] - <== Row: 9, 孙六, 工人, 44444444444444444444
TRACE [main] - <== Row: 10, 孙六, 工人, 44444444444444444444
DEBUG [main] - <== Total: 3
1.2 <choose>元素
作用:在给出的条件中,仅仅拼接满足条件的第一个子句。
(1)语法
<choose>
<when test="条件表达式1"> SQL子句1</when>
<when test="条件表达式2"> SQL子句2</when>
...
<otherwise>
SQL子句3
</otherwise>
</choose>
按顺序判断,只要满足其中一个条件,则不再往下判断;当所有条件不满足,执行otherwise中的子句。
(2)示例:查询客户信息
【需求】:
1.如果给出客户名称,则按客户名称进行模糊查询;
2.如果给出客户职业,按照客户职业进行模糊查询;
3.如果两者都不给出,则查询所有联系电话不为空的客户信息。
CustomerMapper.xml内容
<select id="findCustomer" parameterType="customer" resultType="customer">
select * from customer
where 1=1
<choose>
<when test="username!=null and username!=''">
and username like concat('%',#{username},'%')
</when>
<when test="jobs!=null and jobs!=''">
and jobs like concat('%',#{jobs},'%')
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
</select>
1.3 where元素
【作用】根据是否有条件子句在进行拼接。根据标记内是否包含SQL子句,决定是否使用where关键字拼接查询子句,同时它会去除多余的连接运算符,比如AND 和 OR等。有了这个标记,上例的示例中的子句就不要这么写了:where 1=1
示例:使用where 元素重新实现上例
<select id="findCustomer2" parameterType="customer"
resultType="customer">
select * from customer
<where>
<choose>
<when test="username!=null and username!=''">
and username like concat('%',#{username},'%')
</when>
<when test="jobs!=null and jobs!=''">
and jobs like concat('%',#{jobs},'%')
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
</where>
</select>
1.4 trim元素
【作用】能够自动添加前缀,并去除子句前面多余的、指定的连接字符(串);或者添加后缀,自动去除子句后面多余的、指定的连接字符或字符串。用法非常灵活。
其包含以下属性:
实际上,就是在子句之前、后是否要添加SQL连接关键字,是否要删除子句前后的多余关键字,目的是自动构成正确的SQL语句。
示例1:使用trim代替where元素
如果有子句,自动加上指定的where前缀,然后删除多余的、指定的连接符
【需求】:根据客户名或者职业来查询客户信息
【思路】:在子句前加入WHERE,去掉子句前面多余的OR
CustomerMapper.xml内容
<!-- 使用trim 来代替 where -->
<select id="getCustomer_trim" parameterType="customer"
resultType="customer">
select * from customer
<trim prefix="WHERE" prefixOverrides="OR">
<if test="username!=null and username!=''">
OR username like concat('%',#{username},'%')
</if>
<if test="jobs!=null and jobs!=''">
OR jobs like concat('%',#{jobs},'%')
</if>
</trim>
</select>
【 测试】:
@Test
public void test3() {
sqlSession = ssf.openSession();
Customer c=new Customer();
c.setJobs("工人");
c.setUsername("孙");
sqlSession.selectList("getCustomer_trim",c);
}
【结果类似】:
DEBUG [main] - ==> Preparing: select * from customer WHERE username like concat('%',?,'%') or jobs like concat('%',?,'%')
DEBUG [main] - ==> Parameters: 孙(String), 工人(String)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 8, 孙六六, 管理员, 133333334444, 2019-10-16 20:59:08
TRACE [main] - <== Row: 9, 孙六1, 工人, 44444444444444444444, 2022-01-16 23:09:50
TRACE [main] - <== Row: 10, 孙六2, 工人, (Null), 2022-01-16 23:10:57
TRACE [main] - <== Row: 59, 测试张三, 工人, 136, 2022-01-16 19:57:16
DEBUG [main] - <== Total: 4
示例2:使用trim,代替更新子句set
如果有子句,自动加上指定的set前缀,然后删除多余的、指定的连接符
【需求】:根据id更新客户信息,如果参数存在客户名称,则更新客户名称;如果存在职业,则同时更新职业。即只更新包含值的字段。
【注意】:这种情况不能一个值没有,这样无法构成正确的SQL语句(前端去判断)。
<!-- 使用trim 代替 set -->
<update id="updateCustomer" parameterType="customer">
update customer
<trim prefix="set" suffixOverrides=",">
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="jobs!=null and jobs!=''">
jobs=#{jobs},
</if>
</trim>
where id=#{id}
</update>
【测试】
@Test
public void test4() {
sqlSession = ssf.openSession();
Customer c=new Customer();
c.setId(8);
c.setJobs("工人");
c.setUsername("孙明");
sqlSession.update("updateCustomer",c);
sqlSession.commit();//需要提交
}
【结果类似】
DEBUG [main] - ==> Preparing: update customer set username=?, jobs=? where id=?
DEBUG [main] - ==> Parameters: 孙明(String), 工人(String), 8(Integer)
DEBUG [main] - <== Updates: 1
1.5 复杂查询:foreach
列举出输入参数的每一个元素。类似java的循环结果,依次从传入的数组Array、列表List或Map中取出数据,拼接成SQL子句。
【建议】:先在MySQL写出完整的sql,再去修改对应的动态SQL。
【应用场景】:
1.从一组指定的下标中查询客户信息,使用数组或列表作为输入参数。
2.输入参数同时包含多种类型,如普通类型、POJO类型、数组或列表类型,即多参数查询,使用map。例如使用用户名、职业进行模糊查询,并使用一组下标进行查询客户信息。
3.插入一组数据。
(1)语法1:使用数组和列表
<select id="" parameterType="arraylist|list" resultType="返回结果类型">
...
<foreach item="循环变量" collection="array/list" index="当前循环下标"
open="整个子句的前缀" close="整个子句的后缀" seperator="分隔符" >
SQL子语句
</foreach>
</select>
(2)语法1应用示例:查找一组id对应的数据
【需求】:从一组指定的id中查询客户信息,使用数组和列表分别实现
CustomerMapper.xml内容
<!-- 使用数组 -->
<select id="byArrays" parameterType="arraylist" resultType="customer">
select * from customer where id in
<foreach item="id" collection="array" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!--使用列表 -->
<select id="byList" parameterType="list" resultType="customer">
select * from customer where id in
<foreach item="id" collection="list" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
【测试】
@Test
public void test5() {
sqlSession = ssf.openSession();
int[] ids= {5,8,10};
sqlSession.selectList("byArrays",ids);
}
@Test
public void test6() {
sqlSession = ssf.openSession();
List<Integer> ids=new ArrayList<Integer>();
ids.add(5);
ids.add(8);
ids.add(10);
sqlSession.selectList("byList",ids);
}
【结果类似如下】:
DEBUG [main] - ==> Preparing: select * from customer where id in ( ? , ? , ? )
DEBUG [main] - ==> Parameters: 5(Integer), 8(Integer), 10(Integer)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 8, 孙明, 工人, 133333334444, 2022-01-17 07:14:33
TRACE [main] - <== Row: 10, 孙六2, 工人, (Null), 2022-01-16 23:10:57
DEBUG [main] - <== Total: 2
DEBUG [main] - ==> Preparing: select * from customer where id in ( ? , ? , ? )
DEBUG [main] - ==> Parameters: 5(Integer), 8(Integer), 10(Integer)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 8, 孙明, 工人, 133333334444, 2022-01-17 07:14:33
TRACE [main] - <== Row: 10, 孙六2, 工人, (Null), 2022-01-16 23:10:57
DEBUG [main] - <== Total: 2
(3)语法2:使用Map
当需要根据多个不同类型参数查询时,可以使用map类型参数
【语法】:
<select id="" parameterType="map" resultType="">
...
<foreach item="循环变量" collection="key名" index="当前循环下标"
open="前缀" close="后缀" seperator="分隔符" >
组合的SQL语句
</foreach>
</select>
(4)语法2应用示例:根据一组id和其他字段查询
【需求】:根据一组指定的id,和职业进行查询客户信息
CustomerMapper.xml内容
<select id="byMap" parameterType="map" resultType="customer">
select * from customer
where jobs like concat('%',#{jobs},'%')
and id in
<foreach item="id" collection="ids" open="(" close=")" separator="," >
#{id}
</foreach>
</select>
【测试】
@Test
public void test7() {
sqlSession = ssf.openSession();
List<Integer> ids=new ArrayList<Integer>();
ids.add(5);
ids.add(8);
ids.add(10);
Map<String,Object> map=new HashMap<String,Object>();
map.put("jobs", "工人");//普通字符串
map.put("ids", ids);//集合或者数组 int[] ids= {5,8,10};
sqlSession.selectList("byMap",map);
}
1.6foreach扩展应用
【扩展内容】主要是了解foreach还有哪些用法。
(1)使用数组,传递数组范围
【需求】:传递范围id(两个整数id,上限和下限),查询id在该范围的客户信息
【分析】:使用数组 int[] ={1,10} 封装数据,在foreach中,根据下标不同,拼接SQL子句
<select id="byRange" parameterType="int[]" resultType="customer">
select * from customer
<where>
<foreach item="id" collection="array" index="index">
<if test="index==0">
and id >= #{id}
</if>
<if test="index==1">
and id <=#{id}
</if>
</foreach>
</where>
</select>
【测试】
@Test
public void test8() {
sqlSession = ssf.openSession();
int[] ids= {1,10};
sqlSession.selectList("byRange",ids);
}
【结果类似】
DEBUG [main] - ==> Preparing: select * from customer WHERE id >= ? and id <=?
DEBUG [main] - ==> Parameters: 1(Integer), 10(Integer)
TRACE [main] - <== Columns: id, username, jobs, phone, createtime
TRACE [main] - <== Row: 2, 李四, 老板, 111111111111111, 2022-01-16 20:28:23
TRACE [main] - <== Row: 3, 王五, 经理, 111111111111111, 2022-01-16 20:28:33
TRACE [main] - <== Row: 8, 孙明, 工人, 133333334444, 2022-01-17 07:14:33
TRACE [main] - <== Row: 9, 孙六1, 工人, 44444444444444444444, 2022-01-16 23:09:50
TRACE [main] - <== Row: 10, 孙六2, 工人, (Null), 2022-01-16 23:10:57
DEBUG [main] - <== Total: 5
(2)同时插入一组客户信息
【需求】:同时插入多条记录
【分析】:多条记录可以使用List方式封装数据,在映射SQL中,使用foreach获取逐个对象,再拼接SQL子句
<insert id="addCustomers" parameterType="list">
insert into customer(username,jobs,phone) values
<foreach item="c" collection="list" separator=",">
( #{c.username},#{c.jobs},#{c.phone} )
</foreach>
</insert>
【测试】
@Test
public void test9() {
sqlSession = ssf.openSession();
List<Customer> cs=new ArrayList<Customer>();
Customer c=new Customer();
c.setUsername("A1");
c.setJobs("管理工人");
c.setPhone("1111");
cs.add(c);//插入记录1
c=new Customer();
c.setUsername("A2");
c.setJobs("管理");
c.setPhone("1111");
cs.add(c);//插入记录2
sqlSession.insert("addCustomers",cs);
sqlSession.commit();//别忘记了
}
结果类似
DEBUG [main] - ==> Preparing: insert into customer(username,jobs,phone) values ( ?,?,? ) , ( ?,?,? )
DEBUG [main] - ==> Parameters: A1(String), 管理工人(String), 1111(String), A2(String), 管理(String), 1111(String)
DEBUG [main] - <== Updates: 2
【重点记住】
1.parameterType:
2.foreach中