目录
1、简介
2、业务场景及环境准备
2.1、环境
2.2、ER图
2.3、SQL
3、一对一
3.1、POJO
3.2、OrderMapper.xml
3.3、resultMap
3.4、执行结果
4、一对多
4.1、POJO
4.2、UserMapper.xml
4.3、resultMap
4.4、执行结果
5、多对多
5.1、POJO
5.2、UserMapper.xml
5.3、执行结果
6、注意事项
作者简介:准大三本科网络工程专业在读,持续学习Java,努力输出优质文章
⭐MyBatis系列①:增删改查
⭐MyBatis系列②:两种Dao开发方式
⭐MyBatis系列③:动态SQL
⭐MyBatis系列④:核心配置文件详解
MyBatis 是一个优秀的持久层框架,它提供了强大的支持来执行数据库操作,包括多表查询。多表查询是指从多个数据库表中检索数据的过程,这在实际的应用中非常常见。MyBatis 提供了多种方法来执行多表查询,以下是一些常见的技术和方法:
无论使用哪种方法,多表查询都需要仔细考虑性能和结果的数据结构。在执行多表查询时,需要注意数据库表之间的关联关系、连接方式(内连接、左连接等)以及查询结果的组织方式,以便在查询结果中获取所需的数据。 MyBatis 的强大灵活性使得你可以根据实际情况选择合适的方法来执行多表查询。
模拟的业务场景为订单与用户的关系,可以是一对一、一对多。
比如,一个用户有多个订单,一个订单只有一个用户。
还有就是用户与角色的关系,多个用户拥有多个角色,一个角色可以被多个用户拥有,一个用户可以拥有多个角色。
下面准备四张表:
ordersQuery
userQuery
roleQuery
user_role
CREATE TABLE `rolequery` (
`id` bigint(20) NOT NULL,
`roleName` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `rolequery`(`id`,`roleName`) VALUES (1,'CEO'),(2,'CFO'),(3,'COO');
CREATE TABLE `userquery` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`birthday` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `userquery`(`id`,`username`,`password`,`birthday`)
VALUES (1,'lucy','123','2019-02-15'),
(2,'tom','123','2002-10-26');
CREATE TABLE `user_role` (
`user_id` bigint(20) NOT NULL,
`role_id` bigint(20) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `userquery` (`id`),
CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `rolequery` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/* 插入数据到表 'user_role' */
INSERT INTO `user_role`(`user_id`,`role_id`) VALUES (1,1),(1,2),(2,2),(2,3);
CREATE TABLE `ordersquery` (
`id` INT(11) NOT NULL,
`ordertime` VARCHAR(255),
`total` DOUBLE,
`userID` BIGINT(20) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `orders_user_ibfk` FOREIGN KEY (`userID`) REFERENCES `userquery` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `ordersquery` (`id`,`ordertime`,`total`,`userID`)
VALUES (1,'1693059342876','3000',1),
(2,'1693059342876','4000',1),
(3,'1693059342876','5000',2);
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
对应的SQL语句:
select ordersquery.*, userquery.username,userquery.`password`,userquery.birthday from ordersquery, userquery where ordersquery.userID = userquery.id;
User:
package com.xzl.domain;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class User {
private int id;
private String username;
private String password;
private String birthday;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday=" + birthday +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
Order:
package com.xzl.domain;
import java.util.Date;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class Order {
private int id;
private Date ordertime;
private double total;
//这个地方数据库里面的表单是"userID",但是这里应该封装整个User对象
private User user;
@Override
public String toString() {
return "Order{" +
"id=" + id +
", ordertime=" + ordertime +
", total=" + total +
", user=" + user +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getOrdertime() {
return ordertime;
}
public void setOrdertime(Date ordertime) {
this.ordertime = ordertime;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
OrderMapper.xml
OrderMapper.xml中的resultMap有两种写法:
这个部分其实就是手动指定字段与实体属性的映射关系
(column: 数据表的字段名称
property:实体的属性名称)
写法①:
写法②:
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
相比之前一对一的POJO,变化在于User类多了一个List
(两个POJO代码里面的toString正常写就可以,这里只是为了方便测试):
User:
package com.xzl.domain;
import java.util.List;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class User {
private int id;
private String username;
private String password;
private String birthday;
private List orderList;
@Override
public String toString() {
return orderList == null ? "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday=" + birthday +
'}' : "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday='" + birthday + '\'' +
", orderList=" + orderList +
'}';
}
public List getOrderList() {
return orderList;
}
public void setOrderList(List orderList) {
this.orderList = orderList;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
Order:
package com.xzl.domain;
import java.util.Date;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class Order {
private int id;
private Date ordertime;
private double total;
//这个地方数据库里面的表单是"userID",但是这里应该封装整个User对象
private User user;
@Override
public String toString() {
return user == null ? "Order{" +
"id=" + id +
", ordertime=" + ordertime +
", total=" + total +
'}' : "Order{" +
"id=" + id +
", ordertime=" + ordertime +
", total=" + total +
", user=" + user +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getOrdertime() {
return ordertime;
}
public void setOrdertime(Date ordertime) {
this.ordertime = ordertime;
}
public double getTotal() {
return total;
}
public void setTotal(double total) {
this.total = total;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
UserMapper.xml的resultMap相比于OrderMapper.xml的写法略有不同,区别在于这里需要封装的是对象集合,而不是单个对象:
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
相比之前一对一的POJO,变化在于User类多了一个List
User(toString正常写就可以,这里只是为了方便测试):
package com.xzl.domain;
import java.util.List;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class User {
private int id;
private String username;
private String password;
private String birthday;
private List orderList;
private List roleList;
@Override
public String toString() {
if (orderList==null && roleList==null){
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday='" + birthday + '\'' +
'}';
}else {
if (orderList==null){
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday='" + birthday + '\'' +
", roleList=" + roleList +
'}';
}else {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", birthday='" + birthday + '\'' +
", orderList=" + orderList +
'}';
}
}
}
public List getRoleList() {
return roleList;
}
public void setRoleList(List roleList) {
this.roleList = roleList;
}
public List getOrderList() {
return orderList;
}
public void setOrderList(List orderList) {
this.orderList = orderList;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
}
Role:
package com.xzl.domain;
import java.util.Date;
/**
* @author 逐梦苍穹
* @date 2023/8/26 16:48
*/
public class Role {
private int id;
private String rolename;
@Override
public String toString() {
return "Role{" +
"id=" + id +
", rolename='" + rolename + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
}
MyBatis多表配置方式:
一对一配置:使用
一对多配置:使用
多对多配置:使用
这里涉及到的操作都需要有一个配置好的日期类型转换器,不然Date无法正确转换。具体请看我这篇文章:MyBatis核心配置文件详解
转换器代码如下:
package com.xzl.handle;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;
/**
* @author 逐梦苍穹
* @date 2023/8/24 23:30
* setNonNullParameter为java程序设置数据到数据库的回调方法
* getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法
* i 是一个整数,表示要设置的参数在 SQL 语句中的位置。
* s 表示数据库列名
*/
public class DateTypeHandle extends BaseTypeHandler {
@Override
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
// 在预处理语句中设置非空参数
// 将 Date 类型的数据转换为 long 类型的时间戳,并以字符串形式设置到 PreparedStatement 中
// preparedStatement.setString(i, date.getTime() + "");
preparedStatement.setLong(i,date.getTime());
}
@Override
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
// 从结果集中获取可空结果(根据列名)
// 将结果集中的 long 类型的时间戳转换为 Date 类型并返回
return new Date(resultSet.getLong(s));
}
@Override
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
// 从结果集中获取可空结果(根据列索引)
// 将结果集中的 long 类型的时间戳转换为 Date 类型并返回
return new Date(resultSet.getLong(i));
}
@Override
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
// 从存储过程的调用结果中获取可空结果
// 直接获取存储过程的 Date 类型数据并返回
return callableStatement.getDate(i);
}
}
如果觉得文章写的不错,欢迎点赞收藏加关注!
您的一键三连,就是我创作的最大动力!