水煮MyBatis(七)- 驼峰配置解决了什么问题

251 阅读3分钟

前言

程序员使用Mybatis的时候,通常会加一个驼峰配置,如下:

mybatis:
  configuration:
    map-underscore-to-camel-case: true

驼峰约定

什么是驼峰写法?简单来说,是java ORM对象中,增加可读性的一种约定俗成的写法;一个字段如果由多个单词组成,除第一个单词以外,后续单词都用大写开头。比如图片地址,我们一般写成:imageUrl。
在最终源码解释的例子中,我们将数据结果集中的个性签名字段别名设置成了【Per_son_Al_Signat_ure】,是否可以正确映射java ORM对象字段呢?

在Mybatis中的使用

返回java对象

public class UserInfoResponse {

    /**
     * 个性签名
     */
    private String personalSignature;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 头像图片url
     */
    private String avatarUrl;
	/**
     * 忽略后续字段
     */
	
}
    @Select("select nickname ,avatar_url ,personal_signature" +
            " from tb_user  " +
            " where user_id = #{userId} ")
    UserInfoResponse userInfo(@Param("userId") Integer userId);

在这个查询方法中,可以看到avatar_url、personal_signature与java对象中的属性拼写不是一一对应的,mybatis的驼峰配置可以将这个查询结果集映射到UserInfoResponse对象中。

什么情况下失效

以一个属性为例,personal_signature,如果将数据库字段别名拼写成tm.personal_signature as personalSignature,是没有问题的,可以正确返回;如果别名写成per_son_al_signat_ure或者p_signature呢?
经过实践,写成per_son_al_signat_ure也是可以的,但是如果删减了字符,比如p_signature,查询语句不会出异常,返回对象中personalSignature属性不会进行值映射,返回null。
也就是说,除下划线以外,删减字符会使值映射失效

源码解释

  1. 在DefaultResultSetHandler类的createAutomaticMappings方法,引入了驼峰配置。
    final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
  2. 真正使用驼峰配置的地方【MetaClass类】,可以看到,如果设置了驼峰参数,则只是将属性中的下划线都替换成了空字符,而没有处理大小写。那为什么别名设置成personalSignature也可以生效呢?
  public String findProperty(String name, boolean useCamelCaseMapping) {
    if (useCamelCaseMapping) {
      name = name.replace("_", "");
    }
    return findProperty(name);
  }
  1. 与第二步同一个类的buildProperty方法中,可以看到mybatis框架是如何用数据库的属性映射到java对象的。下面这行代码,根据数据的属性名称,获取到了java对象属性名称。
    String propertyName = reflector.findPropertyName(name);
    reflector的findPropertyName方法简单直接,是从reflector类对象的一个全局map属性获取java属性名称。【在mybatis启动时,就已经给每个Mapper方法生成了reflector对象,并将返回对象的属性写到caseInsensitivePropertyMap中】
  public String findPropertyName(String name) {
    return caseInsensitivePropertyMap.get(name.toUpperCase(Locale.ENGLISH));
  }

在此,我将别名设置成了Per_son_Al_Signat_ure,在第二步中将下划线去除以后,这里的findPropertyName方法中,name参数为【PersonAlSignature】,方法中对此参数进行了二次加工,即转换为英文大写【PERSONALSIGNATURE】。如下图所示,最终根据此参数,获取到了java属性名称:【personalSignature】

image.png
5. 查询日志输出
可以看到最后成功进行了对象值映射: Per_son_Al_Signat_ure -> personalSignature
==> Preparing: select nickname ,avatar_url ,personal_signature as Per_son_Al_Signat_ure from tb_user where user_id = ?
==> Parameters: 83921(Integer)
<== Columns: nickname, avatar_url, Per_son_Al_Signat_ure
<== Row: yuzjang, null, 江海寄余生
18:15:21.527 logback [HikariPool-1 housekeeper] WARN com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Thread starvation or clock leap detected (housekeeper delta=14m45s569ms312µs500ns).
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5242e9f7]
18:15:27.478 logback [main] INFO com.test.xxx.ForumTest - authorInfo:UserInfoResponse(personalSignature=江海寄余生, nickname=yuzjang, avatarUrl=null)