前言
在查询过程中根据不同的条件和逻辑动态构建查询语句,以实现灵活、个性化的数据查询操作。它的设计目的是提供一种方便而高效的方式来处理各种不同的查询需求。
用过 JDBC 的都知道吧,再动态查询时候需要根据不同的查询拼接 SQL 语句,还要确保不能漏了必要的空格,还要注意省掉列名列表最后的逗号。
Mybatis 就很好的解决了这些问题。
一、创建数据库表
-- 创建学生信息表
create table student_info(
stu_id int auto_increment primary key ,
stu_name varchar(20),
stu_age int,
c_id int
);
insert into student_info(stu_name, stu_age, c_id) value ('student2',22,2),('stu1',12,1),('stu2',12,1),('stu3',12,1);
二、新建一个 Maven 项目
1、导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<!-- Mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<!-- Lombok 依赖 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- junit 依赖:测试代码-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
2、配置 mybatis 核心配置文件
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 设置 mybatis 在控制台使用日志输出 sql 语句 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="edu.nf.ch05.entity"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/psm?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&timeZone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
<settings> <!-- 设置 mybatis 在控制台使用日志输出 sql 语句 --> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>这个设置是把执行过后的 SQL 语句的结果已日志的方式输出来,不再需要我们在控制台去输出。
3、在项目中新建一个包 entity,在包下新建 Student 实体类
@Data
public class Student {
/**
* 学生编号
*/
private Integer stuId;
/**
* 学生姓名
*/
private String stuName;
/**
* 学生年龄
*/
private Integer stuAge;
private Integer cid;
}
4、在项目中新建一个包 Util ,在包下新建一个 MybatisUtil 工具类
public class MyBatisUtil {
// 声明 SqlSessionFactory
private static SqlSessionFactory sqlSessionFactory;
// 在静态代码块中初始化 SqlSessionFactory 工厂
static {
try {
// 通过查找 mybatis 的核心配置文件,先创建一个用于读取 xml 文件的输入流
InputStream resource = Resources.getResourceAsStream("mybatis.xml");
// 将输入流传给创建的 SqlSessionFactoryBuilder 进行解析,并初始化整个 SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
public static SqlSession getSqlSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
}
5、在项目中新建一个包 dao,在包下新建 StuDao 接口
public interface StuDao {
/**
* 多条件查询
* 多个条件可以使用实体类封装也可以使用 Map 集合封装
* @param map
* @return
*/
List<Student> listStudents(Map<String, Object> map);
/**
* 多条件动态查询
* @param map
* @return
*/
List<Student> listStudents2(Map<String, Object> map);
/**
* 根据年龄范围查询
* @param age
* @return
*/
List<Student> listStudents3(List<Integer> age);
/**
* 根据年龄范围查询,参数是一个数组
* @param age
* @return
*/
List<Student> listStudents4(Integer[] age);
/**
* 动态更新
* @param student
*/
void updateStu(Student student);
/**
* 批量添加
* @param students
*/
void batchAdd(List<Student> students);
/**
* 批量删除
* @param list
*/
void batchDelete(List<Integer> list);
}
在 dao 接口中定义了很多个方法,每个方法都定义了相应的类型,使用多种类型去动态操作 SQL 语句,这都是比较常用的类型。
6、在 dao 包下新建一个包 impl,在包下新建 StuDaoImpl 实现类
public class StuDaoImpl implements StuDao {
@Override
public List<Student> listStudents(Map<String, Object> map) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
return sqlSession.getMapper(StuDao.class).listStudents(map);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public List<Student> listStudents2(Map<String, Object> map) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
return sqlSession.getMapper(StuDao.class).listStudents2(map);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public List<Student> listStudents3(List<Integer> age) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
return sqlSession.getMapper(StuDao.class).listStudents3(age);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public List<Student> listStudents4(Integer[] age) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
return sqlSession.getMapper(StuDao.class).listStudents4(age);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public void updateStu(Student student) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
sqlSession.getMapper(StuDao.class).updateStu(student);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public void batchAdd(List<Student> students) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
sqlSession.getMapper(StuDao.class).batchAdd(students);
} catch (RuntimeException e) {
throw e;
}
}
@Override
public void batchDelete(List<Integer> list) {
try (SqlSession sqlSession = MybatisUtil.getSqlSession(true)) {
sqlSession.getMapper(StuDao.class).batchDelete(list);
} catch (RuntimeException e) {
throw e;
}
}
}
实现了所有的 dao 接口的方法,代码逻辑都一样,只不过是返回的类型不一致。
7、在 resources 配置文件中新建一个文件夹 mapper,在文件夹里新建 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="edu.nf.ch05.dao.StuDao">
<!-- 定义 resultMap 映射查询结果集 -->
<resultMap id="stuMap" type="student">
<id property="stuId" column="stu_id"/>
<result property="stuName" column="stu_name"/>
<result property="stuAge" column="stu_age"/>
</resultMap>
<!-- 动态条件查询 (多个条件),使用 <where> 标签和 <if> 标签来实现 -->
<!-- 如果先写 age ,mybatis 会自动删掉 age -->
<select id="listStudents" parameterType="map" resultMap="stuMap">
select stu_id,stu_name,stu_age from student_info
<where>
<if test="uname != nul and uname != ''">
stu_name = #{ uname }
</if>
<if test="age != null">
and stu_age = #{ age }
</if>
</where>
</select>
<!-- 动态条件选择(多选一),使用 <choose> 标签 -->
<select id="listStudents2" parameterType="map" resultMap="stuMap">
select stu_id,stu_name,stu_age from student_info
<choose>
<when test="uname != null and uname !='' ">
where stu_name = #{ uname }
</when>
<when test="age != null ">
where stu_age = #{ age }
</when>
<otherwise>
order by stu_id desc
</otherwise>
</choose>
</select>
<!-- 使用 <foreach> 标签循环参数,适用于 or 或者 in 字句查询语句
当参数是 list 或者数组时,parameterType 的值为 collection,ognl 表达式中使用 list
-->
<select id="listStudents3" resultMap="stuMap" parameterType="list">
select stu_id,stu_name,stu_age from student_info
<where>
stu_age in
<if test="list != null">
<foreach collection="list" item="age" open="(" separator="," close=")">
#{ age }
</foreach>
</if>
</where>
</select>
<!-- 使用 <foreach> 标签循环参数,适用于 or 或者 in 字句查询语句 -->
<!-- 注意:当参数是数组的时候,parameterType 的值为 collection
ognl 表达式中使用 array
-->
<select id="listStudents4" resultMap="stuMap" parameterType="collection">
select stu_id,stu_name,stu_age from student_info
<where>
stu_age in
<if test="array != null">
<foreach collection="array" item="age" open="(" separator="," close=")">
#{ age }
</foreach>
</if>
</where>
</select>
<!-- 动态更新,使用 <set> 标签实现动态更新字段 -->
<update id="updateStu" parameterType="student">
update student_info
<set>
<if test="stuName != null and stuName != ''">
stu_name = #{ stuName },
</if>
<if test="stuAge != null ">
stu_age = #{ stuAge }
</if>
</set>
where stu_id = #{ stuId }
</update>
<!-- 使用 <foreach> 标签实现批量添加 -->
<insert id="batchAdd" parameterType="collection">
insert into student_info(stu_name,stu_age) values
<foreach collection="list" item="stu" separator=",">
(#{ stu.stuName } , #{ stu.stuAge })
</foreach>
</insert>
<!-- 批量删除,与 int 范围查询类似 -->
<delete id="batchDelete" parameterType="collection">
delete from student_info where stu_id in
<if test="collection != null">
<foreach collection="list" item="id" open="(" separator="," close=")">
#{ id }
</foreach>
</if>
</delete>
</mapper>
在上面的 SQL 语句中用到了很多的标签where、if、when、choose、otherwise、foreach、set,它们是什么意思呢?
1) <where> </where> 标签
<where>:<where> 标签可以用于将条件语句包装在 WHERE 子句内。它会自动在第一个条件前添加 AND 关键字,并忽略条件语句中的其他无效或空条件。
2)<if> </if>
<if>:<if> 标签用于根据条件判断是否包含某个语句片段。如果条件满足,则该语句片段会被包含在最终的查询语句中,否则会被忽略。
3) <when> </when>
<when>:<when> 标签与 <choose> 结合使用,用于指定选择块中的条件分支。与 <if> 类似,它根据条件判断是否包含某个语句片段,但是 <when> 只能在 <choose> 内部使用。
4) <choose> </choose>
<choose>:<choose> 标签提供了多个条件分支供选择,类似于 Java 中的 switch 语句。其中的 <when> 标签用于定义条件分支,可以根据条件判断选择其中的一个分支。
5) <otherwise> </otherwise>
<otherwise>:<otherwise> 标签是 <choose> 块中的可选项,用于指定当条件分支都不满足时的默认语句片段。
6) <foreach> </foreach>
<foreach>:<foreach> 标签用于循环遍历集合或数组,并在查询语句中动态生成多个重复的语句片段。它可以将集合或数组中的元素作为参数进行替换或遍历。
其中:
foreach 有 三个属性 open="(" separator="," close=")"
我们在写 SQL 语句的时候,当根据条件查询时 where stu_age in (11,12),我们 in 后面的条件如果有多个的话是不是有逗号隔开的,而且参数外面是有括号括起来的,open = “(”就是左边开始的括号,separator = “,” 是中间的括号,close = “)” 是右边的半个括号。
7) <set> </set>
<set>:<set> 标签用于在更新语句中设置需要更新的字段和字段值。它可以根据不同的条件和逻辑来动态生成更新语句,实现部分字段的更新。
以上标签都是 MyBatis 中用于构建动态查询的常用标签,它们可以根据不同的条件和逻辑来生成不同的查询语句,从而实现灵活的数据操作。在使用这些标签时,您可以根据具体的需求选择合适的标签组合和条件判断逻辑,以实现个性化的查询操作。
8、单元测试
1) 新建一个 StuDao 测试类(测试,多条件查询,使用 map 封装查询数据)
@Test
public void testStudents(){
StuDaoImpl stuDao = new StuDaoImpl();
HashMap<String, Object> map = new HashMap<>();
map.put("uname","stu2");
map.put("age",12);
List<Student> students = stuDao.listStudents2(map);
// students.forEach( student -> {
// System.out.println(student.getStuName());
// });
}
注意:这里不需要在控制台输出,因为在核心配置文件已经开启了日志输出。
测试结果
当我们根据多条件查询,是匹配我们输入的条件去查询的,如果不根据条件查询就是查询全部的数据,并且输出方式是日志输出,日志输出的好处就是,它会输出 SQL 语句,并且输出的结果会有列名行数,总数,非常的好用,可以很详细的查看到查询出来的结果。最下面的是控制台输出,大家可以对比一下。
2) 根据年龄范围查询,参数是一个数组
@Test
public void testStudents4(){
StuDaoImpl stuDao = new StuDaoImpl();
Integer[] num = new Integer[]{12,22};
stuDao.listStudents4(num)
.forEach( student -> System.out.println(
student.getStuName()
));
}
测试结果
数组的和 map 集合的是一样的,都是根据多条件去查询数据
3) 根据年龄范围查询
@Test
public void testStudents3(){
StuDaoImpl stuDao = new StuDaoImpl();
List<Integer> list = Arrays.asList(12,22);
stuDao.listStudents3(list)
.forEach( student -> System.out.println(
student.getStuName()
));
}
测试结果
这个测试结果和上面使用数组的是一样的,但是数组是多条件查询, 这个整数集合,是范围查询,具体的实现在上面的 StudentMapper.xml 中。
4)动态更新(update)
@Test
public void testUpdate(){
StuDaoImpl stuDao = new StuDaoImpl();
Student student = new Student();
student.setStuId(8);
student.setStuName("aaa");
student.setStuAge(23);
stuDao.updateStu(student);
}
测试结果
这个修改操作,是根据 id 去修改的,所以前面的 id 是和数据库的一样的,不是修改 id 为 8 ,是修改 8 这个 id 的数据。
5)批量添加(insert)
@Test
public void testbatAdd(){
Student student = new Student();
student.setStuName("student1");
student.setStuAge(19);
Student student1 = new Student();
student1.setStuName("student2");
student1.setStuAge(22);
List<Student> list = Arrays.asList(student,student1);
StuDaoImpl stuDao = new StuDaoImpl();
stuDao.batchAdd(list);
System.out.println(list);
}
测试结果
是不是很方便,有批量数据的时候只需要封装成多个对象,就可以实现批量添加了。
6)批量删除(delete)
@Test
public void testbatchDelete(){
List<Integer> list = Arrays.asList(12,13);
new StuDaoImpl().batchDelete(list);
}
测试结果
批量删除和添加是一样的,这里不单只可以是一个集合,还可以是一个数组。
结语
这上面所有的代码,最重要的是 StudentMapper.xml 里的 SQL 语句里面的每一个标签,上面我都有解释,在代码中也有注释。和 JDBC 的一对比真的是 Mybatis 的好用,终于感受到了框架的好处,人家已经做好了这个功能,你只需要知道怎么用就可以了。这里面的标签也不多,也就 7 个,不要背代码,代码是敲出来的,敲多了就会有记忆。