5 MyBatis的注解开发
约 7136 字大约 24 分钟
2025-12-21
学习目标
- 掌握基于注解的单表增删改查
- 熟悉基于注解的一对一关联查询
- 熟悉基于注解的一对多关联查询
- 熟悉基于注解的多对多关联查询
前面几章介绍了MyBatis的基本用法、动态 SQL、关联映射和缓存机制等知识,所有的配置都是基于XML文件完成的,但在实际开发中,大量的XML配置文件的编写是非常繁琐的,为此,MyBatis 提供了更加简便的基于注解的配置方式。
5.1 基于注解的单表增删改查
除了XML的映射方式,MyBatis还支持通过注解实现POJO对象和数据表之间的关联映射。使用注解时,一般将SQL语句直接写在接口上。与XML的映射方式相比,基于注解的映射方式相对简单且不会增加系统的开销。MyBatis 提供了@Select、@Insert、@Update、@Delete 和@Param等用于增删改查,以及传递参数的常用注解。
5.1.1 @Select注解
@Select注解用于映射查询语句,其作用等同于XML配置文件中的<select>元素。下面通过一个案例演示@Select注解的使用,该案例要求根据员工的id查找员工信息:
(1) 在heima_ssm_book 数据库中创建名称为 ch5_tb_worker的数据表,同时预先插入三条测试数据:
use heima_ssm_book;
create table ch5_tb_worker
(
id int primary key auto_increment,
name varchar(32),
age int,
sex varchar(8),
worker_id int unique
);
insert into ch5_tb_worker(name, age, sex, worker_id)
values ('张三', 32, '女', 1001);
insert into ch5_tb_worker(name, age, sex, worker_id)
values ('李四', 29, '男', 1002);
insert into ch5_tb_worker(name, age, sex, worker_id)
values ('王五', 26, '男', 1003);完成上述操作后,ch5_tb_worker表中的数据如图所示。

(2) 在项目包下创建持久化类src/main/java/io/weew12/github/pojo/Worker.java,在Worker类中定义员工id、员工姓名、年龄、性别、工号等属性,以及属性对应的getter/setter方法。Worker类具体代码如文件5-1所示。
package io.weew12.github.pojo;
public class Worker {
/**
* 员工id
*/
private Integer id;
/**
* 员工姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
/**
* 性别
*/
private String sex;
/**
* 工号
*/
private String workerId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getWorkerId() {
return workerId;
}
public void setWorkerId(String workerId) {
this.workerId = workerId;
}
@Override
public String toString() {
return "Worker{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", workerId='" + workerId + '\'' +
'}';
}
}(3) 在项目目录下创建src/main/java/io/weew12/github/mapper/WorkerMapper.java接口,用于编写@Select 注解映射的 select 查询方法。WorkerMapper 接口具体代码如文件5-2 所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Worker;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface WorkerMapper {
@Select("select * from heima_ssm_book.ch5_tb_worker where id = #{id} ")
// 后文讲解
@Results(
@Result(column = "worker_id", property = "workerId")
)
Worker selectWorker(int id);
}在文件5-2中,@Select注解的参数是一个查询语句,当程序调用@Select注解标注的selectWorker()方法时,@Select注解中映射的查询语句将被执行。
(4) 在核心配置文件 mybatis-config.xml 中的 <mappers> 元素下引入接口,将 WorkerMapper.java 接口加载到核心配置文件中,具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
......
<mappers>
<package name="io.weew12.github.mapper"/>
</mappers>
</configuration>(5) 为了验证上述配置,在测试类 MyBatisTest 中编写测试方法 findWorkerByIdTest(),具体代码如下:
@Test
public void findWorkerByIdTest() {
// 获取sqlSession
SqlSession sqlSession = MybatisUtils.getSession();
// 使用WorkerMapper 查询员工id为1的员工信息
WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);
Worker worker = workerMapper.selectWorker(1);
// 输出
System.out.println(worker);
// 关闭会话
sqlSession.close();
}执行MyBatisTest测试类的findWorkerByIdTest()方法后,控制台的输出结果如图所示。

5.1.2 @Insert注解
@Insert 注解用于映射插入语句,其作用等同于XML配置文件中的<insert>元素。下面通过一个案例演示@Insert注解的使用,该案例要求实现员工信息的插入:
(1) 在WorkerMapper 接口中添加向ch5_tb_worker数据表插入数据的方法insertWorker(),并在方法上添加@Insert 注解,具体代码如下:
@Insert("insert into heima_ssm_book.ch5_tb_worker(name, age, sex, worker_id) values(#{name} , #{age}, #{sex} , #{workerId} )")
int insertWorker(Worker worker);在上述代码中,@Insert注解的参数是一条插入语句,当程序调用@Insert注解标注的insertWorker()方法时,@Insert 注解中映射的插入语句将被执行。
(2) 为了验证上述配置,在测试类 MyBatisTest 中编写测试方法 insertWorkerTest(),具体代码如下:
@Test
public void insertWorkerTest() {
// 获取sqlSession
SqlSession sqlSession = MybatisUtils.getSession();
// 获取mapper代理对象
WorkerMapper workerMapper = sqlSession.getMapper(WorkerMapper.class);
// 创建新对象
Worker worker = new Worker();
worker.setId(4);
worker.setName("赵六");
worker.setAge(37);
worker.setSex("女");
worker.setWorkerId("1004");
// 插入信息
int i = workerMapper.insertWorker(worker);
if (i > 0) {
System.out.println("成功插入" + i + "条数据");
} else {
System.out.println("插入数据失败");
}
System.out.println(worker);
sqlSession.commit();
sqlSession.close();
}执行MyBatisTest测试类的insertWorkerTest()方法后,控制台的输出结果如图所示。
完成上述操作后,数据库ch5_tb_worker表中的数据如图所示。


5.1.3 @Update注解
@Update注解用于映射更新语句,其作用等同于XML配置文件中的<update>元素。下面通过一个案例演示@Update注解的使用,该案例要求实现员工信息的修改:
(1) 在WorkerMapper接口中添加更新ch5_tb_worker表中数据的方法,并在方法上添加@Update注解,具体代码如下:
@Update("update heima_ssm_book.ch5_tb_worker set name=#{name}, age=#{age} where id=#{id}")
int updateWorker(Worker worker);在上述代码中,@Update注解的参数是一条更新语句,当程序调用@Update注解标注的updateWorker()方法时,@Update注解中映射的更新语句将被执行。
(2) 为了验证上述配置,在测试类MyBatisTest中编写测试方法updateWorkerTest(),具体代码如下:
@Test
public void updateWorkerTest() {
// 1. 通过工具类生成SqlSession对象
SqlSession session = MybatisUtils.getSession();
Worker worker = new Worker();
worker.setId(4);
worker.setName("李华");
worker.setAge(28);
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2. 更新员工信息
int result = mapper.updateWorker(worker);
if (result > 0) {
System.out.println("成功更新" + result + "条数据");
} else {
System.out.println("更新数据失败");
}
System.out.println(worker.toString());
session.commit(); // 3. 关闭 sqlSession
session.close();
}完成上述操作后,数据库ch5_tb_worker表中的数据如图所示。


5.1.4 @Delete注解
@Delete注解用于映射删除语句,其作用等同于XML配置文件中的<delete>元素。下面通过一个案例演示@Delete注解的使用,该案例要求实现员工信息的删除,案例具体实现步骤如下。
(1) 在WorkerMapper接口中添加删除数据库中数据的方法,并在方法上添加@Delete 注解,具体代码如下:
@Delete("delete from heima_ssm_book.ch5_tb_worker where id = #{id}")
int deleteWorker(int id);在上述代码中,@Delete注解的参数是一条删除语句,当程序调用@Delete注解标注的deleteWorker()方法时,@Delete注解中映射的删除语句将被执行。
(2) 为了验证上述配置,在测试类MyBatisTest中编写测试方法deleteWorkerTest(),具体代码如下:
@Test
public void deleteWorkerTest() {
// 1. 通过工具类生成 SqlSession 对象
SqlSession session = MybatisUtils.getSession();
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2. 删除员工信息
int result = mapper.deleteWorker(4);
if (result > 0) {
System.out.println("成功删除" + result + "条数据");
} else {
System.out.println("删除数据失败");
}
session.commit(); // 3. 关闭sqlSession
session.close();
}执行MyBatisTest测试类的deleteWorkerTest()方法后,控制台的输出结果如图所示。

完成上述操作后,数据库tb_worker表中的数据如图所示。

5.1.5 @Param注解
@Param注解的功能是指定SQL语句中的参数,通常用于SQL语句中参数比较多的情况。下面通过一个案例演示@Param注解的使用,该案例要求根据员工的id和姓名查询员工信息:
(1) 在WorkerMapper接口中添加多条件查询的方法,具体代码如下:
@Select("select * from heima_ssm_book.ch5_tb_worker where id=#{param01} and name = #{param02}")
Worker selectWorkerByIdAndName(@Param("param01") int id, @Param("param02") String name);在上述代码中,使用@Param注解分别将id和name两个参数命名为param01和param02,id和name这两个参数命名需要与@Select注解中的#中的名称对应,@Param注解用于将参数id和name的值映射到#{param01}和#{param02}中。
(2) 为了验证上述配置,在测试类MyBatisTest中编写测试方法selectWorkerByIdAndNameTest(),具体代码如下:
@Test
public void selectWorkerByIdAndNameTest() {
// 1. 通过工具类生成SqlSession 对象
SqlSession session = MybatisUtils.getSession();
WorkerMapper mapper = session.getMapper(WorkerMapper.class);
// 2. 查询id为3、姓名为王五的员工信息
Worker worker = mapper.selectWorkerByIdAndName(3, "王五");
System.out.println(worker.toString());
session.close();
}执行MyBatisTest测试类的selectWorkerByIdAndNameTest()方法后,控制台的输出结果如图所示。

5.2 基于注解的关联查询
使用MyBatis的注解配置,除了可以实现单表的增删改查操作外,还可以实现多表的关联查询,包括一对一查询、一对多查询和多对多查询。MyBatis提供了@Results、@Result、@One和@Many等注解来实现多表之间的关联查询。
5.2.1 一对一查询
MyBatis中使用@One注解实现数据表的一对一关联查询,其作用等同于XML配置文件中的<association>元素。
下面以 ch5_tb_idcard 和 ch5_tb_person 数据表为例,详细讲解基于@One 注解实现ch5_tb_idcard 和ch5_tb_person 数据表之间的一对一关联查询。
数据表创建:
use heima_ssm_book;
create table ch5_tb_idcard
(
id int primary key auto_increment,
code varchar(18)
);
insert into ch5_tb_idcard(code)
values ('152221198711020624');
insert into ch5_tb_idcard(code)
values ('152201199008150317');
create table ch5_tb_person
(
id int primary key auto_increment,
name varchar(32),
age int,
sex varchar(8),
card_id int unique,
foreign key (card_id) references ch5_tb_idcard (id)
);
insert into ch5_tb_person(name, age, sex, card_id)
values ('Rose', 22, '女', 1);
insert into ch5_tb_person(name, age, sex, card_id)
values ('Jack', 23, '男', 2);

(1) IdCard类和Person类作为持久类。
package io.weew12.github.pojo;
public class IdCard {
/**
* id
*/
private Integer id;
/**
* code
*/
private String code;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
@Override
public String toString() {
return "IdCard{" +
"id=" + id +
", code='" + code + '\'' +
'}';
}
}package io.weew12.github.pojo;
public class Person {
/**
* id
*/
private Integer id;
/**
* name
*/
private String name;
/**
* age
*/
private Integer age;
/**
* sex
*/
private String sex;
/**
* card
*/
private IdCard idCard;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public IdCard getIdCard() {
return idCard;
}
public void setIdCard(IdCard idCard) {
this.idCard = idCard;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", idCard=" + idCard +
'}';
}
}(2) 在项目包下创建src/main/java/io/weew12/github/mapper/IdCardMapper.java 接口,在该接口中编写 selectIdCardById()方法,通过id查询人员对应的身份证信息。IdCardMapper接口具体代码如文件5-3所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.IdCard;
import org.apache.ibatis.annotations.Select;
public interface IdCardMapper {
@Select("select * from heima_ssm_book.ch5_tb_idcard where id = #{id}")
IdCard selectIdCardById(Integer id);
}在文件5-3中,@Select注解的参数是一个查询语句,当程序调用@Select注解标注的selectIdCardById()方法时,@Select注解中映射的查询语句将被执行。
在项目包下创建 src/main/java/io/weew12/github/mapper/PersonMapper.java接口,在该接口中编写selectPersonById()方法,通过id查询人员信息。PersonMapper接口具体代码如文件5-4所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Person;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface PersonMapper {
@Select("select * from heima_ssm_book.ch5_tb_person where id = #{id}")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "name", column = "name"),
@Result(property = "age", column = "age"),
@Result(property = "idCard", column = "card_id",
one = @One(select = "io.weew12.github.mapper.IdCardMapper.selectIdCardById"))
})
Person selectPersonById(Integer id);
}在文件5-4中:
- 使用
@Select注解映射根据id查询Person对象的SQL语句,当程序调用@Select注解标注的selectPersonById()方法时,@Select注解中映射的查询语句将被执行; - 使用
@Results注解映射查询结果,在@Results注解中,可以包含多个@Result注解,一个@Result注解完成实体类中一个属性和数据表中一个字段的映射。
Person 对象中的基本属性可以自动完成结果映射,而关联的属性card 需要手动完成映射。因此,在@Results注解中使用了一个@Result 注解来映射属性card的关联结果。在@Result 注解中,有column、property和one三个属性,它们的含义分别如下。
property属性用于指定关联的是实体类属性,这里为card。column属性用于指定关联的数据库表中的字段,这里为card_id。one属性用于指定数据表之间属于哪种关联关系,@One注解表明数据表ch5_tb_idcard和ch5_tb_person之间是一对一的关联关系。
在@One注解中,select属性用于指定关联属性card的值是通过执行IdCardMapper接口定义的 selectIdCardById()方法获得的。
(3) 在核心配置文件 mybatis-config.xml 中的 <mappers> 元素下引入 IdCardMapper 和 PersonMapper接口,具体引入代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
......
<mappers>
<package name="io.weew12.github.mapper"/>
</mappers>
</configuration>(4) 为了验证上述配置,在测试类MyBatisTest中编写测试方法selectPersonByIdTest(),具体代码如下:
@Test
public void selectPersonByIdTest() {
SqlSession sqlSession = MybatisUtils.getSession();
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
Person selectPersonById = personMapper.selectPersonById(2);
System.out.println(selectPersonById);
sqlSession.close();
}执行MyBatisTest测试类的 selectPersonByIdTest()方法后,控制台的输出结果如图所示。由图可知,控制台输出了id为2的人员信息和人员的身份证信息。这表明程序在查询出 Person对象信息的同时,其关联的IdCard对象的信息也被查询出来了。

5.2.2 一对多查询
MyBatis使用@Many 注解实现数据表的一对多关联查询,@Many注解的作用等同于XML配置文件中的<collection>元素。
下面以ch5_tb_user和ch5_tb_orders 数据表为例,详细讲解基于@Many注解配置实现ch5_tb_user和ch5_tb_orders数据表之间的一对多关联查询,具体步骤如下。
创建数据表:
use heima_ssm_book;
create table ch5_tb_user
(
id int(32) primary key auto_increment,
username varchar(32),
address varchar(256)
);
insert into ch5_tb_user
values (1, '小明', '北京');
insert into ch5_tb_user
values (2, '张华', '上海');
insert into ch5_tb_user
values (3, '李华', '上海');
create table ch5_tb_orders
(
id int(32) primary key auto_increment,
number varchar(32) not null,
user_id int(32) not null,
foreign key (user_id) references ch5_tb_user (id)
);
insert into ch5_tb_orders
values (1, '1000011', 1);
insert into ch5_tb_orders
values (2, '1000012', 2);
insert into ch5_tb_orders
values (3, '1000013', 3);

(1) 创建Users类和Orders类作为持久类。
package io.weew12.github.pojo;
import java.util.List;
public class Users {
/**
* id
*/
private Integer id;
/**
* username
*/
private String username;
/**
* address
*/
private String address;
/**
* order list
*/
private List<Orders> ordersList;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public List<Orders> getOrdersList() {
return ordersList;
}
public void setOrdersList(List<Orders> ordersList) {
this.ordersList = ordersList;
}
@Override
public String toString() {
return "Users{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
", ordersList=" + ordersList +
'}';
}
}package io.weew12.github.pojo;
public class Orders {
/**
* id
*/
private Integer id;
/**
* number
*/
private String number;
/**
* userid
*/
private Integer userId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", number='" + number + '\'' +
", userId=" + userId +
'}';
}
}(2) 在项目包下创建src/main/java/io/weew12/github/mapper/OrdersMapper.java接口,在该接口中编写 selectOrdersByUserId()方法,通过user_id查询用户对应的订单信息。OrdersMapper接口具体代码如文件5-5所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Orders;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface OrdersMapper {
@Select("select * from heima_ssm_book.ch5_tb_orders where user_id = #{userId} ")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "number", column = "number"),
@Result(property = "userId", column = "user_id")
})
List<Orders> selectOrdersByUserId(Integer userId);
}在文件5-5中:
- 代码使用
@Select注解映射根据user_id查询Orders对象的SQL语句,当程序调用@Select注解标注的selectOrdersByUserId()方法时,@Select注解中映射的查询语句将被执行; - 代码使用
@Results注解映射查询结果,在@Results注解中,使用@Result注解完成Orders实体类中属性和数据表中字段的映射。
在项目包下创建src/main/java/io/weew12/github/mapper/UsersMapper.java接口,在该接口中编写 selectUserById()方法,通过id查询用户信息。UsersMapper接口具体代码如文件5-6所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Users;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface UsersMapper {
@Select("select * from heima_ssm_book.ch5_tb_user where id = #{userid} ")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "username", column = "username"),
@Result(property = "address", column = "address"),
@Result(property = "ordersList", column = "id",
many = @Many(select = "io.weew12.github.mapper.OrdersMapper.selectOrdersByUserId"))
})
Users selectUserById(Integer userid);
}在文件5-6中:
- 使用
@Select注解映射根据id查询Users对象的SQL语句,当程序调用@Select注解标注的selectUserById()方法时,@Select注解中映射的查询语句将被执行; - 使用
@Results注解映射查询结果,在@Results注解中,使用4个@Result注解完成Users实体类中属性和数据表中字段的映射。其中@Many注解表明数据表chtb_user和tb_orders之间是一对多的关联关系。在@Many注解中,select属性用于指定关联属性ordersList的值,是通过执行com.itheima.dao包中OrdersMapper接口定义的selectOrdersByUserId()方法获得的。
(3) 在核心配置文件 mybatis-config.xml中的<mappers>元素下引入 UsersMapper 和 OrdersMapper 接口,将这两个接口加载到核心配置文件中,具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
......
<mappers>
<package name="io.weew12.github.mapper"/>
</mappers>
</configuration>(4) 为了验证上述配置,在测试类MyBatisTest中编写测试方法selectUserByIdTest(),具体代码如下:
@Test
public void selectUserByIdTest() {
SqlSession sqlSession = MybatisUtils.getSession();
UsersMapper usersMapper = sqlSession.getMapper(UsersMapper.class);
Users selectUserById = usersMapper.selectUserById(1);
System.out.println(selectUserById);
sqlSession.close();
}执行MyBatisTest测试类的selectUserByIdTest()方法后,控制台的输出结果如图所示。
由图可知,控制台输出了id为1的用户信息和用户订单信息。这表明在查询出Users对象信息的同时,其关联的Orders对象的信息也被查询出来了。

5.2.3 多对多查询
在数据库中,表与表之间的多对多关联关系通常使用一个中间表来维护,以4.4节中使用的订单表ch4_tb_orders 和商品表ch4_tb_product为例,这两个表之间的关联关系使用了一个中间表ch4_tb_ordersitem来维护,订单表ch4_tb_orders 和商品表ch4_tb_product 都与中间表ch4_tb_ordersitem形成了一对多关联关系,即中间表ch4_tb_ordersitem将订单表ch4_tb_orders和商品表ch4_tb_product拆分成了两个一对多的关联关系。
下面基于上述案例讲解基于注解的多对多查询:
创建所需数据表:
ch5_tb_orders 基于 5.2.1 的表,无需创建,此处只要创建剩下的两个表ch5_tb_product和ch5_tb_orders_item,建表语句如下所示:
use heima_ssm_book;
create table ch5_tb_product
(
id int(32) primary key auto_increment,
name varchar(32),
price double
);
insert into ch5_tb_product
values (1, 'Java 基础入门', 45.0);
insert into ch5_tb_product
values (2, 'Java Web 程序开发入门', 138.5);
insert into ch5_tb_product
values (3, 'SSM 框架整合实战', 150.0);
create table ch5_tb_orders_item
(
id int(32) primary key auto_increment,
orders_id int(32),
product_id int(32),
foreign key (orders_id) references ch5_tb_orders (id),
foreign key (product_id) references ch5_tb_product (id)
);
insert into ch5_tb_orders_item
values (1, 1, 1);
insert into ch5_tb_orders_item
values (2, 2, 2);
insert into ch5_tb_orders_item
values (3, 3, 3);
insert into ch5_tb_orders_item
values (4, 1, 2);三个表的数据:
订单表

产品表

中间表(订单产品表)

创建实体类:
1.创建 Product实体类
package io.weew12.github.pojo;
public class Product {
/**
* 商品id
*/
private Integer id;
/**
* 商品名称
*/
private String name;
/**
* 商品单价
*/
private Double price;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
", price=" + price +
'}';
}
}2.修改订单实体类Orders增加关联商品的字段
package io.weew12.github.pojo;
import java.util.List;
public class Orders {
/**
* id
*/
private Integer id;
/**
* number
*/
private String number;
/**
* userid
*/
private Integer userId;
/**
* 订单关联的商品
*/
private List<Product> productList;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public List<Product> getProductList() {
return productList;
}
public void setProductList(List<Product> productList) {
this.productList = productList;
}
@Override
public String toString() {
return "Orders{" +
"id=" + id +
", number='" + number + '\'' +
", userId=" + userId +
", productList=" + productList +
'}';
}
}(1) 在项目包下创建src/main/java/io/weew12/github/mapper/ProductMapper.java接口,在该接口编写 selectProductByOrdersId()方法,通过orders_id查询用户对应的订单信息。ProductMapper接口具体代码如文件所示。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Product;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface ProductMapper {
@Select("select * from heima_ssm_book.ch5_tb_product where id in " +
"(select product_id from heima_ssm_book.ch5_tb_orders_item where orders_id = #{ordersId} )")
List<Product> selectProductByOrdersId(Integer ordersId);
}在文件中,使用@Select注解映射根据orders_id查询其关联对象Product对象的SQL语句,当程序调用@Select注解标注的selectProductByOrdersId()方法时,@Select注解中映射的查询语句将被执行。
在项目包下的OrdersMapper接口中添加selectOrdersById()方法,该方法用于通过id查询订单信息。selectOrdersById()方法具体代码如下:
package io.weew12.github.mapper;
import io.weew12.github.pojo.Orders;
import org.apache.ibatis.annotations.Many;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
import java.util.List;
public interface OrdersMapper {
@Select("select * from heima_ssm_book.ch5_tb_orders where id = #{id} ")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "number", column = "number"),
@Result(property = "userId", column = "user_id"),
@Result(property = "productList", column = "id",
many = @Many(select = "io.weew12.github.mapper.ProductMapper.selectProductByOrdersId"))
})
Orders selectOrdersById(Integer id);
}在上述代码中:
- 代码使用
@Select注解映射根据id查询Orders对象的SQL语句,当程序调用@Select注解标注的selectOrdersById()方法时,@Select注解中映射的查询语句将被执行; - 使用
@Results注解映射查询结果,在@Results注解中,使用 4 个@Result注解完成Orders实体类中属性和数据表中字段的映射。@Result注解中的property属性用于指定关联属性,column属性用于指定关联的数据库表中的字段。代码的@Many注解中,select属性用于指定关联属性productList的值是通过执行包中ProductMapper接口定义的selectProductByOrdersId()方法获得的。
(2) 在核心配置文件mybatis-config.xml中的<mappers>元素下引入ProductMapper接口,将这个接口加载到核心配置文件中,具体代码如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
......
<mappers>
<package name="io.weew12.github.mapper"/>
</mappers>
</configuration>(3) 为了验证上述配置,在测试类MyBatisTest中编写测试方法selectOrdersByIdTest(),具体代码如下:
@Test
public void selectOrdersByIdTest() {
SqlSession sqlSession = MybatisUtils.getSession();
OrdersMapper ordersMapper = sqlSession.getMapper(OrdersMapper.class);
Orders selectOrdersById = ordersMapper.selectOrdersById(1);
System.out.println(selectOrdersById);
sqlSession.close();
}执行MyBatisTest测试类的 selectOrdersByIdTest()方法,控制台的输出结果如图所示。
由图可知,控制台输出了id为 1 的订单信息及订单商品信息。这表明在查询出Orders对象信息的同时,其关联的Product 对象的信息也被查询出来了。

5.3 案例:基于MyBatis注解的学生管理程序
现有一个学生表ch5_s_student和一个班级表ch5_c_class,其中,班级表ch5_c_class和学生表ch5_s_student 是一对多的关系。学生表ch5_s_student和班级表ch5_c_class 如表5-1和表5-2所示。

创建数据表:
use heima_ssm_book;
create table ch5_c_class
(
id int primary key auto_increment,
class_name varchar(30)
);
create table ch5_s_student
(
id int primary key auto_increment,
name varchar(50),
age int,
cid int,
foreign key (cid) references ch5_c_class (id)
);
insert into ch5_c_class
values (1, '一班'),
(2, '二班');
insert into ch5_s_student
values (1, '张三', 18, 1),
(2, '李四', 18, 2),
(3, '王五', 19, 2),
(4, '赵六', 20, 1);

创建实体类:
package io.weew12.github.pojo;
import java.util.List;
public class MyClass {
/**
* id
*/
private Integer id;
/**
* 班级名
*/
private String className;
/**
* 学生列表
*/
private List<Student> studentList;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
@Override
public String toString() {
return "Class{" +
"id=" + id +
", className='" + className + '\'' +
", studentList=" + studentList +
'}';
}
}package io.weew12.github.pojo;
public class Student {
/**
* id
*/
private Integer id;
/**
* name
*/
private String name;
/**
* age
*/
private Integer age;
/**
* 班级id
*/
private Integer cid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", cid=" + cid +
'}';
}
}创建 mapper:
src/main/java/io/weew12/github/mapper/MyClassMapper.java
package io.weew12.github.mapper;
public class MyClassMapper {
}src/main/java/io/weew12/github/mapper/StudentMapper.java
package io.weew12.github.mapper;
public interface StudentMapper {
}请使用MyBatis注解完成以下要求。
(1) MyBatis注解实现查询操作。
查询id为2的学生的信息。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Student;
import org.apache.ibatis.annotations.Select;
public interface StudentMapper {
@Select("select * from heima_ssm_book.ch5_s_student where id = #{id} ")
Student findStudentById(Integer id);
} @Test
public void findStudentByIdTest() {
SqlSession sqlSession = MybatisUtils.getSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.findStudentById(2);
System.out.println(student);
sqlSession.close();
}
(2) MyBatis注解实现修改操作。
将id为4的学生姓名修改为李雷,年龄修改为21。
修改前:

package io.weew12.github.mapper;
import io.weew12.github.pojo.Student;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface StudentMapper {
@Update("update heima_ssm_book.ch5_s_student set name = #{name} where id = #{id} ")
int updateStudent(Student student);
} @Test
public void updateStudentTest() {
SqlSession sqlSession = MybatisUtils.getSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setId(4);
student.setName("李雷");
int i = studentMapper.updateStudent(student);
if (i > 0) {
System.out.println("更新" + i + "条数据");
} else {
System.out.println("更新失败");
}
sqlSession.commit();
sqlSession.close();
}更新后:

(3) MyBatis注解实现一对多查询。查询出二班所有学生的信息。
package io.weew12.github.mapper;
import io.weew12.github.pojo.Student;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface StudentMapper {
@Select("select * from heima_ssm_book.ch5_s_student where cid = #{cid} ")
List<Student> findStudentsByClassId(Integer cid);
}package io.weew12.github.mapper;
import io.weew12.github.pojo.MyClass;
import org.apache.ibatis.annotations.One;
import org.apache.ibatis.annotations.Result;
import org.apache.ibatis.annotations.Results;
import org.apache.ibatis.annotations.Select;
public interface MyClassMapper {
@Select("select * from heima_ssm_book.ch5_c_class where class_name = #{className} ")
@Results({
@Result(id = true, property = "id", column = "id"),
@Result(property = "className", column = "class_name"),
@Result(property = "studentList", column = "id",
one = @One(select = "io.weew12.github.mapper.StudentMapper.findStudentsByClassId"))
})
MyClass findMyClassWithStudents(String className);
} @Test
public void findMyClassWithStudentsTest() {
SqlSession sqlSession = MybatisUtils.getSession();
MyClassMapper classMapper = sqlSession.getMapper(MyClassMapper.class);
MyClass myClass = classMapper.findMyClassWithStudents("二班");
System.out.println(myClass);
sqlSession.close();
}结果:

5.4 本章小结
本章主要讲解了MyBatis的注解开发。首先讲解了基于注解的单表增删改查常用注解,包括@Select注解、@Insert 注解、@Update注解、@Delete 注解、@Param注解等;然后讲解了基于注解的关联查询,包括一对一查询、一对多查询和多对多查询。通过学习本章的内容,读者可以了解MyBatis中常用注解的主要作用,并能够掌握这些注解在实际开发中的应用。在MyBatis框架中,这些注解十分重要,熟练掌握它们能极大地提高开发效率。
【思考题】
1. 请简述MyBatis的常用注解及其作用
- @Select:用于定义查询语句。例如,
@Select("SELECT * FROM users WHERE id = #{id}")可以用来从users表中根据给定的 ID 获取记录。 - @Insert:此注解用于插入数据到数据库表中。比如,
@Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")将向users表添加一条新记录。 - @Update:更新数据库中的已有记录。如
@Update("UPDATE users SET name=#{name} WHERE id=#{id}")会更改指定用户的名称。 - @Delete:删除数据库中的记录。例如,
@Delete("DELETE FROM users WHERE id = #{id}")可移除具有特定 ID 的用户条目。 - @Options:通常与 @Insert 或 @Update 结合使用,用于设置额外选项,如是否使用生成键(useGeneratedKeys)、键属性(keyProperty)等。
- @Results 和 @Result:这两个注解一起工作来定义结果集如何映射到 Java 对象上。它们允许你指定列名和对象属性之间的对应关系。
- @Param:当方法参数超过一个时,可以使用此注解明确指定参数名称,从而确保 SQL 语句正确引用传入的值。
- @Mapper:标注在一个接口或类上,表示该接口/类是 MyBatis 的 Mapper 接口。这有助于 MyBatis 自动扫描并注册这些接口为数据访问层的一部分。
2. 请简述@Result 注解中的常用属性及其作用
@Result 注解主要用于描述结果集中某一列与 Java 对象属性之间的映射关系。它有几个关键属性可以帮助开发者更灵活地控制数据转换过程:
- column:指定数据库列名。这是必需的,用来告诉 MyBatis 哪一列的数据应该被映射。
- property:指示 Java 对象中的哪个字段将接收来自数据库列的数据。这也是必须提供的信息。
- javaType:定义了 Java 属性的类型。虽然大多数情况下 MyBatis 能够自动推断出正确的类型,但在某些复杂场景下可能需要显式声明。
- jdbcType:指定 JDBC 类型,有时为了优化性能或者处理 NULL 值问题,我们需要指定 JDBC 类型。
- typeHandler:如果默认的类型处理器不能满足需求,可以通过这个属性指定自定义的类型处理器。
- one 和 many:这两个属性分别用于一对一和一对多的关系映射。它们允许你嵌套另一个
@Results注解来进一步定义关联实体的映射规则。
通过合理利用这些注解及属性,开发者可以在 MyBatis 中实现高效且易于维护的数据访问逻辑。
