• 自定义映射resultMap(处理一对多、多对一映射)


    目录

    1.处理字段名和属性名不一致的情况

    2.多对一映射处理

    2.1 使用association处理多对一的映射关系

    2.2 使用分布查询处理多对一的映射关系

    2.3 分布查询的优点

    3.一对多的映射处理

    3.1 通过collection处理一对多

    3.2 通过分布查询处理一对多


    1.处理字段名和属性名不一致的情况

    这里注意一下:我们的表用的是下划线,而实体类用的是驼峰命名。

    这时候通过mybatis查询,这个时候字段跟属性肯定是对应不上的。

    我们之前都写过JDBC工具类,其实就是把查询出来的字段名,通过反射获取对应的属性名,然后来进行赋值。

    但是这里属性名和字段名不一样,该怎么赋值?


    这时候我们建一个查询环境

    1. public interface EmpMapper {
    2. /**
    3. * 根据id查询员工信息
    4. * @param empId
    5. * @return
    6. */
    7. Emp getEmpByEmpId(@Param("empId") Integer empId);
    8. }
    1. "1.0" encoding="UTF-8" ?>
    2. mapper
    3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    5. <mapper namespace="com.godairo.mybatis.mapper.EmpMapper">
    6. <select id="getEmpByEmpId" resultType="Emp">
    7. select * from t_emp where emp_id = #{empId}
    8. select>
    9. mapper>
    1. "1.0" encoding="UTF-8" ?>
    2. configuration
    3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
    5. <configuration>
    6. <properties resource="jdbc.properties"/>
    7. <typeAliases>
    8. <package name="com.godairo.mybatis.pojo"/>
    9. typeAliases>
    10. <environments default="development">
    11. <environment id="development">
    12. <transactionManager type="JDBC"/>
    13. <dataSource type="POOLED">
    14. <property name="driver" value="${jdbc.driver}"/>
    15. <property name="url" value="${jdbc.url}"/>
    16. <property name="username" value="${jdbc.username}"/>
    17. <property name="password" value="${jdbc.password}"/>
    18. dataSource>
    19. environment>
    20. environments>
    21. <mappers>
    22. <package name="com.godairo.mybatis.mapper"/>
    23. mappers>
    24. configuration>

    此时查询出来的应该是什么样的?

    字段名跟属性名一致的话,那它们一定是相对应的,可以查出来,但是员工id,员工姓名,字段名和属性名不一致。

    创建测试类

    1. @Test
    2. public void testGetEmpByEmpId(){
    3. SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    4. EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    5. Emp emp = mapper.getEmpByEmpId(1);
    6. System.out.println(emp);
    7. }

    会发现empId和empName都是null,字段名和属性名不一致,没有创建映射关系。  


    那么我们怎么创建这种映射关系?

    方式1:去核心配置文件中,添加一个settings标签 

    这是将下划线映射为驼峰,我们再来执行普通sql语句  

     

    最后还是可以查询出来的。  

    emp_id 映射为 empId

    emp_name 映射为 empName

    这里不是随便映射的,这是有规则的。


    方式2:使用resultMap自定义映射处理

    使用resultMap,这个需要我们自己去定义,resultMap里写的是我们配置resultMap的ID。


    我们查询出来的字段,跟Emp类不一致,我们当前要解决的是id为getEmpByEmpId里的sql语句查询出来的字段跟Emp实体类中的映射关系。所以resultMap里的type就是Emp,那么需要resultMap处理映射字段跟属性的关系,里面的语句该怎么写?

    这里面常用的标签有id,association,collection,result

    id:处理主键和属性映射关系

    result:处理普通字段和属性的映射关系

    association:处理多对一

    collection:处理一对多

    我们写个id,看看里面都有什么

    property:属性

    colum:字段

    javaType:属性的类型

    jdbcType:字段类型  

    此时我们来写个colum:

    这个就是把当前的emp_id字段和Emp实体类中的empId来进行映射。

    以上是主键和属性的映射关系。


    而其他的普通字段则是用result:

    这就是一个自定义映射,此时我们来进行查询


    1. <resultMap id="empResultMap" type="Emp">
    2. <id column="emp_id" property="empId">id>
    3. <result column="emp_name" property="empName">result>
    4. <result column="age" property="age">result>
    5. <result column="gender" property="gender">result>
    6. resultMap>
    7. <select id="getEmpByEmpId" resultMap="empResultMap">
    8. select * from t_emp where emp_id=#{empId}
    9. select>

    总结:

    resultMap 里有两个属性,一个id一个type。

    这个id自己根据规范进行命名,到时候需要由自己编写SQL语句的那个select里的resultMap进行使用。这个type是查询的数据要映射的实体类的类型

    那么resultMap里面有子标签,这里先分析id标签和result标签

    id标签:这是设置主键的映射关系,里面的colum属性是用来设置映射关系中表中的字段名,这里必须是sql查询出的某个字段。

    而property属性是设置映射关系中的属性的属性名,这里必须是处理的实体类类型中的属性名

    result标签:这是设置普通字段的映射关系,里面的colum属性和property属性和id标签里的colum属性和property属性所填写的内容是一样的性质。

    2.多对一映射处理

    表和表之间有关系,则它所映射的实体类也会有关系,比如说我们在表里面,可以通过一个字段,来表示当前员工所对应的部门id,那我们在实体类中,也要设置一个属性来表示员工所对应的部门信息。

    表对应的是实体类,字段对应的是当前的属性,表里面的一条数据对应实体类对象。

    那现在员工跟部门是多对一,也就是说一个员工对应一条部门信息,一个部门信息对应的则是一个部门对象。

    那么我们来理一下实体类的关系:

    在员工实体类中,员工对部门是多对一的关系。所以说一个员工对应一个部门对象。一个部门有多个员工。那么一个部门里需要一个员工集合。

    以后对一,就是对应的一个对象,对多对应的是一个集合

    那么员工的实体类则是:

    1. public class Emp {
    2. private Integer empId;
    3. private String empName;
    4. private Integer age;
    5. private String gender;
    6. private Dept dept;
    7. public Dept getDept() {
    8. return dept;
    9. }
    10. public void setDept(Dept dept) {
    11. this.dept = dept;
    12. }
    13. public Integer getEmpId() {
    14. return empId;
    15. }
    16. public void setEmpId(Integer empId) {
    17. this.empId = empId;
    18. }
    19. public String getEmpName() {
    20. return empName;
    21. }
    22. public void setEmpName(String empName) {
    23. this.empName = empName;
    24. }
    25. public Integer getAge() {
    26. return age;
    27. }
    28. public void setAge(Integer age) {
    29. this.age = age;
    30. }
    31. public String getGender() {
    32. return gender;
    33. }
    34. public void setGender(String gender) {
    35. this.gender = gender;
    36. }
    37. public Emp(Integer empId, String empName, Integer age, String gender) {
    38. this.empId = empId;
    39. this.empName = empName;
    40. this.age = age;
    41. this.gender = gender;
    42. }
    43. public Emp() {
    44. }
    45. @Override
    46. public String toString() {
    47. return "Emp{" +
    48. "empId=" + empId +
    49. ", empName='" + empName + '\'' +
    50. ", age=" + age +
    51. ", gender='" + gender + '\'' +
    52. ", dept=" + dept +
    53. '}';
    54. }
    55. }

    那我们现在要查询员工信息,还要把员工所对应的部门信息也要查出来,用一个员工的实体类来表示当前员工的信息和员工所对应的部门。

    我们现在通过一个SQL既要把员工查出来,还要把部门查出来,那就涉及到了多表连查

    1. <select id="getEmpAndDeptByEmpId" resultType="Emp">
    2. select
    3. *
    4. from t_emp
    5. left join t_dept
    6. on t_emp.dept_id = t_dept.dept_id
    7. where t_emp.emp_id=#{empId};
    8. select>

    现在我们SQL语句写完了,但是对应不上,我们查出来的是dept_id,dept_name,它们两个字段要是想去映射的话,那就是dept_id,dept_name这两个属性。

    而我们当前让它映射的直接是Emp类型,此时在Emp实体类中只有Dept类型


    2.1 使用association处理多对一的映射关系

    association这个属性可以处理一对一,也可以处理多对一,它其实就是处理实体类类型的属性

    1. <resultMap id="empAndDeptResultMap" type="Emp">
    2. <id column="emp_id" property="empId">id>
    3. <result column="emp_name" property="empName">result>
    4. <result column="age" property="age">result>
    5. <result column="gender" property="gender">result>
    6. <association property="dept" javaType="Dept">
    7. <id column="dept_id" property="deptId">id>
    8. <result column="dept_name" property="deptName">result>
    9. association>
    10. resultMap>
    11. <select id="getEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
    12. select
    13. *
    14. from t_emp
    15. left join t_dept
    16. on t_emp.dept_id = t_dept.dept_id
    17. where t_emp.emp_id=#{empId};
    18. select>

    association:处理多对一的映射关系(处理实体类类型属性)

    property:设置需要处理映射关系的属性的属性名

    javaType:设置要处理的属性的类型


    2.2 使用分布查询处理多对一的映射关系

    所谓的分布查询,就是一步一步查,我们要查的是员工信息和员工的部门信息。

    那可以把员工先查出来,查出来员工之后,再把员工对应的部门id作为条件,在部门表里来查询,这样就可以查询出来对应的部门

    简单的说就是通过多个SQL语句,一步一步的把需要的数据查出来

    我们使用分布查询一定要想清楚,当前查询应该分几步,每一步应该是什么?

    比如说我们查员工以及员工所对应的部门,那第一步就需要查员工,查员工之后就知道员工所对应的部门id,那再把部门id作为条件,去部门表里查询部门信息,查询出来的结果再赋值给Emp里的Dept对象,就完事儿了


    分步查询的第一步:

    1. /**
    2. * 通过分布查询,查询员工以及所对应的部门信息的第一步
    3. * @param empId
    4. * @return
    5. */
    6. Emp getEmpAndDeptByStepOne(@Param("empId") Integer empId);
    1. <resultMap id="empAndDeptByStepResultMap" type="Emp">
    2. <id column="emp_id" property="empId">id>
    3. <result column="emp_name" property="empName">result>
    4. <result column="age" property="age">result>
    5. <result column="gender" property="gender">result>
    6. <association property="dept">association>
    7. resultMap>
    8. <select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
    9. select * from t_emp where emp_id = #{empId}
    10. select>

    这里我们来看association,我们现在需要把dept这个属性给查出来,那么我们怎么查呢?原来是通过两表联查,只要一个SQL查询出来的字段就能跟dept就行对应,也就是跟dept中的属性进行对应。

    而现在分步查询,这个dept的属性需要从分步查询的第二步,也就是从另外一个SQL语句来的,所以说这里面需要用到的属性有select,还有一个是column。


    现在来进行分布查询员工以及所对应的部门信息的第二步,创建DeptMapper接口和对应的Mapper文件。

    1. /**
    2. * 通过分布查询,查询员工以及所对应的部门信息的第二步
    3. * @return
    4. */
    5. Dept getEmpAndDeptByStepTwo(@Param("deptId") Integer deptId);

    这里deptId传进去的是查出来员工信息所对应的部门id

    1. <select id="getEmpAndDeptByStepTwo" resultType="Dept">
    2. select * from t_dept where dept_id=#{deptId}
    3. select>

    这时候直接用resultType就行了,不用resultMap,我们在核心配置文件已经配置了settings标签,它会自动将下划线映射为驼峰


    那么我们现在两个SQL语句写完了,写完了就需要关联起来。

    我们当前的dept属性的值需要从DeptMapper接口中的方法查询出来,再给当前的属性赋值,所以我们的select里面需要写的就是分布查询SQL的唯一标识,我们需要定位到id为getEmpAndDeptByStepTwo的SQL,把这个SQL查询出来的结果,赋值给dept属性。

    这里的唯一标识就是:namesapce.sql的id,就是唯一标识。也就是说这个唯一标识就是DeptMapper接口的全类名+方法名。

    直接对这个方法名Copy Reference就行。

    那么这个column又是什么呢?

    我们可以看到,执行以下这个SQL的时候,它有条件,这个方法并不是我们自己在调,而是Mybatis在实现这个功能的时候,当我们需要去查询dept属性的时候,它会自动调用getEmpAndDeptByStepTwo这个方法,把这个方法的查询结果赋给dept属性。那这个条件的来向就是:执行完第一个SQL后,由第一个SQL传输到下一个SQL,所以colum写的是条件,来作为下一步SQL的条件的字段

    也就是说我们第一个SQL可以查出来员工信息,其中有emp_id,emp_name,age,gender,dept_id;

    而我们就是将这个查询出来的dept_id传出给下一个SQL语句查询的条件

    接下来进行测试:

    1. @Test
    2. public void testGetEmpAndDeptByStep(){
    3. SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    4. EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
    5. Emp emp = mapper.getEmpAndDeptByStepOne(1);
    6. System.out.println(emp);
    7. }

    可以看到,执行的SQL语句有两条,先执行查询员工信息,然后根据查询出来的员工所对应的部门id,然后去查询部门信息。

    association里的属性名:

    property:设置需要处理映射关系的属性的属性名

    select:设置分布查询的sql的唯一标识

    colum:将查询出的某个字段作为分布查询的sql的条件


    2.3 分布查询的优点

    那么为什么要用分布查询呢?我用一条语句执行完不行吗?

    分布查询是有优势的,优势就是可以实现延迟加载

    但是必须在核心配置文件中设置全局配置信息:

    lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

    aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载,此时就可以实现按需加载。获取的数据是什么,就只会执行相应的sql。

    此时可通过association和collection中的fetchType属性设置当前的分步查询是否使用延迟加载, fetchType="lazy(延迟加载)|eager(立即加载)"

    什么是延迟加载?

    分布查询是通过两个SQL语句把数据查询出来。

    如果这时候我们只需要获取员工信息,暂时不需要获取部门信息。

    那么我们使用了分布查询,开启了延迟加载,获取员工信息只执行查询员工的SQL,如果没有执行获取部门信息,就不会去执行获取部门的SQL。这样就能减少内存消耗。


    那么接下来在核心配置文件mybatis-config.xml中进行配置:

    那么我们此时去获取员工姓名,因为我们没有获取部门,所以就不会去加载获取部门信息的SQL语句

    可以看到只执行了一条员工的SQL语句。  


    此时我们需要了解lazyLoadingEnabled是和aggressiveLazyLoading一起使用的,aggressiveLazyLoading是按需加载,也就是说当我们在核心配置文件中设置了延迟加载之后,这个lazyLoadingEnabled的默认值是false,我们需要手动设置为true,此时aggressiveLazyLoading默认值是false,我们可以写出来看一看:  

    如果我们把按需加载设置为true,那么结果是这样的:

    虽然查的只有员工姓名,但是部门的SQL也会执行。

    所以我们需要把aggressiveLazyLoading设置为false


    那么我们现在设置的这两个配置,是针对全局的,也就是说,不管在哪个Mapper文件中写的,最后执行,都会有这种效果。

    由于这个是全局配置,所以是针对所有的分步查询,如果我们要想让某一个分布查询实现完整的加载,我们可以这样做:

    找到association标签,也就是实现分布查询的地方,这里面有个fetchType属性,这是将当前的分布查询设置为立即加载和延迟加载,lazy是延迟加载,eager是立即加载。

    我们尝试一下设置为eager

    可以看到,将fetchType设置为eager的时候,虽然只查员工名,但是还是执行了部门SQL。

    所以这个fetchType就是在我们开启了延迟加载的环境中,然后指定某个分步为延迟加载或立即加载。  


    3.一对多的映射处理

    员工对部门是多对一,那部门对员工就是一对多。一个部门中对应多个员工信息。

    这里可以掌握一个规律:

    在表关系映射到实体类中时,对一则是对应对象,对多就是对应集合。  

    需求:查询部门信息,并且把当前部门的所有员工查询出来。


    3.1 通过collection处理一对多

    用colleciton就是得用一个SQL,把当前的部门查出来,也要把部门中的员工查出来。我们用SQL查询一下

    那么我们需要把部门的这些字段放在部门对应的实体类属性中,而员工字段则是先放在员工对象中,再放到我们所设置的集合属性里。 

    Dept getDeptAndEmpByDeptId(@Param("deptId") Integer deptId);
    1. <resultMap id="deptAndEmpResultMap" type="Dept">
    2. <id column="dept_id" property="deptId">id>
    3. <result column="dept_name" property="deptName">result>
    4. <collection property="emps" ofType="Emp">
    5. <id column="emp_id" property="empId">id>
    6. <result column="emp_name" property="empName">result>
    7. <result column="age" property="age">result>
    8. <result column="gender" property="gender">result>
    9. collection>
    10. resultMap>
    11. <select id="getDeptAndEmpByDeptId" resultMap="deptAndEmpResultMap">
    12. select * from t_dept as dept
    13. left join t_emp as emp
    14. on dept.dept_id=emp.dept_id
    15. where dept.dept_id=#{deptId}
    16. select>

     collection:处理一对多的映射关系(处理集合类型的属性)

    这里要注意:

    association用的是javaType设置一个属性的类型

    collection用的是ofType设置集合中的类型

    然后我们进行测试:

    1. @Test
    2. public void testGetDeptAndEmpByDeptId(){
    3. SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    4. DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    5. Dept deptAndEmpByDeptId = mapper.getDeptAndEmpByDeptId(1);
    6. System.out.println(deptAndEmpByDeptId);
    7. }


    3.2 通过分布查询处理一对多

    现在我们查询的是部门信息,以及部门所对应的员工,用分布查询的话,就是先把部门查出来,然后再去查询部门中的员工信息。


    先把部门信息查出来

    1. /**
    2. * 通过分布查询,查询部门以及部门中的员工信息--->第一步
    3. * @param deptId
    4. * @return
    5. */
    6. Dept getDeptAndEmpByStepOne(@Param("deptId")Integer deptId);
    1. <resultMap id="deptAndEmpResultMapByStep" type="Dept">
    2. <id column="dept_id" property="deptId">id>
    3. <result column="dept_name" property="deptName">result>
    4. <collection property="emps"
    5. select="com.godairo.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
    6. column="dept_id">collection>
    7. resultMap>
    8. <select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpResultMapByStep">
    9. select * from t_dept where dept_id=#{deptId}
    10. select>

    然后将查询出来的字段,将dept_id作为条件传给第二条要执行的SQL语句,也就是去员工那边查询部门为dept_id的员工信息

    1. /**
    2. * 通过分布查询,查询部门以及部门中的员工信息--->第二步
    3. * @param deptId
    4. * @return
    5. */
    6. List getDeptAndEmpByStepTwo(@Param("deptId") Integer deptId);

    这里要注意,我们通过dept_id查询出来的员工信息还是多个员工,所以要用List

    1. <select id="getDeptAndEmpByStepTwo" resultType="Emp">
    2. select * from t_emp where dept_id=#{deptId}
    3. select>

    最后测试:

    1. @Test
    2. public void testgetDeptAndEmpByStep(){
    3. SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    4. DeptMapper mapper = sqlSession.getMapper(DeptMapper.class);
    5. Dept deptAndEmpByStepOne = mapper.getDeptAndEmpByStepOne(1);
    6. System.out.println(deptAndEmpByStepOne);
    7. }


    那么因为我们在核心配置文件中设置了延迟加载,这是针对全局的

    所以我们测试一下只获取部门信息,这样就不会去加载员工信息。

    同样可以在colletcion中设置立即加载

    eager是立即加载,lazy是延迟加载。  

  • 相关阅读:
    c++ vs2019 cpp20规范的STL库的map与multimap源码分析
    [HNCTF 2022 WEEK2]ez_ssrf题目解析
    java.awt.FONT
    二叉搜索树
    《golang设计模式》第二部分·结构型模式-05-门面模式Facade)
    有序表2:跳表
    c++ SFML ftp上传文件
    MarkDown语法——更好地写博客
    【Vue五分钟】五分钟让你了解vue组件的层级关系
    python+playwright 学习-84 Response 接口返回对象
  • 原文地址:https://blog.csdn.net/qq_44706176/article/details/126652569