MyBatis实现多表查询
在实际的项目中,我们往往会接触到多表的操作,什么是多表呢, 在实际生活中,每个实体之间往往是存在关系的,而我们的项目却是要依赖数据库将这些实体之间的关系串联起来,从而实现我们的业务,下面着重讲解如何使用 MyBatis 框架处理多张数据表之间的联系
一、关联查询
1.一对一关联查询
实现:根据编号查询员工信息及所在的部门信息
1.1实体类
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 public class Emp implements Serializable { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; //组合一个Dept对象作为自己的属性 private Dept dept; }
1.2接口
package com.augus01.mapper; import com.augus01.pojo.Emp; public interface EmpMapper { //根据编号查询员工信息及所在的部门信息 Emp findEmpJoinDeptByEmpno(int empno); }
1.3映射文件
<?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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus01.mapper.EmpMapper"> <!-- 创建映射关系,用于接受查询出来的结果 --> <resultMap id="empJoinDept" type="emp"> <!-- 设置emp本身的属性 主键字段使用id标签 其他字段使用result标签 property 实体类中的名字 column 后面写数据库中查出来的字段名,如果字段用了别名,则使用别名 --> <id property="empno" column="EMPNO"></id> <result property="ename" column="ENAME"></result> <result property="job" column="JOB"></result> <result property="mgr" column="MGR"></result> <result property="hiredate" column="HIREDATE"></result> <result property="sal" column="SAL"></result> <result property="comm" column="COMM"></result> <result property="deptno" column="DEPTNO"></result> <!-- association标签是用来处理一对一关系 封装一对一信息关系的标签 property emp类的属性名 javaType 用哪个类的对象给属性赋值 --> <association property="dept" javaType="Dept"> <id property="deptno" column="DEPTNO"></id> <result property="dname" column="DNAME"></result> <result property="loc" column="LOC"></result> </association> </resultMap> <!--//根据编号查询员工信息及所在的部门信息 Emp findEmpJoinDeptByEmpno(int empno);--> <select id="findEmpJoinDeptByEmpno" resultMap="empJoinDept"> select * from emp left join dept on emp.empno = dept.deptno where emp.empno=#{empno} </select> </mapper>
1.4测试文件
import com.augus01.mapper.EmpMapper; import com.augus01.pojo.Emp; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; public class Test1 { private SqlSession sqlSession; @Before public void init(){ SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory factory=ssfb.build(resourceAsStream) ; sqlSession=factory.openSession(); } @Test public void testfindEmpJoinDeptByEmpno(){ /*获取字节码文件*/ EmpMapper mapper = sqlSession.getMapper(EmpMapper.class); //执行SQL Emp emp= mapper.findEmpJoinDeptByEmpno(7788); System.out.println(emp); } }
2.一对多关联查询
实现:根据部门号查询部门信息及该部门的所有员工信息
2.1.实体类
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 //实体类需要实现Serializable接口 public class Dept implements Serializable { private Integer deptno; private String dname; private String loc; //创建一个集合,保存查出来一个部门对应的多个员工 List<Emp> empList; }
2.2.接口
package com.augus01.mapper; import com.augus01.pojo.Dept; import org.apache.ibatis.annotations.Param; public interface DeptMapper { //根据部门编号查询部门中员工信息 Dept findDeptJoinEmpsByDeptno(int deptno); }
2.3映射文件
<?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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus01.mapper.DeptMapper"> <resultMap id="DeptJoinEmpsByDeptno" type="dept"> <id property="deptno" column="DEPTNO"></id> <result property="dname" column="DNAME"></result> <result property="loc" column="LOC"></result> <!--collection标签处理一对多关系的标签 property="empList" : 实体类中属性名 ofType="emp" 该属性属于那个类(多表使用这个属性) --> <collection property="empList" ofType="Emp"> <id property="empno" column="EMPNO"></id> <result property="ename" column="ENAME"></result> <result property="job" column="JOB"></result> <result property="mgr" column="MGR"></result> <result property="hiredate" column="HIREDATE"></result> <result property="sal" column="SAL"></result> <result property="comm" column="COMM"></result> <result property="deptno" column="DEPTNO"></result> </collection> </resultMap> <select id="findDeptJoinEmpsByDeptno" resultMap="DeptJoinEmpsByDeptno"> select * from dept left join emp on dept.deptno=emp.deptno where dept.deptno=#{deptno} </select> </mapper>
2.4测试文件
import com.augus01.mapper.DeptMapper; import com.augus01.mapper.EmpMapper; import com.augus01.pojo.Dept; import com.augus01.pojo.Emp; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; public class Test1 { private SqlSession sqlSession; @Before public void init(){ SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory factory=ssfb.build(resourceAsStream) ; sqlSession=factory.openSession(); } @Test public void testfindDeptJoinEmpsByDeptno(){ /*获取字节码文件*/ DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); //执行SQL Dept deptJoinEmpsByDeptno = mapper.findDeptJoinEmpsByDeptno(30); System.out.println(deptJoinEmpsByDeptno); } }
3.多对多关联查询
根据项目编号查询项目信息,以及参与到该项目之中的所有的员工信息,数据准备: 创建项目表和项目记录表
CREATE TABLE `projects` ( `pid` int(2) NOT NULL AUTO_INCREMENT, `pname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, `money` int(11) NULL DEFAULT NULL, PRIMARY KEY (`pid`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; INSERT INTO `projects` VALUES (1, ' ***大学OA', 500000); INSERT INTO `projects` VALUES (2, '学生选课系统', 100000); INSERT INTO `projects` VALUES (3, '讲师测评系统', 20000); INSERT INTO `projects` VALUES (4, '线上问答系统 ', 20000); CREATE TABLE `projectrecord` ( `empno` int(4) NOT NULL, `pid` int(2) NOT NULL, PRIMARY KEY (`empno`, `pid`) USING BTREE, INDEX `fk_project_pro`(`pid`) USING BTREE, CONSTRAINT `fk_emp_pro` FOREIGN KEY (`empno`) REFERENCES `emp` (`EMPNO`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `fk_project_pro` FOREIGN KEY (`pid`) REFERENCES `projects` (`pid`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; INSERT INTO `projectrecord` VALUES (7369, 1); INSERT INTO `projectrecord` VALUES (7521, 1); INSERT INTO `projectrecord` VALUES (7369, 2); INSERT INTO `projectrecord` VALUES (7499, 2); INSERT INTO `projectrecord` VALUES (7521, 2); INSERT INTO `projectrecord` VALUES (7369, 3); INSERT INTO `projectrecord` VALUES (7499, 3); INSERT INTO `projectrecord` VALUES (7521, 3); INSERT INTO `projectrecord` VALUES (7369, 4); INSERT INTO `projectrecord` VALUES (7499, 4);
3.1实体类
创建表Projectrecord如下:
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 //实体类需要实现Serializable接口 public class Projectrecord implements Serializable { private Integer empno; private Integer pid; //组合一个Emp对象作为属性 private Emp emp; }
创建表Projects 如下:
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import sun.dc.pr.PRError; import java.io.Serializable; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 //实体类需要实现Serializable接口 public class Projects implements Serializable { private Integer pid; private String pname; private Integer money; //一个项目对应多个员工 private List<Projectrecord> prd; }
3.2接口
package com.augus01.mapper; import com.augus01.pojo.Projects; public interface ProjectsMapper { Projects findProjectJoinEmpByPid(int pid); }
3.3映射文件
<?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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus01.mapper.ProjectsMapper"> <resultMap id="ProjectJoinEmpByPid" type="projects"> <id property="pid" column="pid"></id> <result property="pname" column="pname"></result> <result property="money" column="money"></result> <!-- 一个项目对应多个开发 一对多 集合属性 collection --> <collection property="prd" ofType="Projectrecord"> <id property="empno" column="empno"></id> <id property="pid" column="pid"></id> <!-- association标签处理一对一 --> <association property="emp" javaType="emp"> <id property="empno" column="empno"></id> <result property="ename" column="ename"></result> <result property="job" column="job"></result> <result property="sal" column="sal"></result> <result property="hiredate" column="hiredate"></result> <result property="mgr" column="mgr"></result> <result property="comm" column="comm"></result> <result property="deptno" column="deptno"></result> </association> </collection> </resultMap> <!-- Projects findProjectJoinEmpByPid(int pid); --> <select id="findProjectJoinEmpByPid" resultMap="ProjectJoinEmpByPid"> SELECT * FROM projects LEFT JOIN projectrecord ON projects.pid= projectrecord.pid LEFT JOIN emp ON emp.EMPNO = projectrecord.empno WHERE projects.pid=#{pid} </select> </mapper>
3.4测试
import com.augus01.mapper.DeptMapper; import com.augus01.mapper.EmpMapper; import com.augus01.mapper.ProjectsMapper; import com.augus01.pojo.Dept; import com.augus01.pojo.Emp; import com.augus01.pojo.Projects; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.security.spec.PSSParameterSpec; public class Test1 { private SqlSession sqlSession; @Before public void init(){ SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory factory=ssfb.build(resourceAsStream) ; sqlSession=factory.openSession(); } //多对多查询 @Test public void testfindProjectJoinEmpByPid(){ ProjectsMapper mapper = sqlSession.getMapper(ProjectsMapper.class); //执行SQL Projects pes = mapper.findProjectJoinEmpByPid(2); System.out.println(pes); } }
二、级联查询
级联查询是利于数据库表间的外键关联关系进行自动的级联查询操作。使用MyBatis实现级联查询,除了实体类增加关联属性外,还需要在映射文件中进行配置。
2.1.立即加载
实现:询30号部门及其该部门员工信息。
所谓立即加载就是查询时,所有的相关数据一次被读取出来,而不是分N次。
2.1.1实体类
Emp类
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.Date; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 public class Emp implements Serializable { private Integer empno; private String ename; private String job; private Integer mgr; private Date hiredate; private Double sal; private Double comm; private Integer deptno; }
Dept类
package com.augus01.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.util.List; @Data //@Data 注解的主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()等方法;但是需要先导入lombok @AllArgsConstructor //使用lombok后生成一个构造方法 @NoArgsConstructor //使用lombok后生成无参数的构造方法 //实体类需要实现Serializable接口 public class Dept implements Serializable { private Integer deptno; private String dname; private String loc; //创建一个集合,保存查出来一个部门对应的多个员工 List<Emp> empList; }
2.1.2接口
EmpMapper
package com.augus01.mapper; import com.augus01.pojo.Emp; public interface EmpMapper { //根据部门编号查询员工信息 List<Emp> findEmpBydeptno(int deptno); }
DeptMapper
package com.augus01.mapper; import com.augus01.pojo.Dept; public interface DeptMapper { //根据部门编号查询部门中员工信息 Dept findDeptByDeptno(int deptno); }
2.1.3映射文件
EmpMapper.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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus01.mapper.EmpMapper"> <!--//根据部门编号查询员工信息 Emp findEmpByEmpno(int empno);--> <select id="findEmpBydeptno" resultType="emp"> select * from emp where deptno=#{deptno} </select> </mapper>
DeptMapper.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"> <!-- namespace需要指定接口文件的路径 --> <mapper namespace="com.augus01.mapper.DeptMapper"> <resultMap id="DeptByDeptno" type="dept"> <id property="deptno" column="DEPTNO"></id> <result property="dname" column="DNAME"></result> <result property="loc" column="LOC"></result> <!-- property="empList" 实体类属性名字 select="com.augus01.mapper.EmpMapper.findEmpByEmpno" 调用另一个SQL javaType="list" 实体类属性数据类型 column="deptno" 将上面那个属性传入给另一个SQL jdbcType="INTEGER" 参数对应JDBC的数据类型 fetchType="eager" 这里有两个选项:eager积极加载, lazy延迟加载 --> <collection property="empList" select="com.augus01.mapper.EmpMapper.findEmpBydeptno" javaType="list" column="deptno" jdbcType="INTEGER" fetchType="eager"> </collection> </resultMap> <!-- Dept findDeptByDeptno(int deptno) --> <select id="findDeptByDeptno" resultMap="DeptByDeptno"> select * from dept where deptno=#{deptno} </select> </mapper>
2.1.4测试文件
import com.augus01.mapper.DeptMapper; import com.augus01.mapper.EmpMapper; import com.augus01.mapper.ProjectsMapper; import com.augus01.pojo.Dept; import com.augus01.pojo.Emp; import com.augus01.pojo.Projects; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.security.spec.PSSParameterSpec; public class Test1 { private SqlSession sqlSession; @Before public void init(){ SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder(); InputStream resourceAsStream = null; try { resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory factory=ssfb.build(resourceAsStream) ; sqlSession=factory.openSession(); } //级联查询 @Test public void testfindDeptByDeptno(){ DeptMapper mapper = sqlSession.getMapper(DeptMapper.class); //执行SQL Dept deptByDeptno = mapper.findDeptByDeptno(30); System.out.println(deptByDeptno); //如果上面数据信息显示效果不好,可以 System.out.println(deptByDeptno.getEmpList()); } }
2.2.延迟加载
MyBatis中的延迟加载,也称为懒加载,是指在进行表的关联查询时,按照设置延迟规则推迟对关联对象的select查询。例如在进行一对多查询的时候,只查询出一方,当程序中需要多方的数据时,mybatis再发出sql语句进行查询,这样子延迟加载就可以的减少数据库压力。MyBatis 的延迟加载只是对关联对象的查询有迟延设置,对于主加载对象都是直接执行查询语句的。
多表联合查询有时候,并不会立即用到所有的查询结果,我来举两个例子:
- 例如,查询一批笔记本电脑的进货明细,而不直接展示每列明细对应电脑配置或者价格等的详细信息,等到用户需要查看某笔记本相关的详细信息的时候,再进行单表查询
- 再例如 ,银行中某个用户拥有50个账户(打比方),只需要查看他有哪些账户,需要知道每个账户的具体信息的时候在查询才是比较合理的
延迟加载的设置如下:
第一步:全局开关:在sqlMapConfig.xml中打开延迟加载的开关。配置完成后所有的association和collection元素都生效
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="true"/> </settings>
- lazyLoadingEnabled:是否开启延迟加载。是Mybatis是否启用懒加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
- aggressiveLazyLoading:当开启时,任何方法的调用都会懒加载对象的所有属性。否则,每个属性会按需加载,
第二步:分开关:指定的association和collection元素中配置fetchType属性。
- eager:表示立刻加载;
- lazy:表示延迟加载。将覆盖全局延迟设置。

浙公网安备 33010602011771号