select
导学
本节将掌握MyBatis中最常用的查询映射元素。你将学会如何配置select语句,理解parameterType与resultType的协作方式,区分selectOne与selectList的适用场景,并能够编写单条件查询、多条件查询以及返回Map、List和单个对象的完整示例。
定义
select是SQL映射文件中用于定义查询语句的元素。在JDBC原始写法中,开发者需要手动拼接SQL、设置参数、执行查询、遍历ResultSet并逐列提取数据封装成对象。select元素将这一整套流程声明化:只需写好SQL,MyBatis自动完成参数绑定、结果集遍历与对象映射,彻底消除样板代码。
适用位置与核心属性
select元素书写在映射文件的<mapper>根标签内部,与insert、update、delete并列。
| 属性 | 是否必填 | 说明 |
|---|---|---|
id | 是 | 映射语句的唯一标识,与Mapper接口方法名对应 |
parameterType | 否 | 传入参数的全限定类名或别名,MyBatis可自动推断 |
resultType | 否* | 期望返回结果映射到的类名或别名(与resultMap二选一) |
resultMap | 否* | 引用外部resultMap定义的ID(与resultType二选一) |
flushCache | 否 | 执行后是否清空本地缓存与二级缓存,默认false |
useCache | 否 | 执行结果是否放入二级缓存,默认true |
timeout | 否 | 驱动等待数据库返回的最大秒数,默认未设置 |
fetchSize | 否 | 驱动每次从数据库获取的行数,用于优化批量查询 |
statementType | 否 | 使用STATEMENT、PREPARED或CALLABLE,默认PREPARED |
resultOrdered | 否 | 是否按嵌套结果顺序返回,用于嵌套结果映射,默认false |
*
resultType与resultMap必须且只能配置一个。
核心原理
MyBatis执行select语句时,会沿着一条清晰的管道完成从Java方法调用到结果对象返回的全过程。
- Java Mapper接口方法:开发者调用
StudentMapper.findById(1)。 - SqlSession:从
SqlSessionFactory获取会话,作为MyBatis对外暴露的核心操作门面。 - Executor:根据配置选择简单执行器、复用执行器或缓存执行器,负责调度SQL执行。
- StatementHandler:封装JDBC的
Statement操作,包括参数设置和结果集处理。 - JDBC PreparedStatement:真正与MySQL 5.7通信的预编译语句对象。
- ResultSetHandler:将数据库返回的二维结果集,通过反射映射为Java对象的一维列表。
- 返回:根据Mapper接口的返回值类型,决定返回
List<T>、T或Map<K, V>。
完整示例
场景说明
乐途公司技术部组织了一次MyBatis技术培训,学员为大翔、白歌、小崔、黄俪、李眉。培训结束后,需要查询学生管理系统中的学员信息。本节演示四种典型查询场景:按ID查询单个学员、按专业查询学员列表、多条件组合查询、以及将结果直接封装为Map。
操作前的数据库表结构及初始数据
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
age INT,
major VARCHAR(20),
score DECIMAL(5,2)
);
INSERT INTO student (name, age, major, score) VALUES
('大翔', 22, '计算机科学', 95.5),
('白歌', 21, '软件工程', 88.0),
('小崔', 20, '计算机科学', 92.0),
('黄俪', 21, '信息安全', 90.5),
('李眉', 22, '软件工程', 87.0);
当前数据状态:
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 2 | 白歌 | 21 | 软件工程 | 88.00 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
| 4 | 黄俪 | 21 | 信息安全 | 90.50 |
| 5 | 李眉 | 22 | 软件工程 | 87.00 |
完整的映射文件片段与Java代码
POJO类
package com.flywing.entity;
public class Student {
private Integer id;
private String name;
private Integer age;
private String major;
private Double score;
// Getter与Setter省略
}
Mapper接口
package com.flywing.mapper;
import com.flywing.entity.Student;
import java.util.List;
import java.util.Map;
public interface StudentMapper {
// 按ID查询单个对象
Student findById(Integer id);
// 按专业查询列表
List<Student> findByMajor(String major);
// 多条件查询
List<Student> findByCondition(Map<String, Object> params);
// 返回Map(key为id,value为对象)
@MapKey("id")
Map<Integer, Student> findAllAsMap();
}
映射文件 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="com.flywing.mapper.StudentMapper">
<!-- 场景一:按ID查询单个对象 -->
<select id="findById" parameterType="java.lang.Integer" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE id = #{id}
</select>
<!-- 场景二:按专业查询列表 -->
<select id="findByMajor" parameterType="java.lang.String" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE major = #{major}
</select>
<!-- 场景三:多条件查询 -->
<select id="findByCondition" parameterType="java.util.HashMap" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
WHERE major = #{major}
AND age >= #{minAge}
</select>
<!-- 场景四:返回Map -->
<select id="findAllAsMap" resultType="com.flywing.entity.Student">
SELECT id, name, age, major, score
FROM student
</select>
</mapper>
测试代码
package com.flywing.test;
import com.flywing.entity.Student;
import com.flywing.mapper.StudentMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SelectTest {
public static void main(String[] args) throws Exception {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
// 场景一:按ID查询
Student s1 = mapper.findById(1);
System.out.println("按ID查询:" + s1.getName() + ",专业:" + s1.getMajor());
// 场景二:按专业查询列表
List<Student> list = mapper.findByMajor("计算机科学");
System.out.println("计算机科学专业共 " + list.size() + " 人");
for (Student s : list) {
System.out.println(" - " + s.getName() + ",分数:" + s.getScore());
}
// 场景三:多条件查询
Map<String, Object> params = new HashMap<>();
params.put("major", "软件工程");
params.put("minAge", 21);
List<Student> list2 = mapper.findByCondition(params);
System.out.println("软件工程且年龄≥21的学员:");
for (Student s : list2) {
System.out.println(" - " + s.getName() + ",年龄:" + s.getAge());
}
// 场景四:返回Map
Map<Integer, Student> map = mapper.findAllAsMap();
System.out.println("Map方式获取id=3的学员:" + map.get(3).getName());
session.close();
}
}
实际执行结果
控制台SQL输出
[DEBUG] com.flywing.mapper.StudentMapper.findById - ==> Preparing: SELECT id, name, age, major, score FROM student WHERE id = ?
[DEBUG] com.flywing.mapper.StudentMapper.findById - ==> Parameters: 1(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findById - <== Total: 1
按ID查询:大翔,专业:计算机科学
[DEBUG] com.flywing.mapper.StudentMapper.findByMajor - ==> Preparing: SELECT id, name, age, major, score FROM student WHERE major = ?
[DEBUG] com.flywing.mapper.StudentMapper.findByMajor - ==> Parameters: 计算机科学(String)
[DEBUG] com.flywing.mapper.StudentMapper.findByMajor - <== Total: 2
计算机科学专业共 2 人
- 大翔,分数:95.5
- 小崔,分数:92.0
[DEBUG] com.flywing.mapper.StudentMapper.findByCondition - ==> Preparing: SELECT id, name, age, major, score FROM student WHERE major = ? AND age >= ?
[DEBUG] com.flywing.mapper.StudentMapper.findByCondition - ==> Parameters: 软件工程(String), 21(Integer)
[DEBUG] com.flywing.mapper.StudentMapper.findByCondition - <== Total: 2
软件工程且年龄≥21的学员:
- 白歌,年龄:21
- 李眉,年龄:22
[DEBUG] com.flywing.mapper.StudentMapper.findAllAsMap - ==> Preparing: SELECT id, name, age, major, score FROM student
[DEBUG] com.flywing.mapper.StudentMapper.findAllAsMap - <== Total: 5
Map方式获取id=3的学员:小崔
查询结果集表格(以findByMajor为例)
| id | name | age | major | score |
|---|---|---|---|---|
| 1 | 大翔 | 22 | 计算机科学 | 95.50 |
| 3 | 小崔 | 20 | 计算机科学 | 92.00 |
分析
- selectOne与selectList的自动识别:MyBatis根据Mapper接口方法的返回值类型自动判断。
findById返回Student,内部调用selectOne;findByMajor返回List<Student>,内部调用selectList。若selectOne查询到多条记录,MyBatis会抛出TooManyResultsException。 - 参数绑定:
#{id}、#{major}等占位符由MyBatis自动解析并调用PreparedStatement.setXxx()完成预编译绑定,防止SQL注入。 - 返回Map:
@MapKey("id")注解指定用哪一列作为Map的键,resultType指定Map的值类型。若省略@MapKey,则返回Map<String, Object>形式的结果。
易错场景/常见误区
| 误区 | 正解 |
|---|---|
resultType与resultMap同时写在一个select中 | 二者只能选其一;简单映射用resultType,复杂映射用resultMap |
| 接口返回单个对象,但SQL实际返回多条记录 | MyBatis会抛出TooManyResultsException;应确保查询条件能唯一确定记录,或改返回List |
parameterType写全限定名导致XML冗长 | 可在mybatis-config.xml中配置<typeAlias>,如<typeAlias alias="Student" type="com.flywing.entity.Student"/> |
查询条件为null时,期望查出所有记录 | #{param}绑定null后SQL为WHERE major = null,不会匹配任何行;应使用动态SQL的if标签处理(第04章详述) |
认为select的flushCache默认会清空缓存 | 默认false,查询不会清空缓存;只有insert/update/delete默认才会 |
面试考点
Q1:MyBatis的selectOne和selectList有什么区别?
selectOne期望返回单条记录,内部调用selectList后取第一条;若结果集大于1条则抛出TooManyResultsException。selectList返回包含零条或多条记录的列表。开发者通常不需要直接调用它们,MyBatis根据Mapper接口的返回值类型自动选择。
Q2:resultType和resultMap能否同时使用?
不能。二者互斥。
resultType适用于列名与属性名完全一致的简单场景,MyBatis自动映射;resultMap适用于列名与属性名不一致、需要类型转换或复杂嵌套映射的场景,由开发者显式定义映射规则。
Q3:为什么MyBatis查询默认使用PREPARED statement类型?
PREPARED对应JDBC的PreparedStatement,支持预编译和参数绑定,能有效防止SQL注入并提升重复执行效率。STATEMENT对应普通Statement,直接拼接SQL,存在注入风险且每次执行都需要数据库重新编译SQL。
Q4:Mapper接口方法返回Map<String, Object>时,需要注意什么?
需要在接口方法上添加
@MapKey注解指定作为键的列名,且select的resultType应指定为值对象的类型。若希望返回Map<String, Object>(即每一行都是一个列名到值的映射),则接口方法返回类型写Map<String, Object>,且resultType="java.util.HashMap"。
小结
select是MyBatis映射文件中使用频率最高的元素。通过id与Mapper接口方法绑定,parameterType接收外部参数,resultType或resultMap决定结果形态。MyBatis内部通过Executor→StatementHandler→ResultSetHandler的管道完成SQL执行与对象映射,开发者只需关注SQL本身。掌握单条件、多条件、返回List、返回Map四种典型写法,即可覆盖日常80%的查询需求。
下一章引子
查询结果如何映射到Java对象?当数据库列名与实体属性名完全一致时,resultType可以自动完成;但一旦遇到下划线命名与驼峰命名不一致的情况,自动映射就会失效。下一节将深入讲解resultType的适用边界与自动映射原理,并演示基本类型、POJO和Map三种返回形式。