Mybatis 学习之路其四:级联
一、前言
我参加工作的初期,对于mybatis还不太了解,开发的过程更多是参考公司大牛的代码,然后依样画葫芦。当时设计到 A 类里包含 B 类,然后查询 A 类相关信息(包含B类)的时候,我当时还不懂级联,使用的是一个笨方法,重新建了一个类,这个类包含了 A 类和 B 类的所有信息。这是一个非常笨的方法。事实上,利用 mybatis 的级联功能,能够很简单的查询一对一,和一对多的关系。
二、一对一关系查询
在我们使用的 mysql 的测试数据中个,我们选出一个一对一的关系进行测试,比如:员工表,和员工-部门号表。前者是员工的基本信息,我们前面使用了很多次,后面是员工号和部门号的对应关系。现在,我们要在获取员工基本信息的时候,把员工的部门号一并获取过来。
首先,修改下员工的信息的 javabean:
1 import java.util.Date; 2 import java.util.List; 3 import salary.model.SalaryModel; 4 public class EmployeeModel { 5 private int empNo; 6 private Date birthDate; 7 private String firstName; 8 private String lastName; 9 private String gender; 10 private Date hireDate; 11 private String deptNo; 12 private List<SalaryModel> salaries = null; 13 public int getEmpNo() { 14 return empNo; 15 } 16 public void setEmpNo(int empNo) { 17 this.empNo = empNo; 18 } 19 public Date getBirth_date() { 20 return birthDate; 21 } 22 public void setBirth_date(Date birth_date) { 23 this.birthDate = birth_date; 24 } 25 public String getFirstName() { 26 return firstName; 27 } 28 public void setFirstName(String firstName) { 29 this.firstName = firstName; 30 } 31 public String getLastName() { 32 return lastName; 33 } 34 public void setLastName(String lastName) { 35 this.lastName = lastName; 36 } 37 public String getGender() { 38 return gender; 39 } 40 public void setGender(String gender) { 41 this.gender = gender; 42 } 43 public Date getHireDate() { 44 return hireDate; 45 } 46 public void setHireDate(Date hireDate) { 47 this.hireDate = hireDate; 48 } 49 50 public String toString() { 51 StringBuilder strBuilder = new StringBuilder(); 52 strBuilder.append("empNo : "); 53 strBuilder.append(empNo); 54 strBuilder.append(" firstName : "); 55 strBuilder.append(firstName); 56 strBuilder.append(" lastName : "); 57 strBuilder.append(lastName); 58 strBuilder.append(" deptNo : "); 59 strBuilder.append(deptNo); 60 strBuilder.append("\nsalaries:\n"); 61 if(salaries != null) { 62 for(SalaryModel index : salaries) { 63 strBuilder.append(index.toString()); 64 strBuilder.append("\n"); 65 } 66 } 67 return strBuilder.toString(); 68 } 69 public List<SalaryModel> getSalaries() { 70 return salaries; 71 } 72 public void setSalaries(List<SalaryModel> salaries) { 73 this.salaries = salaries; 74 } 75 public String getDeptNo() { 76 return deptNo; 77 } 78 public void setDeptNo(String deptNo) { 79 this.deptNo = deptNo; 80 } 81 }
为了后面测试一对多关系,我提前在 bean 里加了存储工资信息的 List。
然后针对员工-部门号表,我们需要建立对应的 bean,dao,mapper:
bean:
1 import java.util.Date; 2 3 public class DeptManager { 4 private int empNo; 5 private String deptNo; 6 private Date fromDate; 7 private Date toDate; 8 public int getEmpNo() { 9 return empNo; 10 } 11 public void setEmpNo(int empNo) { 12 this.empNo = empNo; 13 } 14 public String getDeptNo() { 15 return deptNo; 16 } 17 public void setDeptNo(String deptNo) { 18 this.deptNo = deptNo; 19 } 20 public Date getFromDate() { 21 return fromDate; 22 } 23 public void setFromDate(Date fromDate) { 24 this.fromDate = fromDate; 25 } 26 public Date getToDate() { 27 return toDate; 28 } 29 public void setToDate(Date toDate) { 30 this.toDate = toDate; 31 } 32 33 }
dao:
public interface DeptManagerDao { String getDeptNoByEmpNo(int empNo); }
mapper:
<?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 = "employee.dao.DeptManagerDao"> <resultMap type="employee.model.DeptManager" id="deptManager"> <result column = "emp_no" property = "empNo"/> <result column = "dept_no" property = "deptNo"/> <result column = "from_date" property = "fromDate"/> <result column = "to_date" property = "toDate"/> </resultMap> <select id = "getDeptNoByEmpNo" parameterType = "int" resultType = "java.lang.String"> select dept_no from dept_manager where emp_no = #{empNo} </select> </mapper>
然后记得在 mybatis-config 里加载这个新建的 mapper:
<mapper resource="employee/mapper/DeptManagerMapper.xml"/>
到目前为止,没有任何特殊的地方,也没有关于一对一的任何操作,接下来是重点,首先,我们再 employee 的 resultMap 里加入我们新建的 dempNo,但是加入的方式和其他不同:
<association property = "deptNo" column = "emp_no" select = "employee.dao.DeptManagerDao.getDeptNoByEmpNo"/>
首先,它的元素不在是 result,而是 association,association 这里我们用到了 3 个属性:
property 和 result 元素一样,对应了 javaBean 里的属性;
column 对应了数据库里的列,注意,这个列不是去和 column 对应的,而是作为参数去传给其他 sql 的,传给谁?;
select 决定了参数的传递对象。
我们先看,这样配置之后,我们再获取 employee 信息的时候发生了什么。
运行测试代码:
1 SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession(); 2 EmployeeDao employeeDao = sqlSession.getMapper(EmployeeDao.class); 3 EmployeeModel model = employeeDao.getEmployeeByEmpNo(111784); 4 System.out.print(model);
然后 log4j 的输出是:
[DEBUG] 2018-10-01 17:17:44,817: [Thread-main : org.apache.ibatis.datasource.pooled.PooledDataSource] Created connection 936906727. [DEBUG] 2018-10-01 17:17:44,817: [Thread-main : org.apache.ibatis.transaction.jdbc.JdbcTransaction] Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@37d80fe7] [DEBUG] 2018-10-01 17:17:44,821: [Thread-main : employee.dao.EmployeeDao.getEmployeeByEmpNo] ==> Preparing: select emp_no,first_name,birth_date,last_name,gender,hire_date from employees where emp_no = ? [DEBUG] 2018-10-01 17:17:44,865: [Thread-main : employee.dao.EmployeeDao.getEmployeeByEmpNo] ==> Parameters: 111784(Integer) [DEBUG] 2018-10-01 17:17:44,911: [Thread-main : employee.dao.DeptManagerDao.getDeptNoByEmpNo] ====> Preparing: select dept_no from dept_manager where emp_no = ? [DEBUG] 2018-10-01 17:17:44,911: [Thread-main : employee.dao.DeptManagerDao.getDeptNoByEmpNo] ====> Parameters: 111784(Integer) [DEBUG] 2018-10-01 17:17:44,914: [Thread-main : employee.dao.DeptManagerDao.getDeptNoByEmpNo] <==== Total: 1
我们发现,在执行了 employee.mapper 里的根据 emp_no 查询员工信息之后,mybatis 又自动执行了一条 sql,这条 sql 就是我们再 select 属性里指定的方法,而这个方法是有一个参数的,系统吧 column 指定的列的值传递了过去,然后将这条 sql 获取的值,装填到 employee 的 deptNo 属性中去。
System.out.println 的结果是:empNo : 111784 firstName : Marjo lastName : Giarratana deptNo : d009 (截取了关键部分)。
可以猜出,这条 sql 的发起点是在 resultmap 里。
光是这样,还不足以体现 mybatis 级联的强大,毕竟上述的例子,只要一个 left join 就能搞定,无需这么麻烦。因为,我们需要从其他表获取的不过一个字段而已,但是如果我们获取的是一个对象,就非常麻烦了,又或者是一个对象的集合。下面的一对多的级联就是一个对象的集合。
三、一对多关联查询
这次我们用工资表和员工表关联,每个月都会发工资,所以员工表和工资表必定能是一对多关联。
还是先把 工资表相关的准备工作做好
bean:
1 import java.util.Date; 2 3 public class SalaryModel { 4 private int empNo; 5 private int salary; 6 private Date fromDate; 7 private Date toDate; 8 9 public int getEmpNo() { 10 return empNo; 11 } 12 13 public void setEmpNo(int empNo) { 14 this.empNo = empNo; 15 } 16 17 public int getSalary() { 18 return salary; 19 } 20 21 public void setSalary(int salary) { 22 this.salary = salary; 23 } 24 25 public Date getFromDate() { 26 return fromDate; 27 } 28 29 public void setFromDate(Date fromDate) { 30 this.fromDate = fromDate; 31 } 32 33 public Date getToDate() { 34 return toDate; 35 } 36 37 public void setToDate(Date toDate) { 38 this.toDate = toDate; 39 } 40 41 public String toString() { 42 return "empNo: " + empNo + " salary: " + salary + " from : " + fromDate + " to : " + toDate + "\n"; 43 } 44 }
dao:
import java.util.List; import salary.model.SalaryModel; public interface SalaryDao { List<SalaryModel> getSalaryByEmpNo(int empNo); }
mapper:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE mapper 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 5 <mapper namespace="salary.dao.SalaryDao"> 6 <resultMap type = "salary.model.SalaryModel" id = "salary"> 7 <result column = "emp_no" property = "empNo"/> 8 <result column = "salary" property = "salary"/> 9 <result column = "from_date" property = "fromDate"/> 10 <result column = "to_date" property = "toDate"/> 11 </resultMap> 12 <sql id = "salaryCols"> 13 emp_no,salary,from_date,to_date 14 </sql> 15 <select id = "getSalaryByEmpNo" resultMap = "salary"> 16 select <include refid = "salaryCols" /> from salaries where emp_no = #{empNo} 17 </select> 18 </mapper>
mybatis-config 加载:
<mapper resource="salary/mapper/SalaryMapper.xml"/>
下面是重点,这次我们还是在 resultMap 里做点手脚,但是不再是 association,一对多关联时,应该使用 collection:
<collection property = "salaries" column = "emp_no" select = "salary.dao.SalaryDao.getSalaryByEmpNo" />
属性作用和 association 一致,然后我们运行测试程序:
输出:
empNo : 111784 firstName : Marjo lastName : Giarratana deptNo : d009 salaries: empNo: 111784 salary: 40000 from : Fri Feb 12 08:00:00 CST 1988 to : Sat Feb 11 08:00:00 CST 1989 empNo: 111784 salary: 41189 from : Sat Feb 11 08:00:00 CST 1989 to : Sun Feb 11 08:00:00 CST 1990 empNo: 111784 salary: 44026 from : Sun Feb 11 08:00:00 CST 1990 to : Mon Feb 11 08:00:00 CST 1991 empNo: 111784 salary: 48315 from : Mon Feb 11 08:00:00 CST 1991 to : Tue Feb 11 08:00:00 CST 1992 empNo: 111784 salary: 48079 from : Tue Feb 11 08:00:00 CST 1992 to : Wed Feb 10 08:00:00 CST 1993 empNo: 111784 salary: 48181 from : Wed Feb 10 08:00:00 CST 1993 to : Thu Feb 10 08:00:00 CST 1994 empNo: 111784 salary: 48273 from : Thu Feb 10 08:00:00 CST 1994 to : Fri Feb 10 08:00:00 CST 1995 empNo: 111784 salary: 49770 from : Fri Feb 10 08:00:00 CST 1995 to : Sat Feb 10 08:00:00 CST 1996 empNo: 111784 salary: 51900 from : Sat Feb 10 08:00:00 CST 1996 to : Sun Feb 09 08:00:00 CST 1997 empNo: 111784 salary: 52121 from : Sun Feb 09 08:00:00 CST 1997 to : Mon Feb 09 08:00:00 CST 1998 empNo: 111784 salary: 54187 from : Mon Feb 09 08:00:00 CST 1998 to : Tue Feb 09 08:00:00 CST 1999 empNo: 111784 salary: 54138 from : Tue Feb 09 08:00:00 CST 1999 to : Wed Feb 09 08:00:00 CST 2000 empNo: 111784 salary: 54183 from : Wed Feb 09 08:00:00 CST 2000 to : Thu Feb 08 08:00:00 CST 2001 empNo: 111784 salary: 57946 from : Thu Feb 08 08:00:00 CST 2001 to : Fri Feb 08 08:00:00 CST 2002 empNo: 111784 salary: 58189 from : Fri Feb 08 08:00:00 CST 2002 to : Fri Jan 01 08:00:00 CST 9999
其道理和 association 是一致的,这里我们成功的将获取到的工资信息装填到了 employee 的属性 salaries(List)中。
四、mybatis 小结
到目前为止,mybatis 的基础功能已经差不多结束了,mybatis 还有很多强大的功能,比如插件在以后再继续学习。本着由简到难的方式,mybatis 暂时告一段落,接下来是 SSM 框架的 spring 部分。