动态 SQL是MyBatis强大特性之一。极大的简化我们拼装 SQL的操作。
动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。
MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作。
if
choose (when, otherwise)
trim (where, set)
foreach
对象图导航语言,这是一种强大的表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等
访问对象属性: person.name
调用方法: person.getName()
调用静态属性: @java.lang.Math@PI
调用静态方法: @java.util.UUID@randomUUID()
调用构造方法: new com.atguigu.bean.Person(‘admin’).name
运算符: +,-*,/,%
逻辑运算符: in,not in,>,>=,<,<=,==,!=
注意:xml中特殊符号如”,>,<等这些都需要使用转义字符
<!--
现在查询员工,要求是:如果查询给了哪一个属性的值,
查询的时候就加上这个属性的值作为查询条件
-->
<select id="getEmpByIf" resultType="bean.Emp">
<!--if
test:就是判断表达式(OGNL)
是从参数里面取值进行判断的
如果遇到特殊符号应该去写转义字符
比如下面的ename的判断
单引号可以写成:"
and本来可以写成&&,但是$是特殊符号,可以写成&
结果就是:<if test="ename!=null && ename!="" "></if>
下面的gender=="男"不能写成gender=='男'
这样不能通过,'男'会将男当成数字进行转换,但是''可以代表空串
-->
select * from emp where
<if test="eid!=null">
eid=#{eid}
</if>
<if test="ename!=null and ename!='' ">
and ename like #{ename}
</if>
<if test="email!=null and email.trim()!=''">
and email=#{email}
</if>
<if test="gender=="男" or gender=="女"">
and gender=#{gender}
</if>
</select>
public List<Emp> getEmpByIf(Emp emp);
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(7, "jane", null, "男");
List<Emp> empByIf = empMapperDynamicSQL.getEmpByIf(emp);
for (Emp emp2 : empByIf)
{
System.out.println(emp2);
}
结果:(发的查询条件没有email)
DEBUG 05-17 22:51:18,439 ==> Preparing: select * from emp where eid=? and ename like ? and gender=? (BaseJdbcLogger.java:145)
DEBUG 05-17 22:51:18,466 ==> Parameters: 7(Integer), jane(String), 男(String) (BaseJdbcLogger.java:145)
DEBUG 05-17 22:51:18,484 <== Total: 1 (BaseJdbcLogger.java:145)
Emp [eid=7, ename=jane, email=jane.com, gender=男, dept=null]
上面的if查询你可以看出来,如果你不传入eid的话,就会多出一个and,那么就会报sql的错,因为sql变成这样的了
select * from emp where and ename like ?
解决的方法有
但是注意的是,where标签只会去掉每个标签第一个位置多出来的and和or
<select id="getEmpByIf" resultType="bean.Emp">
select * from emp
<where>
<if test="eid!=null">
eid=#{eid} and
</if>
<if test="ename!=null and ename!='' ">
ename like #{ename} and
</if>
<if test="email!=null and email.trim()!=''">
email=#{email} and
</if>
<if test="gender=="男" or gender=="女"">
gender=#{gender}
</if>
</where>
</select>
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(7, null, null, null);
List<Emp> empByIf = empMapperDynamicSQL.getEmpByIf(emp);
结果的SQL:select * from emp WHERE eid=? and
正确的写法:
<select id="getEmpByIf" resultType="bean.Emp">
select * from emp
<where>
<if test="eid!=null">
eid=#{eid}
</if>
<if test="ename!=null and ename!='' ">
and ename like #{ename}
</if>
<if test="email!=null and email.trim()!=''">
and email=#{email}
</if>
<if test="gender=="男" or gender=="女"">
and gender=#{gender}
</if>
</where>
</select>
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(null, "jane", null, null);
List<Emp> empByIf = empMapperDynamicSQL.getEmpByIf(emp);
for (Emp emp2 : empByIf)
{
System.out.println(emp2);
}
结果SQL:
select * from emp WHERE ename like ?
<select id="getEmpByIfTrim" resultType="bean.Emp">
select * from emp
<!--
现在是后面多出的and或者or,where标签不能解决
trim:自定义字符串截取规则
trim标签体中是一个整体的字符串拼接后的结果
prefix:前缀,给拼串后的整个字符串增加一个前缀
prefixOverrides:前缀覆盖,去掉字符串(每次拼接的字符串)前面多余的字符
suffix:后缀,给拼串后的整个字符串增加一个后缀
suffixOverrides:后缀覆盖,去掉字符串(每次拼接的字符串)后面多余的字符
-->
<trim prefix="where" suffixOverrides="and">
<if test="eid!=null">
eid=#{eid} and
</if>
<if test="ename!=null and ename!='' ">
ename like #{ename} and
</if>
<if test="email!=null and email.trim()!=''">
email=#{email} and
</if>
<if test="gender=="男" or gender=="女"">
gender=#{gender}
</if>
</trim>
</select>
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(null, "jane", null, null);
List<Emp> empByIf = empMapperDynamicSQL.getEmpByIfTrim(emp);
for (Emp emp2 : empByIf)
{
System.out.println(emp2);
}
结果SQL:select * from emp where ename like ?
<!--
choose(when,otherwise):分支选择,相当于带了break的swtich-case语句
现在需求比如是:如果带了id就只是用id来查,
如果是带了ename就只是使用ename来查,只会进入其中的一个
-->
<select id="getEmpByIfChose" resultType="bean.Emp">
select * from emp
<where>
<choose>
<when test="eid!=null">eid=#{eid}</when>
<when test="ename!=null and ename!='' ">ename like #{ename}</when>
<when test="email!=null and email.trim()!=''">email=#{email}</when>
<otherwise>gender="女"</otherwise>
</choose>
</where>
</select>
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(null, null, null, null);
List<Emp> empByIf = empMapperDynamicSQL.getEmpByIfChose(emp);
for (Emp emp2 : empByIf)
{
System.out.println(emp2);
}
结果:
select * from emp WHERE gender="女"
<!-- public void updateEmp(Emp emp);
现在需求是更新员工的信息,
之前是写死的更新员工的全部信息,现在是哪一个
属性有值就更新哪一列,
如果只有if标签,在每一个if后面就会多出一个,(逗号)
所以使用<set>标签,作用有两:有一个set,每一个标签后面的逗号去掉
这个set标签的作用也可以使用<Trim>标签来代替
-->
<update id="updateEmp" >
update emp
<set>
<if test="ename!=null">ename=#{ename},</if>
<if test="email!=null">email=#{email},</if>
<if test="gender!=null">gender=#{gender}</if>
where eid=#{eid}
</set>
</update>
EmpMapperDynamicSQL empMapperDynamicSQL = openSession.getMapper(EmpMapperDynamicSQL.class);
Emp emp = new Emp(3, "凤", null, "女");
empMapperDynamicSQL.updateEmp(emp);
openSession.commit();
<!-- 现在需求是传入多个eid,查询多名员工
public List<Emp> getEmpByForeach(List<Integer> eids);
collection:指定要便利的集合,就写集合的名字就行
item:将当前遍历的元素赋值给指定的变量
separator:设置每个元素之间的分隔符
open:遍历出所有的结果后,在拼接的总字符串前面加一个开始的东西
close:遍历出总的结果后,在总的字符串后面增加一个结束的字符串
index:索引.遍历list的时候index是索引,item就是值
遍历map的时候index就是map的key,item就是map的值
#{变量名}:这样就能取出变量的值,也就是当前遍历出的元素
-->
<select id="getEmpByForeach" resultType="bean.Emp">
select * from emp
<foreach collection="eids" item="theid"
separator="," open="where eid in(" close=")">
#{theid}
</foreach>
</select>
public List<Emp> getEmpByForeach(@Param("eids")List<Integer> eids);
List<Emp> empByForeach = empMapperDynamicSQL.getEmpByForeach(Arrays.asList(3,5,7));
for (Emp emp2 : empByForeach)
{
System.out.println(emp2);
}
结果SQL:
Preparing: select * from emp where eid in( ? , ? , ? )
<!-- public void addEmps(@Param("emps")List<Emp> emps);
现在需要批量插入多个员工,传入员工的list
有两种写法:
-->
<!-- 第二种方法:这种方式需要数据库设置数据库连接属性allowMultiQueries=true,
这个设置是允许有多条语句,语句之间使用;来连接 -->
<insert id="addEmps">
<foreach collection="emps" item="emp" separator=";">
insert into emp (ename,email,gender,did) values
(#{emp.ename},#{emp.email},#{emp.gender},#{emp.dept.did})
</foreach>
</insert>
<!-- 写法一 -->
<!-- <insert id="addEmps">
insert into emp (ename,email,gender,did) values
<foreach collection="emps" item="emp" separator=",">
(#{emp.ename},#{emp.email},#{emp.gender},#{emp.dept.did})
</foreach>
</insert> -->
<!--
上面的批量保存在MySQL是支持的,
但是在Oracle是不支持values(),(),()这种方式
Oracle支持的批量方式有:
1.多个insert放在begin - end里面
begin
insert into emp(eid,ename,email)
values(emp_seq.nextval,'jane01','jane1.com');
insert into emp(eid,ename,ename)
values(emp_seq.nextval,'jane02','jane2.com');
end;
2.利用中间表,union是联合,后面有多少个数据写多少个
insert into emp(eid,ename,email)
select emp_seq.nextval,ename,email from
(
select 'jane01' ename,'jane1.com' email from dual
union
select 'jane02' ename,'jane2.com' email from dual
union
select 'jane03' ename,'jane3.com' email from dual
)
-->
<insert id="addEmps" databaseId="oracle">
<!-- 第一种方式 -->
<!--
<foreach collection="emps" item="emp" open="begin" close="end;">
insert into emp(eid,ename,email)
values(emp_seq.nextval,#{emp.ename},#{emp.email});
</foreach>
-->
<!-- 第二种方式 -->
<foreach collection="emps" item="emp"
open="insert into emp(eid,ename,email)
select emp_seq.nextval,ename,email from
("
close=")"
separator="union"
>
select #{emp.ename} ename ,#{emp.email} email from dual
</foreach>
</insert>
<!--
两个内置参数:
现在不只是方法传递过来的参数可以用来判断,取值等操作
mybatis还有两个内置的参数:_parameter和_databaseId
_parameter:代表整个参数,就是方法传进来的所有参数
传入单个参数:_parameter就是这个参数
传入多个参数:参数会被封装为一个map,这个_parameter就是代表这个map
_databaseId:如果设置了databaseIdProvider标签给不同数据库厂商起别名
_databaseId就是代表当前数据库厂商的别名
利用这两参数,我们可以在一个select里面写不同数据库的CRUD
例如下面的,我们根据不同的环境切换不同的SQL语句
在mysql环境下执行mysql的语句,oracle执行oracle的语句
只不过我这里两个数据库的表都是一样的
还有根据_parameter可以动态设置查询条件
如果传入的参数不是空的就按照名字查询
-->
<!-- public List<Emp> getEmpInnerParameter(Emp emp); -->
<select id="getEmpInnerParameter" resultType="bean.Emp">
<if test="_databaseId=='mysql'">
select * from emp
<if test="_parameter!=null">
<!-- #{_parameter.ename}和#{ename} 都行 -->
where ename=#{_parameter.ename}
</if>
</if>
<if test="_databaseId=='oracle'">
select * from emp
<if test="_parameter!=null">
where ename=#{ename}
</if>
</if>
</select>
<!-- bind标签:可以将OGNL表达式的值绑定到一个变量中,
方便后来引用这个变量
比如下面的bind标签,先是传入的eid前面增加18154的数字,命名为MyEid
然后在查询的时候引用这个变量
结果:
DEBUG 05-19 06:07:58,349 ==> Preparing: select * from emp where eid=? (BaseJdbcLogger.java:145)
DEBUG 05-19 06:07:58,372 ==> Parameters: 181541(String) (BaseJdbcLogger.java:145)
-->
<select id="getEmpByEid" resultType="bean.Emp">
<bind name="MyEid" value=" '18154'+eid"/>
select * from emp where eid=#{MyEid}
</select>
<!--
抽取可重用的sql片段,方便后面引用
1.sql抽取就是抽取经常要查询的列名,或者插入用的列名抽取出来方便引用
2.而抽取出来的sql想引用使用include来引用
3.include还可以自定义一些property属性,sql标签内能使用自定义的属性
include和property之间的取值使用${变量名}来取值
而${变量名}不能使用这种方法来取值
举个例子使用:
比如之前的在不同的数据库使用不同的查询语句,因为不同数据库里面的字段可能不一样
使用这里就可以将这字段都列出来,而且不同数据库自动选择不同语句
但是我这里数据库字段都一样,这里只是演示
-->
<sql id="insertColumn">
<if test="_databaseId=='mysql' ">
eid,ename,email,#{testcolumn}
</if>
<if test="_databaseId=='oracle'">
eid,ename,email,#{testcolumn}
</if>
</sql>
<!-- 下面的oracle插入就是使用重用sql片段,
其中<property>还设置了一列的列名是jane
-->
<!--
<insert id="addEmps" databaseId="oracle">
insert into emp
(
<include refid="insertColumn">
<property name="testcolumn" value="jane"/>
</include>
)
select emp_seq.nextval,ename,email from
(
<foreach collection="emps" item="emp" close=")" separator="union" >
select #{emp.ename} ename ,#{emp.email} email from dual
</foreach>
</insert>
-->