Mybatisの关联查询
开始前的准备
- 数据库
drop database if exists mybatisdemo; create database mybatisdemo; use mybatisdemo; DROP TABLE IF EXISTS `employee`; CREATE TABLE `employee`( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '员工名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `employee` VALUES (1, '张三'); INSERT INTO `employee` VALUES (2, '李四'); INSERT INTO `employee` VALUES (3, '王五'); INSERT INTO `employee` VALUES (4, '王六'); INSERT INTO `employee` VALUES (5, '王八'); CREATE TABLE `dept` ( `id` int NOT NULL AUTO_INCREMENT , `name` varchar(32) NOT NULL COMMENT '部门名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `dept` VALUES (1, '技术部'); INSERT INTO `dept` VALUES (2, '市场部'); INSERT INTO `dept` VALUES (3, '销售部'); CREATE TABLE `emp_dept`( emp_id int, dept_id int, PRIMARY KEY (`emp_id`,`dept_id`) ) insert into emp_dept values(1,1); insert into emp_dept values(2,1); insert into emp_dept values(3,3); insert into emp_dept values(4,2); insert into emp_dept values(5,3);
- 先导入前三个表,第三个表数据先别insert
- 导入mybatis_guanlian_select1项目,此项目的代码都已经用generator生成好了:密码930b
- 要实现关联查询,必须要第三张表,表的内容是两个关联表的主键,否则会造成数据冗余
一对一关联查询
- 生成的Employee实体类并没有关于部门的字段(因为原来的表没有部门的字段),所以我们现在要添加对应的部门字段
private Dept dept; //对应的get set方法省略
- 进入测试类
@Test public void test1() { List<Employee> employees = employeeMapper.selectByExample(null); for (Employee e : employees) { Dept dept = e.getDept(); System.out.println(e.getName()+"->"+((dept!=null)?dept.getName():"天堂")); } }
- 运行结果
由此可知此方法无法select到dept对象!DEBUG [main] - ==> Preparing: select id, name from employee DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 5 张三->天堂 李四->天堂 王五->天堂 王六->天堂 王八->天堂
- 在before里设置自动提交
sqlSession = sqlSessionFactory.openSession(true);
(或者在after里编写自动提交代码)sqlSession.commit()
(或者像这样编写)
4. 新增一名employee,name为龙哥,发现需要使用左连接才能查询到龙哥的信息:
select * from employee e
left join emp_dept ed on e.id=ed.emp_id
left join dept d on d.id=ed.dept_id;
- 查询结果
- 此时发现查询的数据有我们不需要的,我们修改一下sql语句
select e.id,e.name,dept_id,d.name 'dept_name' from employee e join emp_dept ed on e.id=ed.emp_id join dept d on d.id=ed.dept_id;
- 查询结果
- 编写一对一查询方法
- 初步Mapper.xml
此时发现映射的resultMap只有<select id=""> select e.id,e.name,dept_id,d.name 'dept_name' from employee e join emp_dept ed on e.id=ed.emp_id join dept d on d.id=ed.dept_id; </select>
因此我们需要改写resultMap
=-=完整版=-=
Mapper.java
List<Employee> selectAllEmployeeWithDept();
Mapper.xml
<resultMap id="BaseResultMap" type="net.suncaper.mybatisdemo.domain.Employee"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <association property="dept"> <id column="dept_id" property="id"/> <result column="dept_name" property="name"/> </association> </resultMap> <!--association是负责将Dept在一对一查询中关联起来--> <select id="selectAllEmployeeWithDept" resultMap="BaseResultMap"> select e.id,e.name,dept_id,d.name 'dept_name' from employee e left join emp_dept ed on e.id=ed.emp_id left join dept d on d.id=ed.dept_id; </select>
Test.java
@Test public void testSelectAllEmployeeWithDept() { List<Employee> employees = employeeMapper.selectAllEmployeeWithDept(); for (Employee e : employees) { Dept dept = e.getDept(); System.out.println(e.getName()+"->"+((dept!=null)?dept.getName():"天堂")); } }
执行结果
DEBUG [main] - ==> Preparing: select e.id,e.name,dept_id,d.name 'dept_name' from employee e left join emp_dept ed on e.id=ed.emp_id left join dept d on d.id=ed.dept_id; DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 6 张三->技术部 李四->技术部 王六->市场部 王五->销售部 王八->销售部 龙哥->天堂
最后把原来的baseResultMap复制一份,重新写一份新的,把原来添加到baseResultMap的代码都删掉
<resultMap id="EmployeeWithDeptResultMap" type="net.suncaper.mybatisdemo.domain.Employee" extends="BaseResultMap"> <!--注意这个extends!继承了BaseResultMap--> <!--就算继承了BaseResultMap,但是type是无法删除的,会导致编译无法通过--> <association property="dept"> <id column="dept_id" property="id"/> <result column="dept_name" property="name"/> </association> </resultMap> <select id="selectAllEmployeeWithDept" resultMap="EmployeeWithDeptResultMap"> select e.id,e.name,dept_id,d.name 'dept_name' from employee e left join emp_dept ed on e.id=ed.emp_id left join dept d on d.id=ed.dept_id; </select>
一对一延迟加载(引子)
- 再次进行(开始前的准备中的导入项目)
- 这次我们不要关联层,重新写数据库
drop database if exists mybatisdemo; create database mybatisdemo; use mybatisdemo; DROP TABLE IF EXISTS `employee`; CREATE TABLE `employee`( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL COMMENT '员工名称', dept_id int, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `employee`(`id`,`name`) VALUES (1, '张三'); INSERT INTO `employee`(`id`,`name`) VALUES (2, '李四'); INSERT INTO `employee`(`id`,`name`) VALUES (3, '王五'); INSERT INTO `employee`(`id`,`name`) VALUES (4, '王六'); INSERT INTO `employee`(`id`,`name`) VALUES (5, '王八'); CREATE TABLE `dept` ( `id` int NOT NULL AUTO_INCREMENT , `name` varchar(32) NOT NULL COMMENT '部门名称', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `dept` VALUES (1, '技术部'); INSERT INTO `dept` VALUES (2, '市场部'); INSERT INTO `dept` VALUES (3, '销售部'); update employee set dept_id = 1 where id = 1; update employee set dept_id = 1 where id = 2; update employee set dept_id = 3 where id = 3; update employee set dept_id = 2 where id = 4; update employee set dept_id = 3 where id = 5;
- 把导入项目里生成好的Mapper.java,Mapper.xml,都删掉,然后进行generator操作(注意把配置文件里的dept_emp table标签删除)
- 此时实验以下单表查询是否可以成功
- Test.java
@Test public void test1(){ List<Employee> employees = employeeMapper.selectByExample(null); for (Employee e : employees) { System.out.println(e.getId()+":"+e.getName()+"->"+e.getDeptId()); } }
- 运行结果
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 5 1:张三->1 2:李四->1 3:王五->3 4:王六->2 5:王八->3
- 可是我们现在要查询的是人和部门名,因此要修改employee实体类,添加实体Dept dept在Employee类里
注意保留generator生成的成员变量private Dept dept; //get set方法省略
- 此时要想知道怎么查,先写sql语句
select * from employee e left join dept d on e.dept_id=d.id;
- 查询结果
- 现在我们想先查询出Employee表的dept_id,然后通过dept_id查询需要的类别名,再包装显示出来
EmpMapper.xml<resultMap id="BaseResultMap" type="net.suncaper.mybatisdemo.domain.Employee"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="dept_id" jdbcType="INTEGER" property="deptId" /> <!--在查询到dept_id后进行第二次查找,调用deptMapper的selectByPrimaryKey进行查询dept的name操作,然后进行包装--> <!--property是查询到的映射,column是上一个查询的结果中的dept_id,select是调用DeptMapper中的方法所需要的写法,通过namespace映射到相应方法--> <association property="dept" column="dept_id" select="net.suncaper.mybatisdemo.mapper.DeptMapper.selectByPrimaryKey"/> </resultMap>
- Test.java
@Test public void test1(){ List<Employee> employees = employeeMapper.selectByExample(null); for (Employee e : employees) { Dept dept = e.getDept(); System.out.println(e.getName()+"-->"+dept!=null?dept.getName():""); } }
- 结果
很明显,结果是不正确的,发现是因为没有用好()DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 2(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - <== Total: 5 技术部 技术部 销售部 市场部 销售部
- Test.java
@Test public void test1(){ List<Employee> employees = employeeMapper.selectByExample(null); for (Employee e : employees) { Dept dept = e.getDept(); System.out.println(e.getName()+"-->"+((dept!=null)?dept.getName():"")); } }
- 运行结果
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee DEBUG [main] - ==> Parameters: DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 1(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 3(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 2(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - <== Total: 5 张三-->技术部 李四-->技术部 王五-->销售部 王六-->市场部 王八-->销售部
- 根据查询结果我们可以知道,这种方法使用了大量的sql语句,因为缓存的缘故,共有4条语句,效率很低
为了提升这种写法的效率,我们引入延迟加载的概念。
一对一延迟加载(懒加载)
默认不是延迟加载
- 倘若我们将测试类的代码写为
- Test.java
@Test public void test1(){ List<Employee> employees = employeeMapper.selectByExample(null); for (Employee e : employees) { System.out.println(e.getName()); } }
- 查询结果
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee DEBUG [main] - ==> Parameters: DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 1(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 3(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - ====> Preparing: select id, name from dept where id = ? DEBUG [main] - ====> Parameters: 2(Integer) DEBUG [main] - <==== Total: 1 DEBUG [main] - <== Total: 5 张三 李四 王五 王六 王八
- 依然使用了如此多的sql语句,在这里我们使用延迟加载,在EmployeeMapper.xml中的association标签中添加属性fetchType并设置值为lazy(此时就是延迟加载)
- Mapper.xml
<resultMap id="BaseResultMap" type="net.suncaper.mybatisdemo.domain.Employee"> <id column="id" jdbcType="INTEGER" property="id" /> <result column="name" jdbcType="VARCHAR" property="name" /> <result column="dept_id" jdbcType="INTEGER" property="deptId" /> <!--在查询到dept_id后进行第二次查找,调用deptMapper的selectByPrimaryKey进行查询dept的name操作,然后进行包装--> <association property="dept" column="dept_id" select="net.suncaper.mybatisdemo.mapper.DeptMapper.selectByPrimaryKey" fetchType="lazy"/> </resultMap>
- 此时执行test后结果为
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee DEBUG [main] - ==> Parameters: DEBUG [main] - <== Total: 5 张三 李四 王五 王六 王八
- 此时延迟查询就成功了
延迟加载例子
- Test.java
@Test public void test3(){ Employee employee = employeeMapper.selectByPrimaryKey(3); System.out.println(employee.getName()); }
- 运行结果
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee where id = ? DEBUG [main] - ==> Parameters: 3(Integer) DEBUG [main] - <== Total: 1 王五
- Test.java
@Test public void test3(){ Employee employee = employeeMapper.selectByPrimaryKey(3); System.out.println(employee.getName()); System.out.println(employee.getDept().getName()); }
- 执行结果
DEBUG [main] - ==> Preparing: select id, name, dept_id from employee where id = ? DEBUG [main] - ==> Parameters: 3(Integer) DEBUG [main] - <== Total: 1 王五 DEBUG [main] - ==> Preparing: select id, name from dept where id = ? DEBUG [main] - ==> Parameters: 3(Integer) DEBUG [main] - <== Total: 1 销售部
可以体现出延迟加载,在需要的时候再执行查询
可以在mybatis-config.xml中configuration标签下添加setting标签设置成如下代码可以实现全部懒加载
##一对多关联查询
- 修改Dept实体类
private List<Employee> employeeList; //get set方法省略
- Test.java
@Test public void test1(){ Dept dept = deptMapper.selectByPrimaryKey(2); System.out.println(dept.getName()); }
- 运行结果
DEBUG [main] - ==> Preparing: select id, name from dept where id = ? DEBUG [main] - ==> Parameters: 2(Integer) DEBUG [main] - <== Total: 1 市场部
- 目标sql语句
select d.id,d.name,e.id 'e_id',e.name 'e_name' from dept d join employee e on d.id=e.dept_id where d.id=3;
- Mapper.java
Dept selectByPrimaryKeyWithEmp(Integer id);
- Mapper.xml
<resultMap id="BaseResultMap1" type="net.suncaper.mybatisdemo.domain.Dept" extends="BaseResultMap"> <!--因为反射无法读取employeeListの具体的泛型,因此在collection中添加ofType属性--> <collection property="employeeList" ofType="Employee"> <id property="id" column="e_id"/> <result property="name" column="e_name"/> </collection> </resultMap> <select id="selectByPrimaryKeyWithEmp" parameterType="int" resultMap="BaseResultMap1"> select d.id,d.name,e.id 'e_id',e.name 'e_name' from dept d join employee e on d.id=e.dept_id where d.id=#{value}; </select>
- Test.java
@Test public void test1(){ Dept dept = deptMapper.selectByPrimaryKeyWithEmp(3); System.out.println(dept.getName()); System.out.println("----------------"); List<Employee> employeeList = dept.getEmployeeList(); if(employeeList!=null && employeeList.size()>0){ for (Employee employee : employeeList) { System.out.println(employee.getName()); } } }
- 运行结果
List,Set,数组都可以执行DEBUG [main] - ==> Preparing: select d.id,d.name,e.id 'e_id',e.name 'e_name' from dept d join employee e on d.id=e.dept_id where d.id=?; DEBUG [main] - ==> Parameters: 3(Integer) DEBUG [main] - <== Total: 2 销售部 ---------------- 王五 王八
- 如果以后做页面,没有数据是空的很丑怎么办
if(employeeList!=null && employeeList.size()==0){ return null; }