大家好,我是奇迹老李,一个专注于分享开发经验和基础教程的博主。欢迎来到我的频道,这里汇聚了汇集编程技巧、代码示例和技术教程,欢迎广大朋友们点赞评论提出意见,重要的是点击关注喔 🙆,期待在这里与你共同度过美好的时光🕹?。今天要和大家分享的内容是行为配置之Ⅱ—结果映射配置项说明。做好准备,Let’s go🚎🚀
在这篇文章中,我们将了解Mybatis的配置将如何影响结果映射的行为,其中,有的配置项很常见,有的配置项不常见,这些配置项会对我们的开发工作造成什么样的影响,我们又将如何通过修改这些配置来简化日常开发工作呢?准备好开启今天的神奇之旅吧
在Mybatis的行为配置项中,存在一系列的配置项用于控制Mybatis的结果映射行为,下面我们来一一介绍这些配置项
备注:设置如何将字段映射行为属性,可以在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可以实现复杂对象的自动映射
备注:自动映射时,发现未知列/属性时的行为。可选值包括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]
备注:延迟加载开关,设置关联查询的延迟加载。其中的关联查询即是通过<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属性的可选值包括
如:可以修改这个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
这个配置 👇
备注:开启该配置后,查询结果对象的任意方法调用,都会触发延迟加载属性的加载
默认值: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>
备注:指定对象的哪些方法触发延迟加载,方法名用 ,
分隔
默认值: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
备注:是否支持单次查询返回多个结果集,此配置需要数据库驱动的支持。Mysql数据库多结果集查询方法见[[Mybatis结果映射#collection集合类型的多结果集查询]]
默认值:true
建议值:不设置
建议原因:mysql-connector-java
V8数据库驱动中,此项配置无效
备注:是否使用列标签代替列名。依赖于数据库驱动
默认值:true
建议值:不设置
建议原因:mysql-connector-java
数据库驱动中,此项配置无效
失效原因:
此项设置的区别在于,在对resultSet的处理过程中是调用 ResultSetMetaData#getColumnLabel
方法还是 ResultSetMetaData#getColumnName
方法,(源码见UnknownTypeHandler#resolveTypeHandler
和 ResultSetWrapper构造方法
)
而对于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();
}
}
备注:允许 JDBC 自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。
默认值:false
建议值:不设置
建议原因:主键可通过代码自行生成。另外,mysql-connector-java
V8数据库驱动中,此项配置没有任何影响。
细节:简单的主键生成行为可以参考这篇博客,分布式主键生成方案可参考Twitter雪花算法。
备注:是否开启驼峰命名自动映射
默认值:false
建议值:true
建议原因:可以减少 <resultMap>
标签的使用
在这篇文章中,我们介绍了关于Mybatis结果映射的配置项,这些配置项大多与 <resultMap>
标签相关,可见,在Mybatis中ResultMap是一个重点。其中最常用的就是 mapUnderscoreToCamelCase
这个配置,基本属于必开配置。
📩 联系方式
邮箱:qijilaoli@foxmail.com?版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问奇迹老李的博客首页