10.MyBatis参数处理
10.1 项目信息
- 模块名:mybatis-007-param
- 数据库表:t_student
- 表结构:
- id: 主键
- name: 姓名
- age: 年龄
- height: 身高
- sex: 性别
- birth: 出生日期
- sql文件:
create table t_student
(
id bigint auto_increment
primary key,
name varchar(255) null,
age int null,
height double null,
birth date null,
sex char null
);
INSERT INTO `t_student` VALUES (1, '张三', 20, 1.81, '1980-10-11', '男');
INSERT INTO `t_student` VALUES (2, '李四', 18, 1.61, '1988-10-11', '女');
INSERT INTO `t_student` VALUES (3, '赵六', 20, 1.81, '2022-09-01', '男');
INSERT INTO `t_student` VALUES (4, '赵六', 20, 1.81, '2022-09-01', '男');
INSERT INTO `t_student` VALUES (5, '张飞', 50, 10, '2022-09-01', '女');
INSERT INTO `t_student` VALUES (6, '张飞', 50, 10, '2022-09-01', '女');
10.2 POJO类
package com.example.mybatis.pojo;
import java.time.LocalDate;
public class Student {
private Long id;
private String name;
private Integer age;
private double height;
private Character sex;
private LocalDate birth;
// constructor
// setter and getter
// toString
}
10.3 参数处理方式
10.3.1 单个简单类型参数
适用场景:查询条件只有一个简单类型参数时使用
特点:
- MyBatis可以自动推断参数类型,无需显式指定
- #{…}中的内容可以随意写
- 完整写法(可省略):
<select id="selectByName" resultType="student" parameterType="java.lang.String">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
示例:
// Mapper接口
public interface StudentMapper {
List<Student> selectByName(String name);
Student selectById(Long id);
List<Student> selectByBirth(LocalDate birth);
List<Student> selectBySex(Character sex);
}
// XML配置
<select id="selectByName" resultType="Student">
select * from t_student where name = #{name}
</select>
// 测试代码
@Test
public void testSelectByName() {
List<Student> students = mapper.selectByName("张三");
students.forEach(System.out::println);
}
10.3.2 Map参数
适用场景:需要传递多个参数但没有对应POJO类时
特点:
- 手动封装Map集合,将每个条件以key-value形式存放
- 通过#{map集合的key}来取值
示例:
// Mapper接口
List<Student> selectByParamMap(Map<String,Object> paramMap);
// 测试代码
@Test
public void testSelectByParamMap(){
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("nameKey", "张三");
paramMap.put("ageKey", 20);
List<Student> students = mapper.selectByParamMap(paramMap);
students.forEach(System.out::println);
}
// XML配置
<select id="selectByParamMap" resultType="student">
select * from t_student where name = #{nameKey} and age = #{ageKey}
</select>
10.3.3 实体类参数
适用场景:参数与实体类属性匹配时
特点:
- #{…}中写的是属性名
- 属性名本质上是set/get方法名去掉set/get之后的名字
示例:
// Mapper接口
int insert(Student student);
// 测试代码
@Test
public void testInsert(){
Student student = new Student();
student.setName("李四");
student.setAge(30);
student.setHeight(1.70);
student.setSex('男');
student.setBirth(LocalDate.now());
int count = mapper.insert(student);
System.out.println("插入了" + count + "条记录");
}
// XML配置
<insert id="insert">
insert into t_student values(null,#{name},#{age},#{height},#{birth},#{sex})
</insert>
10.3.4 多参数
适用场景:方法有多个参数但没有使用@Param注解时
特点:
- MyBatis底层会创建map集合存储参数
- 参数命名规则:
- arg0/param1:第一个参数
- arg1/param2:第二个参数
- 以此类推…
示例:
// Mapper接口
List<Student> selectByNameAndSex(String name, Character sex);
// 测试代码
@Test
public void testSelectByNameAndSex(){
List<Student> students = mapper.selectByNameAndSex("张三", '男');
students.forEach(System.out::println);
}
// XML配置
<select id="selectByNameAndSex" resultType="student">
select * from t_student where name = #{arg0} and sex = #{arg1}
</select>
10.3.5 @Param注解(命名参数)
适用场景:方法有多个参数且需要明确参数名时
特点:
- 增强代码可读性
- @Param中的值就是Map集合的key
- 可以自定义参数名称
示例:
// Mapper接口
List<Student> selectByNameAndAge(@Param("name") String name, @Param("age") int age);
// 测试代码
@Test
public void testSelectByNameAndAge(){
List<Student> students = mapper.selectByNameAndAge("张三", 20);
students.forEach(System.out::println);
}
// XML配置
<select id="selectByNameAndAge" resultType="student">
select * from t_student where name = #{name} and age = #{age}
</select>
10.4 总结对比
参数类型 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
单个简单类型 | 单一条件查询 | 简单直接 | 只能处理一个参数 |
Map参数 | 多条件查询且无对应POJO | 灵活,可自定义key | 需要手动封装Map |
实体类参数 | 参数与实体属性匹配 | 自动映射属性 | 需要创建实体类 |
多参数 | 方法有多个参数 | 无需额外封装 | 参数名不直观(arg0/param1) |
@Param注解 | 需要明确参数名 | 可读性好 | 需要添加注解 |
10.5 常见问题
-
#{…}和${…}的区别
- #{…}:预编译处理,防止SQL注入
- ${…}:字符串替换,需要手动处理引号
-
参数类型自动推断
- MyBatis可以自动推断大多数简单类型的参数
- 复杂类型建议显式指定javaType和jdbcType
-
参数命名冲突
- 避免在Map参数和@Param注解中使用相同的key
- 建议使用有意义的参数名
11. MyBatis查询语句专题
模块名:mybatis-008-select
打包方式:jar
引入依赖:mysql驱动依赖、mybatis依赖、logback依赖、junit依赖。
引入配置文件:jdbc.properties、mybatis-config.xml、logback.xml
创建pojo类:Car
创建Mapper接口:CarMapper
创建Mapper接口对应的映射文件:com/powernode/mybatis/mapper/CarMapper.xml
创建单元测试:CarMapperTest
拷贝工具类:SqlSessionUtil
11.1 返回Car
当查询的结果有对应的实体类,并且查询结果只有一条时,可以直接返回Car对象。
使用场景
- 根据主键查询单条记录
- 查询结果保证只有一条记录的情况
- 查询结果有对应的实体类映射
- 需要获取完整对象信息的场景
- 需要直接操作对象属性的场景
实际应用
- 用户详情页根据用户ID查询用户信息
- 商品详情页根据商品ID查询商品信息
- 订单详情页根据订单ID查询订单信息
示例代码
// CarMapper接口
public interface CarMapper {
/**
* 根据id主键查询:结果最多只有一条
* @param id 主键id
* @return Car对象
* @throws TooManyResultsException 当查询结果多于一条时抛出
*/
Car selectById(Long id);
}
// Mapper XML
<!--
使用resultType="Car"指定返回类型为Car实体类
列名通过as或resultMap转换为Java属性名
-->
<select id="selectById" resultType="Car">
select
id,
car_num carNum, -- 数据库列名car_num映射到Java属性carNum
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
where id = #{id} -- 使用#{}防止SQL注入
</select>
// 测试代码
@Test
public void testSelectById(){
// 获取SqlSession和Mapper接口实例
SqlSession sqlSession = SqlSessionUtil.openSession();
try {
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
// 执行查询并处理结果
Car car = mapper.selectById(166L);
// 验证结果
Assert.assertNotNull("查询结果不应为null", car);
System.out.println(car);
} finally {
// 确保关闭SqlSession
sqlSession.close();
}
}
性能优化建议
- 对于频繁查询的字段,考虑添加数据库索引
- 大数据量查询建议使用分页
- 考虑使用二级缓存提高查询性能
常见错误及解决方案
- 错误:查询结果为空返回null
- 解决方案:使用Optional包装返回值或添加空值检查
- 错误:查询结果多于一条抛出TooManyResultsException
- 解决方案:确保查询条件唯一或使用List接收结果
- 错误:列名与属性名不匹配导致映射失败
- 解决方案:使用resultMap或开启驼峰命名映射
执行结果示例
Car{id=166, carNum='京A154345', brand='宝马', guidePrice=20.0, produceTime='2024-01-01', carType='豪华电车'}
注意事项
- 查询结果是一条记录时,也可以使用List集合接收
- 如果查询结果为空,返回null
- 如果查询结果多于一条,会抛出TooManyResultsException异常
- 建议在查询条件中确保结果唯一性
11.2 返回List
当查询的记录条数是多条时,必须使用集合接收。如果使用单个实体类接收会出现异常。
使用场景
- 查询多条记录
- 分页查询
- 条件查询可能返回多条记录
- 需要批量处理数据的场景
- 需要遍历处理每条记录的场景
实际应用
- 商品列表页查询所有商品
- 用户管理页查询所有用户
- 订单列表页查询符合条件的订单
性能考虑
- 大数据量查询建议使用分页
- 考虑使用延迟加载优化性能
示例代码
// CarMapper接口
/**
* 查询所有的Car
* @return Car对象列表
*/
List<Car> selectAll();
// Mapper XML
<select id="selectAll" resultType="Car">
select
id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
</select>
// 测试代码
@Test
public void testSelectAll(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
cars.forEach(car -> System.out.println(car));
}
执行结果示例
[
Car{id=33, carNum='103', brand='奔驰E300L', guidePrice=50.30, produceTime=2020-10-01, carType='燃油车'},
Car{id=34, carNum='102', brand='比亚迪汉', guidePrice=30.23, produceTime=2018-09-10, carType='电车'},
...
]
注意事项
- 如果返回多条记录,采用单个实体类接收会抛出TooManyResultsException异常
- 查询结果为空时返回空集合,而不是null
- 建议在查询条件中明确限制返回条数,避免查询过多数据
11.3 返回Map
当返回的数据没有合适的实体类对应时,可以采用Map集合接收。字段名做key,字段值做value。
使用场景
- 查询结果没有对应的实体类
- 只需要部分字段
- 动态查询结果
示例代码
// CarMapper接口
/**
* 通过id查询一条记录,返回Map集合
* @param id 主键id
* @return Map<String, Object> 字段名和值的映射
*/
Map<String, Object> selectByIdRetMap(Long id);
// Mapper XML
<select id="selectByIdRetMap" resultType="map">
select
id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
where id = #{id}
</select>
// 测试代码
@Test
public void testSelectByIdRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<String,Object> car = mapper.selectByIdRetMap(167L);
System.out.println(car);
}
执行结果示例
{
car_num=1203,
id=167,
guide_price=20.00,
produce_time=2010-12-03,
brand=奔驰GLC,
car_type=电车
}
注意事项
- 如果返回多条记录,采用单个Map接收会抛出TooManyResultsException异常
- Map的key是数据库列名或别名
- Map的value类型会自动转换,但要注意类型转换可能带来的问题
11.4 返回List
查询结果条数大于等于1条数据时,可以返回一个存储Map集合的List集合。
使用场景
- 查询多条记录且没有对应的实体类
- 动态查询结果
- 需要灵活处理查询结果
示例代码
// CarMapper接口
/**
* 查询所有的Car,返回一个List集合。List集合中存储的是Map集合。
* @return List<Map<String,Object>> 结果集
*/
List<Map<String,Object>> selectAllRetListMap();
// Mapper XML
<select id="selectAllRetListMap" resultType="map">
select
id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
</select>
// 测试代码
@Test
public void testSelectAllRetListMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Map<String,Object>> cars = mapper.selectAllRetListMap();
System.out.println(cars);
}
执行结果示例
[
{
carType=燃油车,
carNum=103,
guidePrice=50.30,
produceTime=2020-10-01,
id=33,
brand=奔驰E300L
},
{
carType=电车,
carNum=102,
guidePrice=30.23,
produceTime=2018-09-10,
id=34,
brand=比亚迪汉
},
...
]
注意事项
- 查询结果为空时返回空集合
- 每个Map代表一条记录
- 适合处理动态查询结果
11.5 返回Map<String,Map>
使用Car的id作为key,方便后续取出对应的Map集合。
使用场景
- 需要根据主键快速查找记录
- 批量查询后需要按主键组织数据
- 需要建立主键到记录的映射关系
示例代码
// CarMapper接口
/**
* 获取所有的Car,返回一个Map集合。
* Map集合的key是Car的id。
* Map集合的value是对应Car。
* @return Map<Long,Map<String,Object>> 结果集
*/
@MapKey("id")
Map<Long,Map<String,Object>> selectAllRetMap();
// Mapper XML
<select id="selectAllRetMap" resultType="map">
select
id,
car_num carNum,
brand,
guide_price guidePrice,
produce_time produceTime,
car_type carType
from t_car
</select>
// 测试代码
@Test
public void testSelectAllRetMap(){
CarMapper mapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Map<Long,Map<String,Object>> cars = mapper.selectAllRetMap();
System.out.println(cars);
}
执行结果示例
{
64={
carType=燃油车,
carNum=133,
guidePrice=50.30,
produceTime=2020-01-10,
id=64,
brand=丰田霸道
},
66={
carType=燃油车,
carNum=133,
guidePrice=50.30,
produceTime=2020-01-10,
id=66,
brand=丰田霸道
},
...
}
注意事项
- 必须使用@MapKey注解指定作为key的字段
- 查询结果为空时返回空Map
- 适合需要根据主键快速查找的场景
11.6 resultMap结果映射
当查询结果的列名和Java对象的属性名不对应时,有三种解决方案:
- 使用as给列起别名
- 使用resultMap进行结果映射
- 开启驼峰命名自动映射
使用resultMap进行结果映射
使用场景
- 复杂的对象关系映射
- 需要自定义类型转换
- 需要处理复杂的嵌套对象
示例代码
// CarMapper接口
/**
* 查询所有Car,使用resultMap进行结果映射
* @return List<Car> 结果集
*/
List<Car> selectAllByResultMap();
// Mapper XML
<!--
resultMap:
id:这个结果映射的标识,作为select标签的resultMap属性的值。
type:结果集要映射的类。可以使用别名。
-->
<resultMap id="carResultMap" type="car">
<!--对象的唯一标识,官方解释是:为了提高mybatis的性能。建议写上。-->
<id property="id" column="id"/>
<result property="carNum" column="car_num"/>
<!--当属性名和数据库列名一致时,可以省略。但建议都写上。-->
<!--javaType用来指定属性类型。jdbcType用来指定列类型。一般可以省略。-->
<result property="brand" column="brand" javaType="string" jdbcType="VARCHAR"/>
<result property="guidePrice" column="guide_price"/>
<result property="produceTime" column="produce_time"/>
<result property="carType" column="car_type"/>
</resultMap>
<select id="selectAllByResultMap" resultMap="carResultMap">
select *
from t_car
</select>
// 测试代码
@Test
public void testSelectAllByResultMap(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByResultMap();
System.out.println(cars);
}
开启驼峰命名自动映射
使用场景
- 属性名遵循Java命名规范
Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。 - 数据库列名遵循SQL命名规范
SQL命名规范:全部小写,单词之间采用下划线分割。 - 需要简化映射配置
配置方式
在mybatis-config.xml中配置:
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
命名规范对应关系
实体类中的属性名 | 数据库表的列名 |
---|---|
carNum | car_num |
carType | car_type |
produceTime | produce_time |
示例代码
// CarMapper接口
/**
* 查询所有Car,启用驼峰命名自动映射
* @return List<Car> 结果集
*/
List<Car> selectAllByMapUnderscoreToCamelCase();
// Mapper XML
<select id="selectAllByMapUnderscoreToCamelCase" resultType="Car">
select *
from t_car
</select>
// 测试代码
@Test
public void testSelectAllByMapUnderscoreToCamelCase(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
List<Car> cars = carMapper.selectAllByMapUnderscoreToCamelCase();
System.out.println(cars);
}
11.7 返回总记录条数
使用场景
- 分页查询时获取总记录数
- 统计查询
- 数据报表
示例代码
// CarMapper接口
/**
* 获取总记录条数
* @return 总记录数
*/
Long selectTotal();
// Mapper XML
<!--long是别名,可参考mybatis开发手册。-->
<select id="selectTotal" resultType="long">
select count(*)
from t_car
</select>
// 测试代码
@Test
public void testSelectTotal(){
CarMapper carMapper = SqlSessionUtil.openSession().getMapper(CarMapper.class);
Long total = carMapper.selectTotal();
System.out.println(total);
}
执行结果示例
总记录数:14
注意事项
- 建议使用count(1)或count(*)而不是count(列名)
- 注意count的返回值类型,建议使用Long而不是Integer
- 对于大数据量表,count操作可能较慢,需要考虑优化