MYBATIS+MYSQL多对多关系带条件分页查询的解决方案

一.需求

现有学生表、课程表、学生-课程关系表,学生与课程为多对多关系。前端需要分页查询接口来展示学生信息与对应的多个课程信息,并且要对课程名称进行搜索。信息及展示效果如下:

学生姓名 课程名
小明 高等数学、大学物理
小张 线性代数、概率论与数理统计
小红 高等数学、大学物理、线性代数、概率论与数理统计
小李 高等数学、大学物理、线性代数

二.数据库结构

#学生表
CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

#课程表
CREATE TABLE `course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

#学生课程关系表
CREATE TABLE `student__course` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `student_id` int(11) DEFAULT NULL,
  `course_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

三.思路(懒得看直接跳到实现)

首先我们要明确一下整个实现的步骤:

  1. 写出SQL语句,能够符合条件查询以及分页。
  2. 在Mybatis的resultMap中映射结果集。
  3. 返回封装好的结果,这里重要的是返回的数据总条数一定要正确。

再说下这里面存在的几个坑:

  1. 因为是多对多的分页查询,如果你用的是mybatis-plus自带的分页器的话返回的数量是错误的。
  2. 使用分页助手可以解决上述分页的问题,但也只是这种简化过的需求下,再复杂的话分页助手也无法处理。
  3. 按课程名称查询时,其实是查询包含该课程的全部信息,学生所对应的课程是一个整体。

举个例子,我们最开始的想法应该是这样的

SELECT
	s0.`name` AS sName,
	c0.`name` AS cName
FROM
	student s0
	LEFT JOIN student__course sc0 ON s0.id = sc0.student_id
	LEFT JOIN course c0 ON sc0.course_id = c0.id 
WHERE
	c0.`name` = '高等数学'
LIMIT 0,5;

查询结果显然是错误的:
在这里插入图片描述
那就会想到用子查询+EXISTS或者IN的方法:

-- 	EXISTS
SELECT
	s0.`name` AS sName,
	c0.`name` AS cName  
FROM
	student s0
	LEFT JOIN student__course sc0 ON s0.id = sc0.student_id
	LEFT JOIN course c0 ON sc0.course_id = c0.id 
WHERE
	EXISTS (
	SELECT
		s1.id 
	FROM
		student s1
		LEFT JOIN student__course sc1 ON s1.id = sc1.student_id
		LEFT JOIN course c1 ON sc1.course_id = c1.id 
	WHERE
		c1.`name` = '高等数学' 
		AND s0.id = s1.id 
	);
	
-- 	IN
SELECT
	s0.`name` AS sName,
	c0.`name` AS cName   
FROM
	student s0
	LEFT JOIN student__course sc0 ON s0.id = sc0.student_id
	LEFT JOIN course c0 ON sc0.course_id = c0.id 
WHERE
	s0.id IN (
	SELECT
		s1.id 
	FROM
		student s1
		LEFT JOIN student__course sc1 ON s1.id = sc1.student_id
		LEFT JOIN course c1 ON sc1.course_id = c1.id 
	WHERE
		c1.`name` = '高等数学' 
	GROUP BY
		s1.id 
		LIMIT 0,5 
	);

EXISTS方法直接被排出,因为无法实现分页,limit加在exists语句中毫无意义。
IN方法看似可行,但在MYSQL某些版本下无法在IN子查询中LIMIT。

四.实现

--  查询分页数据
SELECT
	s.`name` AS sName,
	c.`name` AS cName
FROM
	student s
	LEFT JOIN student__course sc ON s.id = sc.student_id
	LEFT JOIN course c ON sc.course_id = c.id
	INNER JOIN (
	SELECT
		s.id 
	FROM
		student s
		LEFT JOIN student__course sc ON s.id = sc.student_id
		LEFT JOIN course c ON sc.course_id = c.id 
	GROUP BY
		s.id 
	HAVING
		FIND_IN_SET('高等数学',GROUP_CONCAT( c.`name` )) 
		LIMIT 0,5
	) AS ids ON s.id = ids.id;

--  查询数据总条数
SELECT
	COUNT( DISTINCT temp.sId ) 
FROM
	(
	SELECT
		s.id AS sId,
		s.`name` AS sName,
		c.`name` AS cName 
	FROM
		student s
		LEFT JOIN student__course sc ON s.id = sc.student_id
		LEFT JOIN course c ON sc.course_id = c.id
		INNER JOIN (
		SELECT
			s.id 
		FROM
			student s
			LEFT JOIN student__course sc ON s.id = sc.student_id
			LEFT JOIN course c ON sc.course_id = c.id 
		GROUP BY
			s.id 
		HAVING
			FIND_IN_SET('高等数学',GROUP_CONCAT( c.`name` )) 
		) AS ids ON s.id = ids.id 
	) AS temp;

该操作分两步:

  1. 查询出ids临时表作为外层查询INNER JOIN的条件,内部使用FIND_IN_SET和GROUP_CONCAT配合使用过滤出符合条件的id,并对这个结果集进行LIMIT。
  2. 将外层与内层关联,将符合条件的结果筛选出来作为最后结果。

在这里已经返回了正确的数据,最后一步就是在xml文件中写对应的resultMap即可。

在xml中多个HAVING条件使用:

		<trim prefix="HAVING" suffixOverrides="AND">
            <if test="? != null">
                FIND_IN_SET( #{?}, GROUP_CONCAT(xx.id )) AND
            </if>
            <if test="? != null">
                FIND_IN_SET( #{?}, GROUP_CONCAT( xx.id ))
            </if>
        </trim>

关于效率问题,在万条数据测试下,存在查询缓慢问题,优化方法就是加中间表字段的索引,性能会有很大提升。

到此文章结束,如果您有更好的方法欢迎评论或者私信我,大家共同分享技术共同提升,感谢阅读。

你可能感兴趣的:(MYSQL,MYBATIS)