问题描述

2016-06-02上线时,出了两个这样的异常。

列类型映射错误 Wrong column type in thread.loan_stop_backups for column data. Found: text, expected: mediumtext

列类型映射错误 Wrong column type in thread.loan_stop_requests for column type. Found: char, expected: mediumtext

直接原因是,上线SQL中相关字段类型与hibernate映射的类型不一致。

问题原因分析

这里说的主要是开发、测试、回归环境上都没有发现问题、但上线时出现问题的原因。

开发环境上,出于减少服务启动时间、提高开发效率的考虑,“hibernate.hbm2ddl.auto”属性的配置是none。因此,开发环境启动服务时没有对映射关系做任何校验。

测试/回归环境上,目前的配置仍然是update(待确认,但从测试环境数据库表的结构,以及pom文件的配置来看,应该是update)。因此,测试环境上的表并不是通过上线SQL来生成的,因而也无法发现错误的映射关系。

解决方案

解决方案零

直接修改数据库的字段定义。

对loan_stop_backups.data字段可以这样,但是对loan_stop_requests.type字段来说,就有点得不偿失了(索引、空间占用等)。

因此,这个方案仅作为解决上线bug时的临时方案。后续需要有更完备的方法来解决问题。

解决方案一

网上找到的第一个解决方式,是在@Column注解中增加一个配置:

@Column(columnDefinition = "char")
private LoanStopType type = LoanStopType.I;

根据javadoc的描述,这个字段的含义是建表的ddl语句中,用来定义字段的那一串字符串。

例如sql中写“type char(1) NOT NULL DEFAULT 'I'  COMMENT'终止类型'”,则columnDefinition="char(1) NOT NULL DEFAULT 'I'  COMMENT'终止类型'"

/**
  * (Optional) The SQL fragment that is used when
  * generating the DDL for the column.
  * Defaults to the generated SQL to create a
  * column of the inferred type.
  */
String columnDefinition() default"";

但是做了相关配置后,仍然报错:

列类型映射错误 Wrong column type in thread.loan_stop_requests for column type. Found: char, expected: char

虽然配置的是“char”,但是由于MySQL的方言设置,这个字符串被转成了“char”。

如果直接改方言配置,影响范围太大。因此放弃这个方案。

解决方案二

这是以前用过的一个方法,在自定义的方言处理器中,增加类型映射。

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">cn.youcredit.thread.common.dao.ThreadMySQL5InnoDBDialect
        </property>
    </session-factory>
</hibernate-configuration>
/** @author linjun*/
public class ThreadMySQL5InnoDBDialect extends MySQL5InnoDBDialect {
    public ThreadMySQL5InnoDBDialect() {
        super();
        // 增加的映射
        this.registerColumnType(Types.VARCHAR, 1, "char");
        ……
    }
}

增加映射后,问题解决。

但是这个方案的让然是修改了整个方言处理器,对全局都有影响。其影响范围、可能带来的问题,都难以估量。

因此,权衡了第三个方案后,放弃了方案二。

解决方案三

这个问题的根源,是本应映射到char类型上的字段被hibernate映射到了mediumtext上。方案二是让hibernate自动识别正确的映射;方案三则是在定义字段时,明白的告诉hibernate自己应该映射到什么类型上。

具体做法就是在字段上增加一个Type注解,显示的声明这个字段的类型。

/** 终止类型*/
@Enumerated(EnumType.STRING)
@JsonView(LoanStopListView.class)
@Column(length = 1)
// 增加了这一行配置
@Type(type = "character") 
private LoanStopType type = LoanStopType.I;

根据@Type的javadoc,其type属性应该是某个类的全名。这里指定的character,实际是org.hibernate.type.CharacterType类注册的别名。可以拿来用。

这个方案只影响对应的字段,对其它字段、方言翻译等没有影响。因此最后选择了它。

线上问题修复

计划

  • checkout一个问题修复分支,随最近的上线版本上线。
  • 修正测试环境的“hibernate.hbm2ddl.auto”属性配置。