关于关联查询
首先,请准备一些测试数据,使得:存在若干条用户数据,存在若干条角色数据,某个用户存在与角色的关联,最好有些用户有多个关联,又有些用户只有1个关联,还有些用户没有关联。
假设存在需求:根据id查询某用户信息时,也查出该用户归属于哪几种角色。
**测试数据参考:**
truncate admin;
truncate admin_role;
truncate role;
truncate permission;insert into admin (username, password) values ('admin001', '123456');
insert into admin (username, password) values ('admin002', '123456');
insert into admin (username, password) values ('admin003', '123456');
insert into admin (username, password) values ('admin004', '123456');
insert into admin (username, password) values ('admin005', '123456');
insert into admin (username, password) values ('admin006', '123456');
insert into admin (username, password) values ('admin007', '123456');
insert into admin (username, password) values ('admin008', '123456');
insert into admin (username, password) values ('admin009', '123456');
insert into admin (username, password) values ('admin010', '123456');
insert into admin (username, password) values ('admin011', '123456');
insert into admin (username, password) values ('admin012', '123456');
insert into admin (username, password) values ('admin013', '123456');
insert into admin (username, password) values ('admin014', '123456');
insert into admin (username, password) values ('admin015', '123456');
insert into admin (username, password) values ('admin016', '123456');
insert into admin (username, password) values ('admin017', '123456');
insert into admin (username, password) values ('admin018', '123456');
insert into admin (username, password) values ('admin019', '123456');
insert into admin (username, password) values ('admin020', '123456');insert into permission (name, value, description) values
('商品-商品管理-读取', '/pms/product/read', '读取商品数据,含列表、详情、查询等'),
('商品-商品管理-编辑', '/pms/product/update', '修改商品数据'),
('商品-商品管理-删除', '/pms/product/delete', '删除商品数据'),
('后台管理-管理员-读取', '/ams/admin/read', '读取管理员数据,含列表、详情、查询等'),
('后台管理-管理员-编辑', '/ams/admin/update', '编辑管理员数据'),
('后台管理-管理员-删除', '/ams/admin/delete', '删除管理员数据');insert intorole (name) values
('超级管理员'), ('系统管理员'), ('商品管理员'), ('订单管理员');insert into admin_role (admin_id, role_id) values
(1, 1), (1, 2), (1, 3), (1, 4),
(2, 1), (2, 2), (2, 3),
(3, 1), (3, 2),
(4, 1);
```
本次查询需要执行的SQL语句大致是:
select *
from admin
left join admin_role on admin.id=admin_role.admin_id
left join role on admin_role.role_id=role.id
where admin.id=?
通过测试运行,可以发现(必须基于以上测试数据):
- 当使用的id值为1时,共查询到4条记录,并且用户的基本信息是相同的,只是与角色关联的数据不同
- 当使用的id值为2时,共查询到3条记录
- 当使用的id值为3时,共查询到2条记录
- 当使用其它有效用户的id时,共查询到1条记录
其实,这种查询期望的结果应该是:
public class xxx {// 用户基本信息的若干个属性,例如用户名、密码等// 此用户的若干个角色数据,可以使用 List<?>
}
则可以先创建“角色”对应的数据类型:
public class Role {private Long id;private String name;private String description;private Integer sort;private LocalDateTime gmtCreate;private LocalDateTime gmtModified;// Setters & Getterss// toString()
}
再创建用于封装此次查询结果的类型:
public class AdminDetailsVO {private Long id;private String username;private String password;private String nickname;private String avatar;private String phone;private String email;private String description;private Integer isEnable;private String lastLoginIp;private Integer loginCount;private LocalDateTime gmtLastLogin;private LocalDateTime gmtCreate;private LocalDateTime gmtModified;private List<Role> roles;// Setters & Getterss// toString()
}
接下来,可以在`AdminMapper`接口中添加抽象方法:
AdminDetailsVO getDetailsById(Long id);
需要注意,由于此次关联了3张表一起查询,结果集中必然出现某些列的名称是完全相同的,所以,在查询时,不可以使用星号表示字段列表(因为这样的结果集中的列名就是字段名,会出现相同的列名),而是应该至少为其中的一部分相同名称的列定义别名,例如:
selectadmin.id,admin.username,admin.password,admin.nickname,admin.avatar,admin.phone,admin.email,admin.description,admin.is_enable,admin.last_login_ip,admin.login_count,admin.gmt_last_login,admin.gmt_create,admin.gmt_modified,role.id AS role_id,role.name AS role_name,role.description AS role_description,role.sort AS role_sort,role.gmt_create AS role_gmt_create,role.gmt_modified AS role_gmt_modified
from admin
left join admin_role on admin.id=admin_role.admin_id
left join role on admin_role.role_id=role.id
where admin.id=1;
在Mybatis处理中此查询时,并不会那么智能的完成结果集的封装,所以,必须自行配置`<resultMap>`用于指导Mybatis完成封装!
<resultMap id="DetailsResultMap" type="xx.xx.xx.xx.AdminDetailsVO"><!-- 在1对多、多对多的查询中,即使名称匹配的结果,也必须显式的配置 --><!-- 主键字段的结果必须使用id节点进行配置,配置方式与result节点完全相同 --><id column="id" property="id" /><result column="gmt_create" property="gmtCreate" /><!-- 需要使用collection节点配置1对多中“多”的数据 --><collection property="roles" ofType="xx.xx.xx.Role"><id column="role_id" property="id" /><result column="gmt_create" property="gmtCreate" /></collection>
</resultMap>
的查询SQL语句,并使用以上的`<resultMap>`封装结果即可!
<sql id="DetailsQueryFields"><if test="true">admin.id,admin.username,admin.password,admin.nickname,admin.avatar,admin.phone,admin.email,admin.description,admin.is_enable,admin.last_login_ip,admin.login_count,admin.gmt_last_login,admin.gmt_create,admin.gmt_modified,role.id AS role_id,role.name AS role_name,role.description AS role_description,role.sort AS role_sort,role.gmt_create AS role_gmt_create,role.gmt_modified AS role_gmt_modified</if>
</sql>
<resultMap id="DetailsResultMap" type="cn.tedu.mybatis.AdminDetailsVO">
<!-- 在1对多、多对多的查询中,即使名称匹配的结果,也必须显式的配置 -->
<!-- 主键字段的结果必须使用id节点进行配置,配置方式与result节点完全相同 -->
<id column="id" property="id" /><result column="username" property="username" /><result column="password" property="password" /><result column="nickname" property="nickname" /><result column="avatar" property="avatar" /><result column="phone" property="phone" /><result column="email" property="email" /><result column="description" property="description" /><result column="is_enable" property="isEnable" /><result column="last_login_ip" property="lastLoginIp" /><result column="login_count" property="loginCount" /><result column="gmt_last_login" property="gmtLastLogin" /><result column="gmt_create" property="gmtCreate" /><result column="gmt_modified" property="gmtModified" /><!-- 需要使用collection节点配置1对多中“多”的数据 --><collection property="roles" ofType="cn.tedu.mybatis.Role"><id column="role_id" property="id" /><result column="role_name" property="name" /><result column="role_description" property="description" /><result column="role_sort" property="sort" /><result column="role_gmt_create" property="gmtCreate" /><result column="role_gmt_modified" property="gmtModified" /></collection>
</resultMap>
<select id="getDetailsById" resultMap="DetailsResultMap">select <include refid="DetailsQueryFields" />from adminleft join admin_role on admin.id=admin_role.admin_idleft join role on admin_role.role_id=role.idwhere admin.id=#{id}
</select>
总结:
MyBatis是一个流行的Java持久化框架,它提供了一种方便的方式来执行数据库查询,并支持各种连表查询。在本文中,我将为您详细介绍MyBatis的连表查询方式,并解释在XML中如何编写SQL语句以及实体类的写法,以及需要注意的事项。
连表查询是通过在SQL语句中使用JOIN子句来实现的。在MyBatis中,有以下几种常见的连表方式:
- 内连接(INNER JOIN):只返回满足连接条件的行。
- 左连接(LEFT JOIN):返回左表中的所有行,以及满足连接条件的右表中的行。
- 右连接(RIGHT JOIN):返回右表中的所有行,以及满足连接条件的左表中的行。
- 全连接(FULL JOIN):返回左右表中的所有行,如果某行在另一表中没有匹配的行,则用NULL填充。
在XML中编写连表查询的SQL语句时,可以使用以下方式:
<select id="selectByExample" parameterType="com.example.entity.Example" resultMap="com.example.mapper.ExampleResultMap">SELECT * FROM table1 t1JOIN table2 t2 ON t1.id = t2.id
</select>
在这个例子中,我们使用了JOIN子句将table1
和table2
进行连接,通过ON
关键字指定了连接条件。
在实体类中,我们可以使用嵌套对象来表示连接的表。例如,如果我们有table1
和table2
两个表,我们可以创建以下两个实体类:
public class Table1 {private int id;private String name;private Table2 table2;// getter and setter
}public class Table2 {private int id;private String value;// getter and setter
}
在上面的例子中,Table1
实体类包含了一个类型为Table2
的属性table2
,这样我们就可以通过连表查询获取到table1
和table2
之间的关联数据。
需要注意的是,在进行连表查询时,要确保表之间的连接条件是正确的,并且在数据库中存在相应的关联关系。此外,还要注意避免查询结果过大导致性能问题,可以使用分页或其他限制条件来控制查询返回的数据量。
总结:
- MyBatis支持多种连表查询方式,包括内连接、左连接、右连接和全连接。
- 在XML中编写连表查询的SQL语句时,可以使用JOIN子句,并通过ON关键字指定连接条件。
- 在实体类中,可以使用嵌套对象来表示连接的表。
- 在进行连表查询时,需要确保连接条件正确,并且注意性能问题。