暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

从源码角度理解Mybatis字段映射

码酱 2020-11-20
1178

两个问题:

    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接口类和对应的映射文件

@Mapper
public 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" >
    select
     id, company_name, company_num, company_type, company_address
    from company 
    where 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" >
select
id,
      company_name AS companyChineseName,
      company_num, company_type, company_address
from company
where 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是最强大与稳健的,resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。
<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" >
select
id, company_name, company_num, company_type, company_address
from company
where id = #{id}
</select>
DefaultResultSetHandler处理返回集合

从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);
}
}
    // 生成未映射和已映射的map
mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);
unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}
如果没配置在resultMap中且select出来的,那么它会继续走下去,寻找映射关系,由于没有未映射的字段,使用自动映射的结果为false

之后继续往下走,使用applyPropertyMappings来创建对象,使用了PropertyMapping里面包含了属性名列名字段类型和对应的处理器

...
最后完成了整个的赋值

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



喜欢就加个关注吧,


往期精选

SpringCloud配置中心的使用
SpringCloud-Eureka高可用搭建
SpringCloud-Zuul服务网关
SpringCloud-Ribbon负载均衡
SpringCloud-Feign远程调用
SpringCloud-Hystrix解决雪崩
SpringCloud-Bus消息总线
Springboot 自定义注解实现日志功能
Nacos入门案例
文章转载自码酱,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论