目录
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系列④:核心配置文件详解
1、简介
MyBatis 是一个优秀的持久层框架,它提供了强大的支持来执行数据库操作,包括多表查询。多表查询是指从多个数据库表中检索数据的过程,这在实际的应用中非常常见。MyBatis 提供了多种方法来执行多表查询,以下是一些常见的技术和方法:
- 使用嵌套查询: 这是最基本的多表查询方法,通过在 SQL 语句中嵌套子查询来联合多个表的数据。例如,你可以在 SELECT 语句中使用子查询来从一个表中获取数据,然后再将其用于主查询中。这种方法在某些简单情况下是有效的,但对于复杂的多表查询可能会变得冗长和难以维护。
- 使用多个 SQL 语句: 在 MyBatis 中,你可以编写多个独立的 SQL 语句,每个语句都从一个表中检索数据,然后在 Java 代码中将这些数据组合起来。这通常需要在 Java 代码中手动执行每个查询并进行数据处理,但对于一些复杂的多表查询情况可能更加灵活。
- 使用关联查询: MyBatis 支持使用关联查询来执行多表查询,特别是在映射文件中配置了表之间的关联关系。通过在 Mapper XML 文件中配置 association 或 collection 来表示表之间的关联关系,MyBatis 可以自动根据关系从多个表中检索数据并构造结果对象。
- 使用自定义映射查询: 有时候,多表查询的结果可能不适合于一个实体类,这时你可以使用自定义映射查询来将结果映射到一个 Map 或者其他自定义的数据结构中,以适应查询的需要。
无论使用哪种方法,多表查询都需要仔细考虑性能和结果的数据结构。在执行多表查询时,需要注意数据库表之间的关联关系、连接方式(内连接、左连接等)以及查询结果的组织方式,以便在查询结果中获取所需的数据。 MyBatis 的强大灵活性使得你可以根据实际情况选择合适的方法来执行多表查询。
2、业务场景及环境准备
模拟的业务场景为订单与用户的关系,可以是一对一、一对多。
比如,一个用户有多个订单,一个订单只有一个用户。
还有就是用户与角色的关系,多个用户拥有多个角色,一个角色可以被多个用户拥有,一个用户可以拥有多个角色。
2.1、环境
下面准备四张表:
ordersQuery
userQuery
roleQuery
user_role
2.2、ER图
2.3、SQL
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);
3、一对一
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
对应的SQL语句:
select ordersquery.*, userquery.username,userquery.`password`,userquery.birthday from ordersquery, userquery where ordersquery.userID = userquery.id;
3.1、POJO
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;
}
}
3.2、OrderMapper.xml
OrderMapper.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.xzl.dao.OrderMapper">
<resultMap id="OrderMapper" type="com.xzl.domain.Order">
<!--column通过SQL语句查询出来的字段,property对应的实体属性-->
<id column="id" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
<result column="userId" property="user.id"/>
<result column="username" property="user.username"/>
<result column="password" property="user.password"/>
<result column="birthday" property="user.birthday"/>
</resultMap>
<resultMap id="OrderMapperAssociation" type="com.xzl.domain.Order">
<!--column通过SQL语句查询出来的字段,property对应的实体属性-->
<id column="id" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
<!--
property: 当前实体(order)中的属性名称(private User user)
javaType: 当前实体(order)中的属性的类型(User)
-->
<association property="user" javaType="com.xzl.domain.User">
<id column="userId" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
</association>
</resultMap>
<select id="findAll" resultMap="OrderMapperAssociation">
SELECT ordersquery.id,
ordersquery.ordertime,
ordersquery.total,
userquery.id AS userId,
userquery.username,
userquery.password,
userquery.birthday
FROM ordersquery,
userquery
WHERE ordersquery.userID = userquery.id;
</select>
</mapper>
3.3、resultMap
OrderMapper.xml中的resultMap有两种写法:
这个部分其实就是手动指定字段与实体属性的映射关系
(column: 数据表的字段名称
property:实体的属性名称)
写法①:
写法②:
3.4、执行结果
4、一对多
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
4.1、POJO
相比之前一对一的POJO,变化在于User类多了一个List<Order> orderList。
(两个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<Order> 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<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> 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;
}
}
4.2、UserMapper.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.xzl.dao.UserMapper">
<resultMap id="UserMapper" type="com.xzl.domain.User">
<!--column通过SQL语句查询出来的字段,property对应的实体属性-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
<!--配置集合信息
property:集合名称
ofType:当前集合中的数据类型
-->
<collection property="orderList" ofType="com.xzl.domain.Order">
<id column="orderID" property="id"/>
<result column="ordertime" property="ordertime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="findAll" resultMap="UserMapper">
SELECT userquery.*,
ordersquery.id AS orderID,
ordersquery.ordertime,
ordersquery.total
FROM userquery
LEFT JOIN ordersquery ON userquery.id = ordersquery.userID;
</select>
</mapper>
4.3、resultMap
UserMapper.xml的resultMap相比于OrderMapper.xml的写法略有不同,区别在于这里需要封装的是对象集合,而不是单个对象:
4.4、执行结果
5、多对多
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
5.1、POJO
相比之前一对一的POJO,变化在于User类多了一个List<Role> roleList。
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<Order> orderList;
private List<Role> 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<Role> getRoleList() {
return roleList;
}
public void setRoleList(List<Role> roleList) {
this.roleList = roleList;
}
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> 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;
}
}
5.2、UserMapper.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.xzl.dao.UserMapper">
<resultMap id="userRoleMap" type="com.xzl.domain.User">
<!--column通过SQL语句查询出来的字段,property对应的实体属性-->
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="birthday" property="birthday"/>
<!--配置集合信息
property:集合名称
ofType:当前集合中的数据类型
-->
<collection property="roleList" ofType="com.xzl.domain.Role">
<id column="roleID" property="id"/>
<result column="rolename" property="rolename"/>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
SELECT userquery.id,
userquery.username,
userquery.`password`,
userquery.birthday,
rolequery.id AS roleID,
rolequery.rolename
FROM userquery
LEFT JOIN user_role ON userquery.id = user_role.user_id
INNER JOIN rolequery ON user_role.role_id = rolequery.id;
</select>
</mapper>
5.3、执行结果
6、注意事项
MyBatis多表配置方式:
一对一配置:使用<resultMap>做配置
一对多配置:使用<resultMap>+<collection>做配置
多对多配置:使用<resultMap>+<collection>做配置
这里涉及到的操作都需要有一个配置好的日期类型转换器,不然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<Date> {
@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);
}
}
🍁如果觉得文章写的不错,欢迎点赞收藏加关注!
🍁您的一键三连,就是我创作的最大动力!😊