1、前言
在前面SQL映射文件的介绍中,说到resultMap元素中有两个标签是用来做关联查询操作的,也就是一对一,一对多,对应到Mybatis中的标签分别是association和collection标签。它们在实际的项目中,会经常用到关联表的查询,因为实际的项目中不可能是对单表的查询,经常会有一对一,一对多等情况,我们可以使用这两个标签来配合实现。在Java实体对象中,一对一属性使用包装对象来实现,一对多属性使用List或者Set来实现。
association和collection二者标签的内部属性基本是一致的,它们的属性介绍如下(红色标注表示常用):
- property:映射实体类属性名。
- column:映射数据库字段名或者其别名(这个别名是数据库起的,如 select username as name)。
- javaType:映射java类型。
- jdbcType:映射数据库类型。
- ofType:映射集合的类型(注意:javaType是用来指定pojo中属性的类型,而ofType指定的是映射到list集合属性中pojo的类型,也就是尖括号的泛型private List<User> users)。
- select:用于加载复杂类型属性的映射语句的id (全限定名加方法,方法名后面无括号,例如:com.thr.mapper.UserMapper.selectAllUser),它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的第二个例子。
- fetchType:延迟加载,lazy打开延迟加载,eager积极加载。指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。
- resultMap:不使用嵌套模式,而是将此关联的嵌套结果集映射到一个外部的<resultMap>标签中,然后通过 id 进行引入。
- resultSet:指定用于加载复杂类型的结果集名字。
- autoMapping:自动封装,如果数据库字段和javaBean的字段名一样,可以使用这种方式,但是不建议采取,还是老老实实写比较稳妥,如果非要使用此功能,那就在全局配置中加上mapUnderscoreToCamelCase=TRUE,它会使经典数据库字段命名规则翻译成javaBean的经典命名规则,如:a_column翻译成aColumn。
- columnPrefix:关联多张表查询时,为了使列明不重复,使用此功能可以减少开发量。
- foreignColumn:指定外键对应的列名,指定的列将与父类型中 column 的给出的列进行匹配。
- notNullColumn:不为空的列,如果指定了列,那么只有当字段不为空时,Mybatis才会真正创建对象,才能得到我们想要的值。
- typeHandler:数据库与Java类型匹配处理器(可以参考前面的TypeHandler部分)。
如果你想对这些属性有更加深入了解的话可以自行去参考Mybatis的官方文档,链接:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
2、案例分析
我们以典型的 员工(Employee)和部门(Department)为例:
- 一个员工只能在一个部门;Employee—>Department(一对一)
- 一个部门可以包含多个员工;Department—>Employee(一对多)
下面我们分别创建 员工表:t_employee 和 部门表:t_department:
对应mysql的sql脚本如下:
-- ---------------------------- -- Table structure for t_department -- ---------------------------- DROP TABLE IF EXISTS `t_department`; CREATE TABLE `t_department` ( `department_id` int(11) NOT NULL AUTO_INCREMENT, `department_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`department_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of t_department -- ---------------------------- INSERT INTO `t_department` VALUES (1, '开发部'); INSERT INTO `t_department` VALUES (2, '人力资源部'); INSERT INTO `t_department` VALUES (3, '市场营销部'); INSERT INTO `t_department` VALUES (4, '财务部'); INSERT INTO `t_department` VALUES (5, '行政部'); INSERT INTO `t_department` VALUES (6, '监察部'); INSERT INTO `t_department` VALUES (7, '客服服务部'); -- ---------------------------- -- Table structure for t_employee -- ---------------------------- DROP TABLE IF EXISTS `t_employee`; CREATE TABLE `t_employee` ( `employee_id` int(11) NOT NULL AUTO_INCREMENT, `employee_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `employee_age` int(255) NULL DEFAULT NULL, `employee_sex` int(255) NULL DEFAULT NULL, `employee_email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `employee_address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `department_id` int(11) NULL DEFAULT NULL, PRIMARY KEY (`employee_id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; -- ---------------------------- -- Records of t_employee -- ---------------------------- INSERT INTO `t_employee` VALUES (1, '唐浩荣', 23, 1, '15477259875@163.com', '中国上海浦东区', 1); INSERT INTO `t_employee` VALUES (2, '黄飞鸿', 32, 1, '86547547@qq.com', '大清广东', 2); INSERT INTO `t_employee` VALUES (3, '十三姨', 18, 0, '520520520@gmail.com', '大清广东', 3); INSERT INTO `t_employee` VALUES (4, '纳兰元述', 28, 1, '545627858@qq.com', '大清京师', 5); INSERT INTO `t_employee` VALUES (5, '梁宽', 31, 1, '8795124578@qq.com', '大清广东', 7); INSERT INTO `t_employee` VALUES (6, '蔡徐坤', 20, 0, '4257895124@gmail.com', '四川成都', 4); INSERT INTO `t_employee` VALUES (7, '杨超越', 21, 0, '8746821252@qq.com', '中国北京', 7); INSERT INTO `t_employee` VALUES (8, '马保国', 66, 1, '6666666666@qq.com', '广东深圳', 6); INSERT INTO `t_employee` VALUES (9, '马牛逼', 45, 1, 'asdfg45678@163.com', '湖北武汉', 3);
注意:在MyBatis中主要有这两种方式实现关联查询:
- 嵌套结果:使用嵌套映射的方式来处理关联结果的子集。
- 分步查询:通过 select 属性来执行另外一个 SQL 映射语句来返回预期的复杂类型。select属性的规则是全限定名加方法名,例如:com.thr.mapper.UserMapper.selectAllUser,方法名后面无括号。
所以下面我们就通过代码来学习这两种方式实现Mybatis的关联查询。
3、嵌套结果
①、分别定义Employee和Department实体类
Employee实体类:
/** * 员工实体类 */ public class Employee { //员工id private Integer empId; //员工名称 private String empName; //员工年龄 private Integer empAge; //员工性别 private Integer empSex; //员工邮箱 private String empEmail; //员工地址 private String empAddress; //员工所属部门,和部门表构成一对一的关系,一个员工只能在一个部门 private Department department; //getter、setter、toString方法和一些构造方法省略... }
Department实体类:
/** * 部门实体类 */ public class Department { //部门id private Integer deptId; //部门名称 private String deptName; //getter、setter、toString方法和一些构造方法省略... }
②、创建EmployeeMapper接口和EmployeeMapper.xml 文件
EmployeeMapper接口:
/** * 员工Mapper接口 */ public interface EmployeeMapper { //查询所有数据 List<Employee> selectAll(); //根据员工id查询数据 Employee selectEmpByEmpId(@Param("id") Integer empId); }
EmployeeMapper.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.thr.mapper.EmployeeMapper"> <resultMap id="employeeMap" type="com.thr.pojo.Employee"> <id property="empId" column="employee_id"/> <result property="empName" column="employee_name"/> <result property="empAge" column="employee_age"/> <result property="empSex" column="employee_sex"/> <result property="empEmail" column="employee_email"/> <result property="empAddress" column="employee_address"/> <!-- 一对一关联对象 --> <association property="department" javaType="department"> <id property="deptId" column="department_id"/> <result property="deptName" column="department_name"/> </association> </resultMap> <!-- 查询所有数据--> <select id="selectAll" resultMap="employeeMap"> SELECT * FROM t_employee e, t_department d where e.department_id=d.department_id </select> <!--根据员工id查询数据--> <select id="selectEmpByEmpId" parameterType="int" resultMap="employeeMap"> SELECT * FROM t_employee e, t_department d where e.department_id=d.department_id and e.employee_id= #{id} </select> </mapper>
③、创建数据库连接文件和日志文件
db.properties文件:
#数据库连接配置 database.driver=com.mysql.cj.jdbc.Driver database.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8 database.username=root database.password=root
log4j.properties文件:
log4j.rootLogger=DEBUG, Console #Console log4j.appender.Console=org.apache.log4j.ConsoleAppender log4j.appender.Console.layout=org.apache.log4j.PatternLayout log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n log4j.logger.java.sql.ResultSet=INFO log4j.logger.org.apache=INFO log4j.logger.java.sql.Connection=DEBUG log4j.logger.java.sql.Statement=DEBUG log4j.logger.java.sql.PreparedStatement=DEBUG
④、注册 EmployeeMapper.xml 文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!--引入properties文件--> <properties resource="db.properties"/> <!--配置别名--> <typeAliases> <!-- 对包进行扫描,可以批量进行别名设置,设置规则是:获取类名称,将其第一个字母变为小写 --> <package name="com.thr.pojo"/> </typeAliases> <!-- 配置环境.--> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"></transactionManager> <dataSource type="POOLED"> <property name="driver" value="${database.driver}"/> <property name="url" value="${database.url}"/> <property name="username" value="${database.username}"/> <property name="password" value="${database.password}"/> </dataSource> </environment> </environments> <!--注册mapper,通过扫描的方式--> <mappers> <package name="com.thr.mapper"/> </mappers> </configuration>
⑤、编写测试代码
package com.thr.test; import com.thr.mapper.EmployeeMapper; import com.thr.pojo.Employee; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; /** * 测试代码 */ public class MybatisTest { //定义 SqlSession private SqlSession sqlSession = null; //定义 EmployeeMapper对象 private EmployeeMapper mapper = null; @Before//在测试方法执行之前执行 public void getSqlSession(){ //1、加载 mybatis 全局配置文件 InputStream is = MybatisTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml"); //2、创建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //3、根据 sqlSessionFactory 产生 session sqlSession = sqlSessionFactory.openSession(); //4、创建Mapper接口的的代理对象,getMapper方法底层会通过动态代理生成UserMapper的代理实现类 mapper = sqlSession.getMapper(EmployeeMapper.class); } @After//在测试方法执行完成之后执行 public void destroy() throws IOException { sqlSession.commit(); sqlSession.close(); } //查询所有数据 @Test public void testSelectAll(){ List<Employee> employees = mapper.selectAll(); for (Employee employee : employees) { System.out.println(employee); } } //根据员工id查询数据 @Test public void testSelectEmpByEmpId(){ Employee employee = mapper.selectEmpByEmpId(1); System.out.println(employee); } }
⑥、运行结果
项目整体目录:
查询所有数据:
根据员工id查询数据:
4、分步查询
分步查询的这种方式是通过association标签中的select属性来完成,它需要执行另外一个 SQL 映射语句来返回预期的复杂类型,并且会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。所以我们必须在关联的另一个Mapper接口中创建一个根据 id 查询数据的方法,并且表字段和实体属性如果不同还要进行映射。
①、更改EmployeeMapper.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.thr.mapper.EmployeeMapper"> <resultMap id="employeeMap" type="com.thr.pojo.Employee"> <id property="empId" column="employee_id"/> <result property="empName" column="employee_name"/> <result property="empAge" column="employee_age"/> <result property="empSex" column="employee_sex"/> <result property="empEmail" column="employee_email"/> <result property="empAddress" column="employee_address"/> <!-- 一对一关联对象,注意:select方式需要加column属性,column属性会从当前查询出的指定列检索数据, 这里为t_employee表中的department_id,然后作为参数传递给目标的select语句--> <association property="department" column="department_id" javaType="department" select="com.thr.mapper.DepartmentMapper.selectDeptByDeptId"/> </resultMap> <!-- 查询所有数据--> <select id="selectAll" resultMap="employeeMap"> SELECT * FROM t_employee </select> <!--根据员工id查询数据--> <select id="selectEmpByEmpId" parameterType="int" resultMap="employeeMap"> SELECT * FROM t_employee where employee_id= #{id} </select> </mapper>
②、创建DepartmentMapper和DepartmentMapper.xml文件
DepartmentMapper接口:
/** * 部门Mapper接口 */ public interface DepartmentMapper { //根据部门id查询数据 Department selectDeptByDeptId(@Param("id") Integer deptId); }
DepartmentMapper.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.thr.mapper.DepartmentMapper"> <resultMap id="departmentMap" type="com.thr.pojo.Department"> <id property="deptId" column="department_id"/> <result property="deptName" column="department_name"/> </resultMap> <!--根据部门id查询--> <select id="selectDeptByDeptId" parameterType="int" resultMap="departmentMap"> select * from t_department where department_id = #{id} </select> </mapper>
③、运行结果
查询所有数据:
根据员工id查询数据:
可以发现使用这种方式明显多执行了很多SQL语句,所以肯定会导致查询的效率变低,但是这种方式也有好处,那就是可以延迟加载。