头疼的一对一,多对一,一对多写法
我们知道,相比较hibernate,mybatis的一对一,一对多都比较繁琐,hibernate可以直接在实体类里面配置好映射关系,获取值的时候就能把一对一和一对多的对象带出来了,而mybatis需要用到resultMap,需要在resultMap中把实体类与表字段的对应关系一一写出来,如果遇到关联表有相同字段名,还要写别名,画风往往是这样的:
看着好像还行,但是要知道有些表可是有几十个字段的, 不仅要写resultMap,还要在sql中把所有列名写出来,这酸爽,看着就头疼,幸亏有个大杀器autoMapping,可以大大简化mapper.xml的代码量,下面来讲解下使用方法,让你对mybatis的一对一,一对多,多对一不再头疼.
autoMapping
有三个自动映射级别:
- NONE - 禁用自动映射。只会设置手动映射的属性。
- PARTIAL - 除了内部定义了嵌套结果映射的结果(联接)之外,其他都会自动映射。
- FULL - 自动映射所有内容。
默认是partial,也就是当你resultMap中包含 association,collection等嵌套结果时是不会自动映射的,但是可以在resultMap中配置autoMapping="true"手动开启,为啥默认配置是partical自然是有原因的,因为表关联的时候很可能会引入相同的字段,这时自动映射可能会出现错误的结果,不过这个问题是有办法规避的
试验用sql
我们定义两张表,department部门表和employee员工表,每个员工属于一个部门,员工与部门是多对一的关系,部门与员工是一对多的关系,一对一的写法其实和多对一一样,就不另外介绍了
CREATE TABLE `department` (`id` int(0) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '备注',`create_time` date NULL DEFAULT NULL COMMENT '成立时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;CREATE TABLE `employee` (`id` int(0) NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`birthday` date NULL DEFAULT NULL,`job` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`department_id` int(0) NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
多对一
定义一个EmployeeDTO,里面有员工所属部门的对象,在员工数据中返回所属部门,就实现了多对一的效果
@Getter
@Setter
public class EmployeeDTO extends Employee {private Department department;
}
多对一有两种实现方式,一种是表连接然后直接映射,还有一种是先查主表,再查关联表
表连接方式
可以看到,下面的代码开启了自动映射,并且sql中没有写字段名,直接通过*来取所有字段,这样是最简洁的写法,后续就算department加字段我们也不需要改resultMap了,但是这种写法遇到同名的字段就会有问题
<mapper namespace="cn.hollycloud.server.mapper.EmployeeMapper"><!-- 错误的示范 --><resultMap id="BaseResultMap" type="cn.hollycloud.server.dto.EmployeeDTO" autoMapping="true"><id column="id" property="id" /><association property="department" javaType="cn.hollycloud.server.entity.Department" autoMapping="true"></association></resultMap><select id="listAll" resultMap="BaseResultMap">select e.*,d.* from employee eleft join department d on e.department_id = d.id</select></mapper>
返回结果中可以看到,department中注入了员工的id和名字,这显然不是我们想要的结果,该如何解决呢?
我们需要把 子表中同名的字段命名为别名,并且在resultMap中做单独的映射,正确版本如下:
<mapper namespace="cn.hollycloud.server.mapper.EmployeeMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="cn.hollycloud.server.dto.EmployeeDTO" autoMapping="true"><id column="id" property="id" /><association property="department" javaType="cn.hollycloud.server.entity.Department" autoMapping="true"><id column="d_id" property="id" /><result column="d_name" property="name" /></association></resultMap><select id="listAll" resultMap="BaseResultMap">select e.*,d.id d_id,d.name d_name,d.* from employee eleft join department d on e.department_id = d.id</select>
</mapper>
可以看到,结果是正确的
关于重名的疑虑
看看上面的sql,select e.*,d.id d_id,d.name d_name,d.*,这个sql展开就是select e.id,e.name,d.id d_id,d.name d_name,d.id,d.name,其实返回的字段还是有重名的,如何能确定resultMap一定能找到正确的值呢?我们可以看看源码
DefaultResultSetHandler的getRowValue
一路跟进去我们发现最后其实就是调用的resultSet.getString(columnName),而这个方法优先返回第一个遇到的列的值,所以employee注入的是e.name而不是d.name,但是如果你写成select d.*,e.*也就是把d放前面就会注入部门的name,这点需要注意
子查询方式
子查询方式实现方式跟hibernate类似,先是查主表,下面例子中就是查员工表,再根据员工表中的department_id去查所属部门,优点是不用考虑重名问题,直接能够自动映射好,缺点是效率没表连接高,比如主表有10条数据,它先查出10条数据,再一个个根据id去查子表,要执行11条sql
association中column="{id = department_id}"的意思是把主表中department_id赋值给id,再把id作为参数传入getDepartmentById中
<mapper namespace="cn.hollycloud.server.mapper.EmployeeMapper"><select id="listAll1" resultMap="BaseResultDto">select * from employee</select><select id="getDepartmentById" resultType="cn.hollycloud.server.entity.Department">select * from department where id = #{id}</select><!-- 通用查询映射结果 --><resultMap id="BaseResultDto" type="cn.hollycloud.server.dto.EmployeeDTO"><id column="id" property="id" /><association property="department" select="getDepartmentById" column="{id = department_id}"/></resultMap>
</mapper>
一对多
一对多跟多对一其实差不多,只要association改成collection就行了,下面的例子中返回的部门数据中包含部门下所有员工,就是一对多了,一对多也有两种实现方式,表连接和子查询,表连接优点是效率高,缺点是需要考虑重名,不能分页,子查询优点是写法简洁,可以分页,缺点是效率不高
@Getter
@Setter
public class DepartmentDTO extends Department {private List<Employee> employees;
}
表连接方式
<mapper namespace="cn.hollycloud.server.mapper.DepartmentMapper"><resultMap id="BaseResultMap" type="cn.hollycloud.server.dto.DepartmentDTO" autoMapping="true"><id column="id" property="id" /><collection property="employees" ofType="cn.hollycloud.server.entity.Employee" autoMapping="true"><id column="e_id" property="id" /><result column="e_name" property="name" /></collection></resultMap><select id="listAll" resultMap="BaseResultMap">select d.*, e.id e_id, e.name e_name, e.* from department d left join employee e on e.department_id = d.id</select>
</mapper>
子查询方式
<mapper namespace="cn.hollycloud.server.mapper.DepartmentMapper"><resultMap id="BaseResultDTO" type="cn.hollycloud.server.dto.DepartmentDTO"><id column="id" property="id" /><collection property="employees" ofType="cn.hollycloud.server.entity.Employee"select="listByDepartmentId" column="{id = id}"/></resultMap><select id="listByDepartmentId" resultType="cn.hollycloud.server.entity.Employee">select * from employee where department_id = #{id}</select><select id="listAll1" resultMap="BaseResultDTO">select * from department</select>
</mapper>