• 后端——框架——持久层框架——Mybatis——《Mybatis从入门到精通》读书笔记——第六章节(高级主题类型处理器,存储过程,多对多映射)


      本章介绍一些高级主题,处理关联映射,鉴别器,存储过程,类型处理器。其中存储过程在实际项目中很少遇到,所以省略这部分的内容。

      具体内容如下:

    1. 第一小节介绍如何配置映射关系,一对一,一对多,多对多的情形以及鉴别器的用法。
    2. 第二小节介绍如何处理存储过程,这部分的内容忽略。
    3. 第三小节介绍如何处理常见的数据类型,如何编写自定义的类型处理器。

    1、结构图

    2、关联映射

    关联关系有三种1对1,1对多,多对多,多对多可以拆分为两个单向的一对多。在配置时只存在1对1和1对多的配置。

    映射这里指将数据库查询的结果集映射为Java实体类的属性,默认情况下会根据驼峰方式自动映射,例如user_name会映射为userName,id会映射为id。

    关联映射,理解为当实体之间的关系为1对1时,结果集如何转换为Java实体类的属性,当为多对多事,又如何转换。

      下面具体介绍

    2.1  1对1

      假定用户与它的家庭地址是一对一的关系,例如User类中存在homeAddress,它的家庭地址又包含五个字段,国家,省份,城市,街道,详细地址(门牌号等)。

    当关联关系为1对1时,映射方式有两种:

    • 自动转换,默认情况下是驼峰方式,例如user_name映射为userName,而homeAddress.country会映射为HomeAddress类的country属性。这种映射关系与前端表单映射到后端参数的方式是一样的。此时无需任何配置
    • 在resultMap中配置association标签,它也有三种方式
    1. 在内部重复罗列HomeAddress类中的所有属性,因为肯定在HomeAddressMapper配置文件中存在resultMap。
    2. 使用association标签的resultMap属性,复用HomeAddressMapper中的配置,格式为namespace.resultMapID,其中namespace表示HomeAddressMapper对应的命名空间,resultMapID表示resultMap的id属性值。
    3. 使用association标签的select属性,它的值为select标签的ID属性,该select的返回值必须是HomeAddress对象。

      如下是association标签的属性:

    表格6- 1 association标签

    描述

    配置对象为一对一关联关系时,数据库结果集与Java实体类之间的映射关系

    属性

    property

    描述: 指定关联对象的属性名称

    示例:上述user与HomeAddress的例子,此值应该为homeAddress

    属性

    JavaType

    描述:指定关联对象的类型。

    示例:此值应该为HomeAddress类的全名

    属性

    resultMap

    描述:指定关联对象的映射关系

    示例:此值应该为HomeAddressMapper.resultMapID,不用重复罗列HomeAddress中的属性值

    属性

    columnPrefix

    描述:指定结果集对应关联对象的列前缀。

    示例:为了区分关联查询时地址表的列,给其添加homeAddress前缀。XX as homeAddress.country。

    属性

    Select

    描述:指定select查询语句,它的返回值必须是关联对象

    示例:此值应该为HomeAddressMapper中的selectAdressByUserId。类似这种查询

    属性

    column

    描述:建立select语句查询结果集与resultMap中列配置的映射关系。例如HomeAddressMapper中的select语句它会返回country列,它需要对应homeAddress.country,然后在转换为HomeAddress中的country属性。

    示例:HomeAddressMapper中select语句的结果集如果不与User中的属性重名,可以使用默认驼峰方式。

    属性

    fetchType

    描述:加载方式,延迟加载还是立刻加载。值为lazy和eager。理解该属性首先需要理解N+1的问题,假设执行selectUser查询用户,它会查询所有满足条件的用户,每个用户都关联一个HomeAddress,当指定associate的select属性时,每个用户都对应一个HomeAddress,所以每个用户的HomeAddress都需要一次查询,所以N个用户会导致查询HomeAddress表N次。再加上查询用户,总共执行了N+1次查询,其中N次查询HomeAddress,1次查询用户。假设用户关联的不止HomeAddress一个类,那可想而知,会执行mN +1次查询,m为与User一对一关系的对象数量。

    指定为懒加载方式时,只有使用到该用户时,才会去查询HomeAddress表。即使这样它的效率还是不如关联查询快,所以慎用select方式。

    示例:此值应该为lazy。

    链接

    https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#association

    2.2   1对多

    1对多与1对1的映射方式类似,标签只是从association变为collection。区别在于如何判断1对多的关系。这里以用户User和角色Role为例,从数据库表的角度看

    当数据库表之前存在主外键关系时,即Role表中存在user_id字段。

    在映射的过程中道理也是一样的,当用户与角色进行关联查询时,角色的user_id列与用户ID列值相同时,这些角色都归属与该用户。

    在配置Collection标签时也必须体现主外键的关系,User的resultMap和Collection标签内部的Role属性都必须配置ID属性。

    例如UserMapper中的

    <resultMap>
       <id property="id" column="id" />
       <collection property=”roles” javaType=”list” ofType=”Role”>
           <id property="id" column="user_id"/>
       </collection>
    </resultMap>
    

      此时结果集中具有相同user_id的会合并,并映射为一个User对象中。若不配置此项,会导致每一条结果集映射为一个User对象,除非结果集中所有列的数据都相同,才会映射为相同User对象,这种情况一般不存在,因为ID肯定是不同的。

    2.3  多对多

      在理解1对多之后,多对多的关系非常简单。它可以拆分为两个1对多的关系,例如用户和角色,需要在UserMapper中配置collection标签,同时在RoleMapper中配置collection标签。

    3、鉴别器

    鉴别器的作用相当于在映射过程中添加分支,可以理解为当结果集中某一列的值为XX时,指定XX对应的resultMap。语法相当于java的switch语句,外层的resultMap相当于default分支。

    下述例子中,当结果集中user_name的属性值为admin时,映射关系为ResultMapWithBLOBS。

    当结果集中的user_name为其他值时,映射关系为userDiscriminatorMap。

    <!-- user discriminator 这段配置的含义是当用户名为admin,加载BLOB的字段,否则加载基本的字段 -->
    <resultMap id="userDiscriminatorMap"  type="com.module.mybatis.bean.User"  extends="BaseResultMap">
        <discriminator javaType="string" column="user_name">
            <case value="admin" resultMap="ResultMapWithBLOBs"/>
        </discriminator>
    </resultMap>
    

    可以尝试练习原书中所给的示例。

    4、类型处理器

    类型处理器的作用是将结果集中的数据类型转换为Java实体中属性的数据类型,这是全局的,不用为每一个属性单独配置。

    自定义类型处理器的步骤如下:

    1. 编写自定义类型处理器,继承自BaseTypeHandler或者实现TypeHandler接口
    2. 注册自定义类型处理器,在mybatis-config中配置typeHandler标签。

      示例如下:这个例子是将地址字符串转换为Address类,地址字符串的格式为country_province_city_street_otherDetails(国家_地区_城市_街道_其他详细信息)

    // import语句省略,Address类有以上五个属性,都是字符串类型
    public class AddressHandler extends BaseTypeHandler<Address> {
    	@Override
    	public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
    		Address address_obj = new Address();
    		// 根据列名称获取
    		String address_string = rs.getString(columnName);
    		// 获取column列中的值,映射成Address类
    		if (null == address_string) {
    			return null;
    		} else {
    			String[] address = address_string.split("_");
    			address_obj = new Address(address);
    		}
    		return address_obj;
    	}
    
    	@Override
    	public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    		Address address_obj = new Address();
    		// 根据列的索引获取
    		String address_string = rs.getString(columnIndex);
    		// 获取column列中的值,映射成Address类
    		if (null == address_string) {
    			return null;
    		} else {
    			String[] address = address_string.split("_");
    			address_obj = new Address(address);
    		}
    		return address_obj;
    	}
    
    	@Override
    	public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    		// 一般不会使用到这个方法,因为STATEMENT="PREPARED",就是JDBC中的PreparedStatement对象
    		Address address_obj = new Address();
    		String address_string = cs.getString(columnIndex);
    		// 根据列的索引获取
    		if (null == address_string) {
    			return null;
    		} else {
    			String[] address = address_string.split("_");
    			address_obj = new Address(address);
    		}
    		return address_obj;
    	}
    
    	@Override
    	public void setNonNullParameter(PreparedStatement ps, int i, Address paramAddress, JdbcType jdbcType)
    			throws SQLException {
    		ps.setString(i, paramAddress.toString());
    	}
    }

      注册配置如下:

    <typeHandlers>
    	<!-- 日期的类型处理器 -->
    	<typeHandler javaType="java.util.Date"  handler="org.apache.ibatis.type.DateTypeHandler"></typeHandler>
    	<typeHandler javaType="com.xx.bean.Address" handler="com.xx.handler.AddressHandler"></typeHandler>
    </typeHandlers>
    

      类型映射在日常编程很容易见到,例如前端往后端传数据时,将JSON字符串转换为实体对象,将表单的字符串转换为对象等等。本质上是类似的,只不过这个转换过程是自动进行的,spring框架也允许自定义类型转换,例如注册自定义PropertyEditor。

  • 相关阅读:
    带有时间间隔的dp
    单调队列优化dp(捡垃圾的机器人)
    实现技巧
    树形dp(灯与街道)
    括号匹配(数组链表模拟)
    二分,求直线上覆盖所有点的最短时间
    可持久化链表(链式前向星)
    二分图匹配模板题
    网络流,设备、插头和转接器建图(简单map的应用)
    第七周助教总结
  • 原文地址:https://www.cnblogs.com/rain144576/p/12228954.html
Copyright © 2020-2023  润新知