Mybatis行为配置之Ⅱ—结果相关配置项说明

2023-12-28 22:48:52

专栏精选

引入Mybatis

Mybatis的快速入门

Mybatis的增删改查扩展功能说明

mapper映射的参数和结果

Mybatis复杂类型的结果映射

Mybatis基于注解的结果映射

Mybatis枚举类型处理和类型处理器

再谈动态SQL

引言

大家好,我是奇迹老李,一个专注于分享开发经验和基础教程的博主。欢迎来到我的频道,这里汇聚了汇集编程技巧、代码示例和技术教程,欢迎广大朋友们点赞评论提出意见,重要的是点击关注喔 🙆,期待在这里与你共同度过美好的时光🕹?。今天要和大家分享的内容是行为配置之Ⅱ—结果映射配置项说明。做好准备,Let’s go🚎🚀

摘要

在这篇文章中,我们将了解Mybatis的配置将如何影响结果映射的行为,其中,有的配置项很常见,有的配置项不常见,这些配置项会对我们的开发工作造成什么样的影响,我们又将如何通过修改这些配置来简化日常开发工作呢?准备好开启今天的神奇之旅吧

正文

首图

在Mybatis的行为配置项中,存在一系列的配置项用于控制Mybatis的结果映射行为,下面我们来一一介绍这些配置项

autoMappingBehavior

备注:设置如何将字段映射行为属性,可以在NONE、PARTIAL、FULL三个可选项中选择
默认值:PARTIAL
建议值:
建议原因:
选项说明:

配置说明
NONE不自动映射,必须通过 <resultMap>标签手动指定映射
PAERIAL只会自动映射没有定义嵌套结果映射的字段
FULL自动映射任何复杂的结果集(无论是否嵌套)

可通过一下代码测试NONE配置的行为

@Test  
public void testAutoMapBehavior() {  
    SqlSession session = this.sqlSessionFactory.openSession();  
    ApplicationRepository mapper = session.getMapper(ApplicationRepository.class);  
    AppTestEntity e1 = mapper.queryById(1L);  
    System.out.println(e1);
}

默认配置下的打印结果

AppTestEntity{id=1, appName='测试应用1', appCode='ceshi', authType='1', createDate=2023-10-31, creator='admin', appStatus='null', authTypeDict=null, appStatusDict=null, services=null}

配置 <setting name="autoMappingBehavior" value="NONE"/>下的打印结果

null

如果通过 resultMap的方式指定映射关系,在NONE配置下也能成功映射

添加resultMap映射

<resultMap id="appTestMap" type="top.sunyog.common.entity.AppTestEntity">  
    <result column="app_name" property="appName"/>  
    <result column="app_code" property="appCode"/>  
    <result column="auth_type" property="authType"/>  
    <result column="create_date" property="createDate"/>  
</resultMap>

<!--省略sql-->
<select id="queryById" resultMap="appTestMap">...</select>

配置 <setting name="autoMappingBehavior" value="NONE"/>下的打印结果

AppTestEntity{id=null, appName='测试应用1', appCode='ceshi', authType='1', createDate=2023-10-31, creator='null', appStatus='null', authTypeDict=null, appStatusDict=null, services=null}

注意:只有result标签指明的字段才能正确映射,没指明的不会映射出结果。

细节:这里可以通过 autoMapping属性实现局部的自动映射,来改变 autoMappingBehavior的配置行为

<resultMap id="appTestMap" type="top.sunyog.common.entity.AppTestEntity" autoMapping="true">
...
</resultMap>

配置 <setting name="autoMappingBehavior" value="NONE"/>下的打印结果

AppTestEntity{id=1, appName='测试应用1', appCode='ceshi', authType='1', createDate=2023-10-31, creator='admin', appStatus='3', authTypeDict=null, appStatusDict=null, services=null}

可以通过以下代码测试FULL配置的行为

public interface ApplicationRepository {
	AppVo queryAppVoById(Long id);
}

//测试代码
public class EnvConfigTest {  
    private SqlSessionFactory sqlSessionFactory;
    @Test  
    public void testAutoMapFull(){  
        SqlSession session = this.sqlSessionFactory.openSession();  
        ApplicationRepository mapper = session.getMapper(ApplicationRepository.class);  
        AppVo e1 = mapper.queryAppVoById(1L);  
        System.out.println(e1);  
    }  
}
<mapper namespace="top.sunyog.mybatis.mapper.ApplicationRepository">
	<resultMap id="appVoMap" type="appVo" autoMapping="true">   
		<!--注意,这里的association内部没有设置自动映射-->
        <association property="service" column="service_id" foreignColumn="id">  
            <id column="service_id" property="id"/>  
            <result column="service_name" property="serviceName"/>  
        </association>    
	</resultMap>    
	<select id="queryAppVoById" resultMap="appVoMap">  
        select t1.id,t1.app_name,t2.id as service_id,t2.service_name,t2.service_code,t2.service_path from app_test t1  
        left join service_test t2 on t1.id=t2.app_id  
        where t1.id=#{id}  
	</select>
</mapper>

默认配置状态下的打印结果

AppVo{id=1, appName='测试应用1', service=ServiceTestEntity{id=3, serviceName='注册中心', serviceCode='null', servicePath='null', appId=null}}

修改配置为 <setting name="autoMappingBehavior" value="FULL"/>后的结果

AppVo{id=1, appName='测试应用1', service=ServiceTestEntity{id=3, serviceName='注册中心', serviceCode='nacos-service', servicePath='/nacos', appId=null}}

因此,FULL可以实现复杂对象的自动映射

autoMappingUnknownColumnBehavior

备注:自动映射时,发现未知列/属性时的行为。可选值包括NONE、WARNING、FAILING
默认值:NONE
建议值:
建议原因:
选项说明:

配置说明
NONE无操作
WARNING打印警告日志,需要保证org.apache.ibatis.session.AutoMappingUnknownColumnBehavior的日志级别为warn
FAILING抛出 SqlSessionException 异常

测试代码使用上例 autoMappingBehavior中的 testAutoMapFull()方法,修改xml映射文件

<select id="queryAppVoById" resultType="appVo">

默认配置下的输出结果(仅输出打印)

AppVo{id=1, appName='测试应用1', service=null}

配置 <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>后的结果(输出错误日志)

 WARN [main] - Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_id,propertyName=service_id,propertyType=null]
 WARN [main] - Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_name,propertyName=service_name,propertyType=null]
 WARN [main] - Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_code,propertyName=service_code,propertyType=null]
 WARN [main] - Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_path,propertyName=service_path,propertyType=null]
AppVo{id=1, appName='测试应用1', service=null}

配置 <setting name="autoMappingUnknownColumnBehavior" value="FAILING"/>0后的结果(报错退出)

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.session.SqlSessionException: Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_id,propertyName=service_id,propertyType=null]
### The error may exist in mapper/ApplicationMapper.xml
### The error may involve top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById
### The error occurred while handling results
### SQL: select t1.id,t1.app_name,t2.id as service_id,t2.service_name,t2.service_code,t2.service_path         from app_test t1         left join service_test t2 on t1.id=t2.app_id         where t1.id=?
### Cause: org.apache.ibatis.session.SqlSessionException: Unknown column is detected on 'top.sunyog.mybatis.mapper.ApplicationRepository.queryAppVoById' auto-mapping. Mapping parameters are [columnName=service_id,propertyName=service_id,propertyType=null]

lazyLoadingEnabled

备注:延迟加载开关,设置关联查询的延迟加载。其中的关联查询即是通过<association><collection> 标签配置的关联查询
默认值:false(V3.4.1及之前版本为true)
建议值:true
建议原因:很多情况下我们只需要获取主查询中的结果,对关联查询的结果不关心,通过设置延迟加载可以减少不必要的查询。

为了测试这个配置需要修改mapper包的日志级别为 DEBUG

<Loggers>  
    <Logger name="top.sunyog.mybatis.mapper" level="debug"/>
<Loggers>

测试类

@Test  
public void testLazyLoad(){  
    SqlSession session = this.sqlSessionFactory.openSession();  
    SimpleQueryMapper mapper = session.getMapper(SimpleQueryMapper.class);  
    AppTestEntity entity = mapper.queryAppDetail(2);  
    System.out.println("主查询结束");  
	System.out.println("查询结果app_name="+entity.getAppName());
    System.out.println("获取子查询结果");  
    DictTest appStatusDict = entity.getAppStatusDict();  
    System.out.println("子查询结果app_status_dict="+appStatusDict);  
}

默认情况下(或 <setting name="lazyLoadingEnabled" value="false"/>)的输出结果(先完成所有查询,再输出app_name)

DEBUG [main] - ==>  Preparing: select t1.* ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds from ( select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=? ) t1 left join ( select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type' ) t2 on t1.auth_type=t2.dict_code
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - ====>  Preparing: select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_status' and dict_code=?
DEBUG [main] - ====> Parameters: 0(String)
DEBUG [main] - <====      Total: 1
DEBUG [main] - ====>  Preparing: select * from service_test where app_id=?
DEBUG [main] - ====> Parameters: 2(Integer)
DEBUG [main] - <====      Total: 2
DEBUG [main] - <==      Total: 1
主查询结束
查询结果app_name=公共应用1
获取子查询结果
子查询结果app_status_dict=DictTest{dictName='临时应用', dictCode='0', dictType='app_status', dictSort=0}

配置 <setting name="lazyLoadingEnabled" value="true"/>下的输出(先执行主查询,再打印app_name,再执行后续必要的查询)

DEBUG [main] - ==>  Preparing: select t1.* ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds from ( select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=? ) t1 left join ( select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type' ) t2 on t1.auth_type=t2.dict_code
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 1
主查询结束
查询结果app_name=公共应用1
获取子查询结果
DEBUG [main] - ==>  Preparing: select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_status' and dict_code=?
DEBUG [main] - ==> Parameters: 0(String)
DEBUG [main] - <==      Total: 1
子查询结果app_status_dict=DictTest{dictName='临时应用', dictCode='0', dictType='app_status', dictSort=0}

细节:这里需要注意resultMap中设定的 fetchType属性,一个resultMap终端关联关系是否延迟加载取决于fetchType的设置,和整体配置无关。fetchType属性的可选值包括

  1. lazy:延迟加载
  2. eager:非延迟加载

如:可以修改这个resultMap,自定义appStatusDict属性延迟加载 services属性不延迟

<resultMap id="appDetail" type="appTestEntity" autoMapping="true">  
    ...
    <association fetchType="lazy" property="appStatusDict" javaType="dictTest" select="queryAppStatus" column="app_status"></association>  
    <collection fetchType="eager" property="services" column="id" select="queryServices" javaType="ArrayList" ofType="serviceTestEntity"/>  
</resultMap>

细节: 另一个需要注意的是 aggressiveLazyLoading这个配置 👇

aggressiveLazyLoading

备注:开启该配置后,查询结果对象的任意方法调用,都会触发延迟加载属性的加载
默认值:false
建议值:false
建议原因:
任意方法包括getter、setter方法,而getter、setter方法的调用频率极高。而Mybatis自动映射通过setter方法实现,因此开启这个配置后延迟加载也就相当于关闭了(包括fetchType)

开启此配置后,上例的测试代码输出内容直接锁定为

DEBUG [main] - ==>  Preparing: select t1.* ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds from ( select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=? ) t1 left join ( select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type' ) t2 on t1.auth_type=t2.dict_code
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - ====>  Preparing: select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_status' and dict_code=?
DEBUG [main] - ====> Parameters: 0(String)
DEBUG [main] - <====      Total: 1
DEBUG [main] - ====>  Preparing: select * from service_test where app_id=?
DEBUG [main] - ====> Parameters: 2(Integer)
DEBUG [main] - <====      Total: 2
DEBUG [main] - <==      Total: 1
主查询结束
查询结果app_name=公共应用1
获取子查询结果
子查询结果app_status_dict=DictTest{dictName='临时应用', dictCode='0', dictType='app_status', dictSort=0}

即便设置了fetchType也失效

<resultMap id="appDetail" type="appTestEntity" autoMapping="true">
	<association fetchType="lazy" property="appStatusDict" javaType="dictTest" select="queryAppStatus" column="app_status"></association>  
	<collection fetchType="lazy" property="services" column="id" select="queryServices" javaType="ArrayList" ofType="serviceTestEntity"/>
</resultMap>

lazyLoadTriggerMethods

备注:指定对象的哪些方法触发延迟加载,方法名用 ,分隔
默认值:equals,clone,hashCode,toString
建议值:
建议原因:

继续修改方法的内容如下:

public class EnvConfigTest {  
    private SqlSessionFactory sqlSessionFactory;
    @Test  
    public void testLazyLoad(){  
        SqlSession session = this.sqlSessionFactory.openSession();  
        SimpleQueryMapper mapper = session.getMapper(SimpleQueryMapper.class);  
        AppTestEntity entity = mapper.queryAppDetail(2);  
        System.out.println("主查询结束");  
        System.out.println("查询结果app_name="+entity.getAppName());  
        int row = entity.hashCode();  
//        System.out.println("获取子查询结果");  
//        DictTest appStatusDict = entity.getAppStatusDict();  
//        System.out.println("子查询结果app_status_dict="+appStatusDict);  
    }  
}

默认配置下的输出:(hashCode方法被调用后,执行了所有的延迟加载)

DEBUG [main] - ==>  Preparing: select t1.* ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds from ( select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=? ) t1 left join ( select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type' ) t2 on t1.auth_type=t2.dict_code
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 1
主查询结束
查询结果app_name=公共应用1
DEBUG [main] - ==>  Preparing: select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_status' and dict_code=?
DEBUG [main] - ==> Parameters: 0(String)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: select * from service_test where app_id=?
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 2

修改配置 <setting name="lazyLoadTriggerMethods" value="toString"/>后的输出:( hashCode方法调用不再执行延迟加载)

DEBUG [main] - ==>  Preparing: select t1.* ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds from ( select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=? ) t1 left join ( select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type' ) t2 on t1.auth_type=t2.dict_code
DEBUG [main] - ==> Parameters: 2(Integer)
DEBUG [main] - <==      Total: 1
主查询结束
查询结果app_name=公共应用1

multipleResultSetsEnabled

备注:是否支持单次查询返回多个结果集,此配置需要数据库驱动的支持。Mysql数据库多结果集查询方法见[[Mybatis结果映射#collection集合类型的多结果集查询]]
默认值:true
建议值:不设置
建议原因:mysql-connector-javaV8数据库驱动中,此项配置无效

useColumnLabel

备注:是否使用列标签代替列名。依赖于数据库驱动
默认值:true
建议值:不设置
建议原因:mysql-connector-java数据库驱动中,此项配置无效
失效原因:
此项设置的区别在于,在对resultSet的处理过程中是调用 ResultSetMetaData#getColumnLabel方法还是 ResultSetMetaData#getColumnName方法,(源码见UnknownTypeHandler#resolveTypeHandlerResultSetWrapper构造方法
而对于mysql-connector-java来说,这两个方法最后都是调用的 Field#getName方法,部分源码如下

package com.mysql.cj.jdbc.result;
public class ResultSetMetaData implements java.sql.ResultSetMetaData {
	@Override  
	public String getColumnLabel(int column) throws SQLException {  
	    if (this.useOldAliasBehavior) {  
	        return getColumnName(column);  
	    }  
	  
	    return getField(column).getColumnLabel();  
	}  
	  
	@Override  
	public String getColumnName(int column) throws SQLException {  
	    if (this.useOldAliasBehavior) {  
	        return getField(column).getName();  
	    }  
	  
	    String name = getField(column).getOriginalName();  
	  
	    if (name == null) {  
	        return getField(column).getName();  
	    }  
	  
	    return name;  
	}
	
	protected Field getField(int columnIndex) throws SQLException {  
	    if ((columnIndex < 1) || (columnIndex > this.fields.length)) {  
	        throw SQLError.createSQLException(Messages.getString("ResultSetMetaData.46"), MysqlErrorNumbers.SQL_STATE_INVALID_COLUMN_NUMBER,  
	                this.exceptionInterceptor);  
	    }  
	  
	    return this.fields[columnIndex - 1];  
	}
}

package com.mysql.cj.result;
public class Field implements ProtocolEntity {
	public String getColumnLabel() {  
	    return getName();  
	}
	public String getName() {  
	    return this.columnName.toString();  
	}
}

useGeneratedKeys

备注:允许 JDBC 自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。
默认值:false
建议值:不设置
建议原因:主键可通过代码自行生成。另外,mysql-connector-javaV8数据库驱动中,此项配置没有任何影响。

细节:简单的主键生成行为可以参考这篇博客,分布式主键生成方案可参考Twitter雪花算法。

mapUnderscoreToCamelCase

备注:是否开启驼峰命名自动映射
默认值:false
建议值:true
建议原因:可以减少 <resultMap>标签的使用

总结

在这篇文章中,我们介绍了关于Mybatis结果映射的配置项,这些配置项大多与 <resultMap>标签相关,可见,在Mybatis中ResultMap是一个重点。其中最常用的就是 mapUnderscoreToCamelCase这个配置,基本属于必开配置。


📩 联系方式
邮箱:qijilaoli@foxmail.com

?版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问奇迹老李的博客首页

文章来源:https://blog.csdn.net/qq_43408971/article/details/135279432
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。