举个栗子

现有如下表结构,用户表、角色表、用户角色关联表。

一个用户有多个角色,一个角色有可以给多个用户,也即常见的多对多场景



现有这样一个需求,分页查询用户数据,除了用户ID和用户名称字段,还要查出这个用户的所有角色



从上面的表格我们可以看出,用户有三个,但每个人的角色不止一个,而且有重复的角色,这里角色的数据从多行合并到了1行

难点分析

SQL存在的问题:

想使用SQL实现上面的效果不是不可以,但是很复杂且效率低下,尤其这个地方还需要分页,所以为了保证查询效率,我们需要把逻辑放到服务端来写;

服务端存在的问题:

服务端可以把需要的数据都查询出来,然后自己判断整合,首先十分复杂不说,而且这里有个问题:如何在查询条件很复杂的情况下保证分页?

解决方案

核心方案就是使用Mybatis的collection标签自动实现多行合并。

下面是collection标签的一些介绍

常见写法

<resultMap id="ExtraBaseResultMap" type="com.example.mybatistest.entity.UserInfoDO">
<!--
WARNING - @mbg.generated
-->
<result column="user_id" jdbcType="INTEGER" property="userId"/>
<result column="user_name" jdbcType="INTEGER" property="userName"/>
<collection javaType="java.util.ArrayList" ofType="com.example.mybatistest.entity.MyRole"
property="roleList">
<result column="role_id" jdbcType="INTEGER" property="roleId"/>
<result column="role_name" jdbcType="VARCHAR" property="roleName"/>
</collection>
</resultMap>

尝试一下

1、准备材料

数据库脚本

/*
Navicat Premium Data Transfer Source Server : 本机
Source Server Type : MySQL
Source Server Version : 80021
Source Host : localhost:3306
Source Schema : mybatis-test Target Server Type : MySQL
Target Server Version : 80021
File Encoding : 65001 Date: 23/06/2022 19:16:34
*/ SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0; -- ----------------------------
-- Table structure for my_role
-- ----------------------------
DROP TABLE IF EXISTS `my_role`;
CREATE TABLE `my_role` (
`role_id` int NOT NULL COMMENT '角色主键',
`role_code` varchar(32) DEFAULT NULL COMMENT '角色code',
`role_name` varchar(32) DEFAULT NULL COMMENT '角色名称',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ----------------------------
-- Records of my_role
-- ----------------------------
BEGIN;
INSERT INTO `my_role` VALUES (1, 'admin', '超级管理员');
INSERT INTO `my_role` VALUES (2, 'visitor', '游客');
COMMIT; -- ----------------------------
-- Table structure for my_user
-- ----------------------------
DROP TABLE IF EXISTS `my_user`;
CREATE TABLE `my_user` (
`user_id` int NOT NULL COMMENT '用户主键',
`user_name` varchar(32) DEFAULT NULL COMMENT '用户名称',
`user_gender` tinyint DEFAULT NULL COMMENT '用户性别,1:男/2:女/3:未知',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ----------------------------
-- Records of my_user
-- ----------------------------
BEGIN;
INSERT INTO `my_user` VALUES (1, '用户1', 1);
COMMIT; -- ----------------------------
-- Table structure for my_user_role_rel
-- ----------------------------
DROP TABLE IF EXISTS `my_user_role_rel`;
CREATE TABLE `my_user_role_rel` (
`rel_id` int NOT NULL COMMENT '角色主键',
`role_id` int DEFAULT NULL COMMENT '角色ID',
`user_id` int DEFAULT NULL COMMENT '用户ID',
PRIMARY KEY (`rel_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- ----------------------------
-- Records of my_user_role_rel
-- ----------------------------
BEGIN;
INSERT INTO `my_user_role_rel` VALUES (1, 1, 1);
INSERT INTO `my_user_role_rel` VALUES (2, 2, 1);
COMMIT; SET FOREIGN_KEY_CHECKS = 1;

SpringBoot项目pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>mybatis-test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-test</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<!-- 我这里使用的是mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

SpringBoot项目application.properties

# 数据库配置
spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis-test?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=xxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # mybatis
mybatis.configuration.auto-mapping-behavior=full
mybatis.configuration.map-underscore-to-camel-case=true
mybatis-plus.mapper-locations=classpath*:/mybatis/mapper/*.xml

2、项目代码

目录结构

各类代码

MybatisTestApplication.java

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication
@MapperScan({"com.example.mybatistest.mapper"})
public class MybatisTestApplication { public static void main(String[] args) {
SpringApplication.run(MybatisTestApplication.class, args);
} }

QueryController.java

import com.example.mybatistest.entity.UserInfoDO;
import com.example.mybatistest.service.UserRoleRelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController
@RequestMapping("/mybatis")
public class QueryController { @Autowired
private UserRoleRelService userRoleRelService; @GetMapping("/queryList")
public List<UserInfoDO> queryList() {
return userRoleRelService.queryList();
}
}

UserRoleRelService.java

import com.example.mybatistest.entity.UserInfoDO;

import java.util.List;

public interface UserRoleRelService {
List<UserInfoDO> queryList();
}

UserRoleRelServiceImpl.java

import com.example.mybatistest.entity.UserInfoDO;
import com.example.mybatistest.repository.MyUserRoleRelRepository;
import com.example.mybatistest.service.UserRoleRelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.List; @Service
public class UserRoleRelServiceImpl implements UserRoleRelService { @Autowired
private MyUserRoleRelRepository myUserRoleRelRepository; @Override
public List<UserInfoDO> queryList() {
return myUserRoleRelRepository.queryList();
}
}

MyUserRoleRelRepository.java

import com.example.mybatistest.entity.UserInfoDO;

import java.util.List;

public interface MyUserRoleRelRepository {
List<UserInfoDO> queryList();
}

MyUserRoleRelRepositoryImpl.java

import com.example.mybatistest.entity.UserInfoDO;
import com.example.mybatistest.mapper.MyUserRoleRelMapper;
import com.example.mybatistest.repository.MyUserRoleRelRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import java.util.List; @Repository
public class MyUserRoleRelRepositoryImpl implements MyUserRoleRelRepository {
@Autowired
public MyUserRoleRelMapper myUserRoleRelMapper; @Override
public List<UserInfoDO> queryList() {
return myUserRoleRelMapper.queryList();
}
}

MyUserRoleRelMapper.java

import com.example.mybatistest.entity.UserInfoDO;
import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper
public interface MyUserRoleRelMapper {
List<UserInfoDO> queryList();
}

MyRole.java

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column; @Builder
@Data
@TableName("my_role")
@NoArgsConstructor
@AllArgsConstructor
public class MyRole { @Column(name = "role_id")
private Integer roleId; @Column(name = "role_name")
private String roleName;
}

MyUserRoleRel.java

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Column; @Builder
@Data
@TableName("my_user_role_rel")
@NoArgsConstructor
@AllArgsConstructor
public class MyUserRoleRel { @Column(name = "rel_id")
private Integer relId; @Column(name = "user_id")
private Integer userId; @Column(name = "role_id")
private Integer roleId;
}

UserInfoDO.java

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor; import java.util.List; @Builder
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfoDO {
private Integer userId; private String userName; private List<MyRole> roleList;
}

MyUserRoleRelMapper.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.example.mybatistest.mapper.MyUserRoleRelMapper">
<resultMap id="BaseResultMap" type="com.example.mybatistest.entity.MyUserRoleRel">
<!--
WARNING - @mbg.generated
-->
<result column="rel_id" jdbcType="INTEGER" property="relId"/>
<result column="role_id" jdbcType="INTEGER" property="roleId"/>
<result column="user_id" jdbcType="INTEGER" property="userId"/>
</resultMap>
<resultMap id="ExtraBaseResultMap" type="com.example.mybatistest.entity.UserInfoDO">
<!--
WARNING - @mbg.generated
-->
<result column="user_id" jdbcType="INTEGER" property="userId"/>
<result column="user_name" jdbcType="INTEGER" property="userName"/>
<collection javaType="java.util.ArrayList" ofType="com.example.mybatistest.entity.MyRole"
property="roleList">
<result column="role_id" jdbcType="INTEGER" property="roleId"/>
<result column="role_name" jdbcType="VARCHAR" property="roleName"/>
</collection>
</resultMap>
<select id="queryList" resultMap="ExtraBaseResultMap">
SELECT
t3.user_id,
t3.user_name,
t2.role_id,
t2.role_name
FROM
my_user_role_rel t1
LEFT JOIN my_role t2 ON t1.role_id = t2.role_id
LEFT JOIN my_user t3 ON t1.user_id = t3.user_id
</select>
</mapper>

3、实现效果



这里可以看到roleList里面有两条数据,说明mybatis已经自动聚合完成了。

4、一些缺点

虽然Mybatis可以帮我们实现多行合并的功能,但并不是没有问题的。

当使用角色当做查询条件时,由于角色已经指定了,那么roleList里面必定只有这一个角色,不再会有聚合效果,也就看不到这个用户所有的角色了。

我只能说具体看产品要求吧,大部分时候上面那种问题产品都是可以接受的。

MyBatis实现多行合并(collection标签使用)的更多相关文章

  1. mybatis <collection>标签 类型为string时无法获取重复数据错误

    1.场景: fyq_share_house 表 和 fyq_sh_tag 表 两张表是一对多的关系, 一个楼盘对应多个标签,在实体类ShareHouse中使用 /** * 楼盘标签 */ privat ...

  2. MyBatis从入门到精通(十二):使用collection标签实现嵌套查询

    最近在读刘增辉老师所著的<MyBatis从入门到精通>一书,很有收获,于是将自己学习的过程以博客形式输出,如有错误,欢迎指正,如帮助到你,不胜荣幸! 本篇博客主要讲解使用collectio ...

  3. MyBatis之ResultMap的association和collection标签(一)

    1.先说resultMap比较容易混淆的点, 2. Map结尾是映射,Type是类型  resultType 和restltMap restulyType: 1.对应的是java对象中的属性,大小写不 ...

  4. MyBatis之ResultMap的association和collection标签详解

    一.前言 MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子. 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样. 如果能有一种数据库映射模式, ...

  5. mybatis collection标签和association标签(一对多,一对一)转载

    mybatis 一对一与一对多collection和association的使用   在mybatis如何进行一对一.一对多的多表查询呢?这里用一个简单的例子说明. 一.一对一 1.associati ...

  6. mybatis的<if>标签,<foreach>标签,<collection>标签,<association>标签以及useGeneratedKeys用法

    <if>标签 1.判断非空或不等于 <if test="assessTypes!= null and assessTypes!='' "> AND FIND ...

  7. MyBatis的Mapper文件的foreach标签详解

    MyBatis的Mapper文件的foreach标签用来迭代用户传递过来的Lise或者Array,让后根据迭代来拼凑或者批量处理数据.如:使用foreach来拼接in子语句. 在学习MyBatis M ...

  8. Mybatis关联查询<association> 和 <collection>

    一.背景 1.在系统中一个用户存在多个角色,那么如何在查询用户的信息时同时把他的角色信息查询出来啦? 2.用户pojo: public class SysUser { private Long id; ...

  9. ssm 关于mybatis启动报Result Maps collection already contains value for ...的问题总结

    Result Maps collection already contains value for com.zhaike.mapping.ChapterMapper.BaseResultMap Err ...

  10. mybatis报错Mapped Statements collection does not contain value for com.inter.IOper

    首页 > 精品文库 > mybatis报错Mapped Statements collection does not contain value for com.inter.IOper m ...

随机推荐

  1. [转帖]Google SRE 薪水,看看同样作为 SRE 的你相差多少

    https://zhuanlan.zhihu.com/p/566098252 SRE 是确保所有生产环境(Infra/Server/DBS 等)一直正常运行的人.每个网络科技公司基本都有这个部门.但是 ...

  2. 一键部署Docker中间件简单方法-redis为例

    一键部署Docker中间件简单方法-redis为例 背景 想能够快速部署一些中间件. 写文档虽然可以, 但是总会有人问, 能够一键部署应该最好不过. 下载以及导出镜像 docker pull redi ...

  3. Tidb异名恢复Mysql数据库的过程

    Tidb异名恢复Mysql数据库的过程 背景 先说坑: TiDB备份恢复的方式 1. mysqldump + mysql source 的方式. 2. mydumper + loader tidb 的 ...

  4. [转帖]TiKV & TiFlash 加速复杂业务查询丨TiFlash 应用实践

    返回全部 边城元元案例实践2022-08-02 复杂业务查询对于传统的关系型数据库来说是一种考验,而通过 TiKV 行存与 TiFlash 的列存结合使用就能很好地应对.本文根据 TUG 用户边城元元 ...

  5. [转帖]TiDB Control 使用说明

    https://docs.pingcap.com/zh/tidb/stable/tidb-control TiDB Control 是 TiDB 的命令行工具,用于获取 TiDB 状态信息,多用于调试 ...

  6. [转帖]查看请求在nginx中消耗的时间

    需求:查看请求在nginx中消耗的时间,不包括程序响应时间. 1.声明日志的格式,在nginx配置文件nginx.conf里的http下添加如下内容: log_format test '$remote ...

  7. WorkStation的网络损耗

    WorkStation的网络损耗 背景 对周六遇到的问题进行了一下深入思考. 发现虽然可以通过WorkStation的方式来进行Clients以及新命令的扩容. 但是Workstation的桥接网络模 ...

  8. [转帖]网络传输性能netperf测试方法和下载

    简介 Netperf是一种网络性能的测试工具,主要针对基于TCP或UDP的传输.Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式 ...

  9. [转帖]【JVM】JVM源码分析之Metaspace解密

    概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所致,看到 ...

  10. ESXi规避ESXiArgs勒索软件的简单方法

    摘要 今天查看深信服科技的公众号 发现有一个ESXiArgs 的勒索软件. 感觉对公司存在一定的风险.但是感觉操作手册有点简单. 这里想着写全面一点. 作为操作手册使用. 并且深信服仅是解决了在运行, ...