两个问题:
1.数据库中的字段名怎么和对象中的属性对应起来?
2.数据库中字段的类型怎么转换到合适的Java类型,不引起转换失败?
本次演示的数据库如下所示
CREATE TABLE `company` (`id` INT ( 100 ) NOT NULL AUTO_INCREMENT COMMENT '主键id',`company_name` VARCHAR ( 100 ) NOT NULL COMMENT '企业名称',`company_num` CHAR ( 20 ) NOT NULL COMMENT '统一社会信用代码',`company_type` CHAR ( 1 ) NOT NULL COMMENT '企业类型',`company_address` CHAR ( 6 ) NOT NULL COMMENT '所属地市编码',PRIMARY KEY ( `id` )) ENGINE = INNODB COMMENT = '公司表';
对应的PO(Persistant Object)Company
public class Company {private int id;private String companyName;private String companyNum;private String companyType;private String companyAddress;}
mybatis接口类和对应的映射文件
@Mapperpublic interface CompanyMapper {Company getCompanyById(String id);}
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.demo.company.mapper.CompanyMapper"><select id="getCompanyById" resultType="com.demo.company.pojo.Company" >selectid, company_name, company_num, company_type, company_addressfrom companywhere id = #{id}</select></mapper>
在上面的映射文件中,namespace指定了对应接口类的全限定类路径,select代表select语句,id为接口类中方法名,resultType代表该sql返回的期望类型的类的全限定类名或别名(typeAliases)。
针对上面的问题,主要解决有三种方案
驼峰式命名开关指定为true,或者数据库字段名和对象的属性名完全一致
Select时指定As
ResultMap最强大与稳健
一、驼峰式命名开关
一般来说我们的数据库字段名采用下划线分隔,比如这里的字段名company_name与对象属性名companyName。
<settings><!-- 驼峰转换开关为true,只要数据库字段名和对象属性名字母一致,会自动去掉下划线识别映射 ><setting name="mapUnderscoreToCamelCase" value="true" /></settings>
mybatis驼峰命名开关默认是关闭的false,mybatis-plus默认是开启的true。
我们从mybatis源码走读一下
mybatis的ResultSet映射默认都在DefaultResultSetHandler中完成。依次进入
handleRowValues->getRowValue->applyAutomaticMappings->createAutomaticMappings->findProperty在这里把下划线去掉了

继续往下走,这里返回找到的字段名

继续往下走

在这可以看到,大小写不敏感处理,debug一下

二、Select时指定AS
其实select时使用as和方案一有点类似,但是方案二比方案一还是有一点点区别的,方案一是数据库字段名和对象属性名除了下划线还有大小写外字母是必须要保持一致的。但是使用AS就可以不一样了。
这里我们将对象属性名companyName改为companyChineseName
public class Company {private int id;private String companyChineseName;private String companyNum;private String companyType;private String companyAddress;}
由于找不到匹配的对象属性,赋值肯定为null
Company{id=1, companyChineseName='null', companyNum='a123456789abcdefghq1', companyType='1', companyAddress='430100'}
这时候我们的as就可以上场表演了...
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.demo.company.mapper.CompanyMapper"><select id="getCompanyById" resultType="com.demo.company.pojo.Company" >selectid,company_name AS companyChineseName,company_num, company_type, company_addressfrom companywhere id = #{id}</select></mapper>
改动后执行得到结果
Company{id=1, companyChineseName='运输企业1', companyNum='a123456789abcdefghq1', companyType='1', companyAddress='430100'}
我们来走读一下源码是如何实现的
在方案一的源码走读中有一个handleRowValues传入了一个rsw参数,它是对ResultSet的一个包装,里面处理了查询到的值与对象的哪个属性进行映射赋值。

AS为什么生效?
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {super();this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.resultSet = rs;final ResultSetMetaData metaData = rs.getMetaData();final int columnCount = metaData.getColumnCount();for (int i = 1; i <= columnCount; i++) {columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));classNames.add(metaData.getColumnClassName(i));}}
在添加列名时,会从配置中获取是否使用类标签,isUseColumnLabel,默认为true,根据Javadoc,这个ColumnLabel就是as后的那个名字,如果没有as的话,就是获取原生的字段名。

三、ResultMap最强大与稳健
<resultMap id="BaseResultMap" type="com.demo.company.pojo.Company"><id column="id" property="id" jdbcType="INTEGER" /><result column="company_name" property="companyName" jdbcType="VARCHAR" /><result column="company_num" property="companyNum" jdbcType="VARCHAR" /><result column="company_type" property="companyType" jdbcType="VARCHAR" /><result column="company_address" property="companyAddress" jdbcType="VARCHAR" /></resultMap><select id="getCompanyById" resultMap="BaseResultMap" >selectid, company_name, company_num, company_type, company_addressfrom companywhere id = #{id}</select>

从resultMap中可以看出,它的属性相当丰富,将之前的result信息保存在resultMappings...propertyMappings等中,后续方便使用


private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {List<String> mappedColumnNames = new ArrayList<String>();List<String> unmappedColumnNames = new ArrayList<String>();final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);// 这里没有配置前缀,定义resultMap后会记录这些已经配置映射的字段final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);for (String columnName : columnNames) {// 遍历列名,如果在已映射的配置中,那就加入已经映射的列名数据final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);if (mappedColumns.contains(upperColumnName)) {mappedColumnNames.add(upperColumnName);} else {unmappedColumnNames.add(columnName);}}// 生成未映射和已映射的mapmappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);}



其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis会自动的给我们把对应的值赋给resultType所指定对象的属性,而当我们提供的返回类型是resultMap的时候,因为Map不能很好表示领域模型,我们就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
喜欢就加个关注吧,


往期精选




