文章目录
1、综述
在Mapper配置文件中,有时候需要根据一些查询条件来选择不同的SQL语句,或者将一些使用频率极高的SQL语句单独配置,在需要的地方引用。MyBatis提供了一种可以根据条件动态配置SQL语句,以及单独配置SQL语句块的机制。动态SQL,即通过MyBatis提供的各种标签对条件作出判断以实现动态拼接SQL语句。这里的条件判断使用的表达式为OGNL表达式。
2、测试环境搭建
2.1 创建数据库表
在之前测试使用的mybatis数据库中创建student表,创建表的SQL语句及添加测试数据如下:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
`id` int(5) NOT NULL auto_increment,
`name` varchar(20) default NULL,
`age` int(3) default NULL,
`score` double default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '张三', '23', '93.5');
INSERT INTO `student` VALUES ('2', '李四', '24', '94.5');
INSERT INTO `student` VALUES ('3', '王五', '25', '92.5');
2.2 定义实体类
在com.ccff.mybatis.model下定义Student实体类,具体代码如下所示:
package com.ccff.mybatis.model;
public class Student {
private int id;
private String name;
private int age;
private double score;
public Student() {
}
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
2.3 定义接口DAO
在com.ccff.mybatis.dao下定义接口IStudentDao,代码如下:
package com.ccff.mybatis.dao;
import com.ccff.mybatis.model.Student;
import java.util.List;
public interface IStudentDao {
//用于测试if标签
List<Student> selectStudentsIf(Student student);
//用于测试where标签
List<Student> selectStudentsWhere(Student student);
//用于测试choose标签
List<Student> selectStudentsChoose(Student student);
//用于测试foreach标签
List<Student> selectStudentsForeachArray(Object[] studentIds);
List<Student> selectStudentsForeachList(List<Integer> studentIds);
List<Student> selectStudentsForeachList2(List<Student> students);
//用于测试sql标签
List<Student> selectStudentsBySQLFragment(List<Student> students);
}
2.4 定义接口实现类
在com.ccff.mybatis.dao下定义接口IStudentDao的实现类IStudentImpl,代码如下:
package com.ccff.mybatis.dao;
import com.ccff.mybatis.datasource.DataConnection;
import com.ccff.mybatis.model.Student;
import org.apache.ibatis.session.SqlSession;
import java.io.IOException;
import java.util.List;
public class IStudentImpl implements IStudentDao {
private SqlSession sqlSession;
private DataConnection dataConnection;
public IStudentImpl(){
super();
try {
DataConnection dataConnection = new DataConnection();
sqlSession = dataConnection.getSqlSession();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public List<Student> selectStudentsIf(Student student) {
return null;
}
@Override
public List<Student> selectStudentsWhere(Student student) {
return null;
}
@Override
public List<Student> selectStudentsChoose(Student student) {
return null;
}
@Override
public List<Student> selectStudentsForeachArray(Object[] studentIds) {
return null;
}
@Override
public List<Student> selectStudentsForeachList(List<Integer> studentIds) {
return null;
}
@Override
public List<Student> selectStudentsForeachList2(List<Student> students) {
return null;
}
@Override
public List<Student> selectStudentsBySQLFragment(List<Student> students) {
return null;
}
private void close(){
sqlSession.close();
}
}
2.5 定义测试类
在com.ccff.mybatis.test下定义测试类StudentTest,代码如下:
package com.ccff.mybatis.test;
import com.ccff.mybatis.dao.IStudentDao;
import com.ccff.mybatis.dao.IStudentImpl;
public class StudentTest {
private IStudentDao studentDao;
public StudentTest(){
super();
studentDao = new IStudentImpl();
}
}
2.6 定义StudentMapper映射文件
在config/sqlmap下创建名为“StudentMapper”的XML文件,具体如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="StudentTest">
</mapper>
2.7 注意事项
在Mapper的动态SQL中若出现大于号、小于号、大于等于号和小于等于号,最好将其转换为实体符号。否则XML可能会出现解析出错问题。特别是对小于号(<),在XML中是绝对不能出现的。否则,一定出错。具体的对应转换如下图所示:
3、if标签
对于该标签的执行,当test的值为true时,会将包含的SQL片段拼接到其所在的SQL语句中。
本例实现的功能是:查询出满足用户提交查询条件的所有学生。用户提交的查询条件可以包含一个姓名的模糊查询,同时还可以包含一个年龄的下限。当然,用户在提交表单时可能两个条件均做出了设定,也可能两个条件均不做设定,也可以只做其中一项设定。
这引发的问题是,查询条件不确定,查询条件依赖于用户提交的内容。此时,就可使用动态SQL语句,根据用户提交内容对将要执行的SQL进行拼接。
首先,修改Mapper映射文件,添加id为“selectStudentsIf”的select标签,具体代码如下:
<!--if-->
<select id="selectStudentsIf" resultType="Student">
select * from student where 1=1
<if test="name != null and name != ''">
and name like '%' #{name} '%'
</if>
<if test="age > 0">
and age > #{age}
</if>
</select>
其次,修改接口实现类IStudentImpl中的selectStudentsIf如下:
@Override
public List<Student> selectStudentsIf(Student student) {
List<Student> students = null;
students = sqlSession.selectList("StudentTest.selectStudentsIf",student);
this.close();
return students;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsIf如下:
@Test
public void TestSelectStudentsIf(){
Student student = new Student("李",23,0);
List<Student> students = studentDao.selectStudentsIf(student);
System.out.println(students);
}
当前student数据表中的数据如下所示:
当运行测试方法后,能够在控制台输出name中包括“李”且age大于23的所有student。
4、where标签
if标签中存在一个比较麻烦的地方:需要在where后手工添加1=1子句。因为,若where后的所有if条件均为false,而where后又没有1=1子句,则SQL中就只会剩下一个空的where,SQL出错。所以,在where后,需要添加永远为真的子句1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。
使用where标签,在有查询条件时,可以自动添加上where子句;没有查询条件时,不会添加where子句。需要注意的是,第一个if标签中的SQL片段,可以不包含and。不过,写上and也不错,系统会自动将多余的and去掉。但其他if标签中SQL片段的and是必须写上的,否则SQL语句会拼接出错。
首先,修改Mapper映射文件,添加id为“selectStudentsWhere”的select标签,SQL语句的功能与测试if标签的SQL语句功能一致。具体代码如下:
<!--where-->
<select id="selectStudentsWhere" resultType="Student">
select * from student
<where>
<if test="name != null and name != ''">
and name like '%' #{name} '%'
</if>
<if test="age > 0">
and age > #{age}
</if>
</where>
</select>
其次,修改接口实现类IStudentImpl中的selectStudentsWhere如下:
@Override
public List<Student> selectStudentsWhere(Student student) {
List<Student> students = null;
students = sqlSession.selectList("StudentTest.selectStudentsWhere",student);
this.close();
return students;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsWhere如下:
@Test
public void TestSelectStudentsWhere(){
Student student = new Student("",23,0);
List<Student> students = studentDao.selectStudentsWhere(student);
System.out.println(students);
}
当前student数据表中的数据如下所示:
当运行测试方法后,能够在控制台输出age大于23的所有student。
5、choose标签
choose标签中只可以包含when、otherwise标签,可以包含多个when和一个otherwise。它们联合使用,完成Java中的开关语句switch/case功能。
本例完成的需求是:若姓名不为空,按照姓名查询;若姓名为空,则按照年龄查询;若没有查询条件,则没有查询结果。
首先,修改Mapper映射文件,添加id为“selectStudentsWhere”的select标签,具体代码如下:
<!--choose-->
<select id="selectStudentsChoose" resultType="Student">
select * from student
<where>
<choose>
<when test="name != null and name != ''">
and name like '%' #{name} '%'
</when>
<when test="age > 0">
and age < #{age}
</when>
<otherwise>
and 1 != 1
</otherwise>
</choose>
</where>
</select>
对于choose标签,其会从第一个when标签开始逐个向后进行条件判断。若出现when标签中的test属性值为true的情况,则直接结束choose标签,不再向后进行判断查找。若所有的when标签的test结果判断均为false,则最后会执行otherwise标签。
其次,修改接口实现类IStudentImpl中的selectStudentsChoose如下:
@Override
public List<Student> selectStudentsChoose(Student student) {
List<Student> students = null;
students = sqlSession.selectList("StudentTest.selectStudentsChoose",student);
this.close();
return students;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsChoose如下:
@Test
public void TestSelectStudentsChoose(){
Student student = new Student("",0,0);
List<Student> students = studentDao.selectStudentsChoose(student);
System.out.println(students);
}
当前student数据表中的数据如下所示:
当运行测试方法后,由于name为空,年龄为0,因此前两个when标签的test属性均为false,将执行otherwise标签中的条件,即无查询结果数据。
6、foreach标签
以下三组均实现需求:查询出id为1和3的学生信息。
6.1 遍历数组
首先,修改Mapper映射文件,添加id为“selectStudentsForeachArray”的select标签,具体代码如下:
<!--foreach array-->
<select id="selectStudentsForeachArray" resultType="Student">
select * from student
<if test="array != null and array.length > 0">
where id in
<foreach collection="array" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
在使用foereach标签时,需要注意的是:
- collection表示要遍历的集合类型。其中array表示数组,list表示集合。
- open、close、separator为对遍历内容的SQL拼接。
- 动态SQL的判断中使用的都是OGNL表达式。OGNL表达式中的数组使用array表示,数组长度使用array.length表示。
其次,修改接口实现类IStudentImpl中的selectStudentsForeachArray如下:
@Override
public List<Student> selectStudentsForeachArray(Object[] studentIds) {
List<Student> students = null;
students = sqlSession.selectList("StudentTest.selectStudentsForeachArray",studentIds);
this.close();
return students;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsForeachArray如下:
@Test
public void TestSelectStudentsForeachArray(){
Object[] studentIds = new Object[]{1,3};
List<Student> students = studentDao.selectStudentsForeachArray(studentIds);
System.out.println(students);
}
当前student数据表中的数据如下所示:
当运行测试方法后,则可以在控制台看到id为1和3的学生信息。
6.2 遍历泛型为基本类型的List
首先,修改Mapper映射文件,添加id为“selectStudentsForeachList”的select标签,具体代码如下:
<!--foreach List<Integer>-->
<select id="selectStudentsForeachList" resultType="Student">
select * from student
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="myid" separator=",">
#{myid}
</foreach>
</if>
</select>
其次,修改接口实现类IStudentImpl中的selectStudentsForeachList如下:
@Override
public List<Student> selectStudentsForeachList(List<Integer> studentIds) {
List<Student> students = null;
students = sqlSession.selectList("StudentTest.selectStudentsForeachList",studentIds);
this.close();
return students;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsForeachList如下:
@Test
public void TestSelectStudentsForeachList(){
List<Integer> studentIds = new ArrayList<>();
studentIds.add(1);
studentIds.add(3);
List<Student> students = studentDao.selectStudentsForeachList(studentIds);
System.out.println(students);
}
当前student数据表中的数据如下所示:
当运行测试方法后,则可以在控制台看到id为1和3的学生信息。
6.3 遍历泛型为自定义类型的List
首先,修改Mapper映射文件,添加id为“selectStudentsForeachList2”的select标签,这里需要注意的是当前遍历对象类型时List中的泛型,即是Student对象。具体代码如下:
<!--foreach List<Student>-->
<select id="selectStudentsForeachList2" resultType="Student">
select * from student
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}
</foreach>
</if>
</select>
其次,修改接口实现类IStudentImpl中的selectStudentsForeachList2如下:
@Override
public List<Student> selectStudentsForeachList2(List<Student> students) {
List<Student> studentRes = null;
studentRes = sqlSession.selectList("StudentTest.selectStudentsForeachList2",students);
this.close();
return studentRes;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsForeachList2如下:
@Test
public void TestSelectStudentsForeachList2(){
List<Student> students = new ArrayList<>();
Student student1 = new Student();
student1.setId(1);
Student student2 = new Student();
student2.setId(3);
students.add(student1);
students.add(student2);
List<Student> studentRes = studentDao.selectStudentsForeachList2(students);
System.out.println(studentRes);
}
当前student数据表中的数据如下所示:
当运行测试方法后,则可以在控制台看到id为1和3的学生信息。
7、sql标签
sql标签用于定义SQL片段,以便其他SQL标签复用。而其他标签使用该SQL片段,需要使用include子标签。该sql标签可以定义SQL语句中的任何部分,所以include子标签可以放在动态SQL的任何位置。
本例将在6.3小节的基础上进行修改,将SQL语句中的“select * from student”部分抽取出来形成独立的SQL片段,以便其他SQL复用。
首先,修改Mapper映射文件,添加id为“selectStudentHead”的sql标签,具体代码如下:
<!--sql-->
<!--定义sql片段-->
<sql id="selectStudentHead">
select * from student
</sql>
其次,修改Mapper映射文件,添加id为“selectStudentsBySQLFragment”的select标签,具体代码如下:
<select id="selectStudentsBySQLFragment" resultType="Student">
<!-- 使用sql片段 -->
<include refid="selectStudentHead" />
<if test="list != null and list.size > 0">
where id in
<foreach collection="list" open="(" close=")" item="stu" separator=",">
#{stu.id}
</foreach>
</if>
</select>
然后,修改接口实现类IStudentImpl中的selectStudentsBySQLFragment如下:
@Override
public List<Student> selectStudentsBySQLFragment(List<Student> students) {
List<Student> studentRes = null;
studentRes = sqlSession.selectList("StudentTest.selectStudentsBySQLFragment",students);
this.close();
return studentRes;
}
最后,修改测试类StudentTest,添加测试方法TestSelectStudentsBySQLFragment如下:
@Test
public void TestSelectStudentsBySQLFragment(){
List<Student> students = new ArrayList<>();
Student student1 = new Student();
student1.setId(1);
Student student2 = new Student();
student2.setId(3);
students.add(student1);
students.add(student2);
List<Student> studentRes = studentDao.selectStudentsBySQLFragment(students);
System.out.println(studentRes);
}
当前student数据表中的数据如下所示:
当运行测试方法后,则可以在控制台看到id为1和3的学生信息。