问题描述
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”属性配置。