MyBatis的第四天学习笔记下
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操作可能较慢,需要考虑优化
 
