Liquibase:使用带 H2 数据库的 modifyDataType 重构将 INT 自动增量列更改为 BIGINT

Posted

技术标签:

【中文标题】Liquibase:使用带 H2 数据库的 modifyDataType 重构将 INT 自动增量列更改为 BIGINT【英文标题】:Liquibase: Change a INT autoincrement column to BIGINT using modifyDataType refactoring with H2 database 【发布时间】:2011-12-05 04:02:26 【问题描述】:

我有一个主键列,它是一个 INT 列,我想将其更改为 BIGINT。我们的测试和生产环境使用 mysql,但对于单元测试,我们使用嵌入式 H2 数据库。

我创建了以下 Liquibase 重构:

...
<changeSet id="1" author="trond">
    <modifyDataType tableName="event" columnName="id" newDataType="BIGINT" />
    <rollback>
        <modifyDataType tableName="event" columnName="id" newDataType="INT" />
    </rollback>
</changeSet>
...

重构有效,但是当我尝试使用 Hibernate 将对象持久化到数据库时,我收到以下错误消息(我已经包装了错误消息):

ERROR org.hibernate.util.JDBCExceptionReporter [main]: NULL not allowed for column "ID"; 
    SQL statement: insert into event (id, eventtime, guid, meta, objectguid, originatorid, subtype, type) values (null, ?, ?, ?, ?, ?, ?, '0') [90006-140]

JDBC exception on Hibernate data access: 
    SQLException for SQL [insert into event (id, eventtime, guid, meta, objectguid, originatorid, subtype, type) values (null, ?, ?, ?, ?, ?, ?, '0')]; 
    SQL state [90006]; error code [90006]; could not insert: [event.MyEvent]; 
    nested exception is org.hibernate.exception.GenericJDBCException: could not insert: [event.MyEvent]

MyEvent 类继承自 AbstractBaseEvent,它在代码中定义了以下 Hibernate 映射:

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;

几点:

休眠映射在重构数据类型之前起作用 Liquibase 版本为 2.0.1 这是否适用于 MySQL 尚未经过测试

【问题讨论】:

【参考方案1】:

我测试了(Hibernate 3.6.2.Final,H2 1.3.160,方言:org.hibernate.dialect.H2Dialect)你的情况发生了什么:

当 GenerationType 为 AUTO 且数据类型为 INT 时,实际生成 类型是 SEQUENCE。 当 GenerationType 为 AUTO 且数据类型为 BIGINT 时,实际 生成类型是 IDENTITY。结果这将失败,如果 id-field 是 定义为 ID BIGINT PRIMARY KEY 而不是 ID BIGINT IDENTITY (在此处添加 PRIMARY KEY 和 H2 将是多余的)。

你可以做什么:

如果你希望实际的生成类型是 SEQUENCE,和以前一样,那么

@GeneratedValue(strategy = GenerationType.SEQUENCE)

似乎有效。序列本身不需要更改,因为根据documentation 类型无论如何都是 BIGINT。我会这样做,因为那样什么都没有真正改变,而且很清楚序列的生成方式

另一种可能性是使用 startValue 将列定义为 IDENTITY(因为可能存在现有值)并像以前一样使用 GenerationType.AUTO。

【讨论】:

非常好的分析 - 谢谢。我看到使用 SEQUENCE 的唯一问题是 MySQL 不支持序列。一种选择是使用我们在另一种情况下使用过的序列表。 好主意 - 无论如何,使用 table 是最便携的。 如果你关心的是可移植性,那么基本上没有问题:你必须使用@TableGenerator【参考方案2】:

我会首先将您的 @GenerationType 更改为特定的内容(例如 IDENTITY),以排除 Hibernate 从序列中获取奇怪值的任何问题。或者完全删除它。

你的重构看起来不错,我看不出有什么明显的问题。

在引用标识符方面,H2 和 Liquibase 通常不能很好地配合使用; Liquibase 中的 H2 数据库类引用了一些而不引用其他的。也许大小写转换把你搞砸了?

当原始类型为 0 (!) 时,EclipseLink 有时会出现问题,因为它有时会将此类值视为 null 或未初始化,但据我所知,Hibernate 不受此限制。

这不是一个真正的答案,我知道,但希望能让你指出正确的方向。

【讨论】:

【参考方案3】:

这是一个老问题,接受的答案有很好的分析 但我遇到了同样的问题,花了 3 个小时才找到真正的问题和解决方案,因此值得将其留在这里以供将来参考。

所以真正的问题是在 liquibase 本声明

<changeSet id="1" author="trond">
    <modifyDataType tableName="event" columnName="id" newDataType="BIGINT" />
    <rollback>
        <modifyDataType tableName="event" columnName="id" newDataType="INT" />
    </rollback>
</changeSet>`

正在生成此更改查询

ALTER TABLE event ALTER COLUMN id BIGINT

从 H2 中的 id 列中删除默认序列值

因此,当您尝试插入新行时,id 为 null,从而引发 SQL 错误。

不幸的是,ALTER 列的行为因数据库供应商而异,因此最好的解决方案可能是为测试 (H2) 和生产 (MySQL) 创建不同的迁移更改日志

在 H2 上,您可以在 modifyDataType 更改后再次将 id 设为 auto_increment

<addAutoIncrement
    columnDataType="bigint"
    columnName="id"
    incrementBy="1"
    startWith="1"
    tableName="event"/>
</changeSet>`

【讨论】:

以上是关于Liquibase:使用带 H2 数据库的 modifyDataType 重构将 INT 自动增量列更改为 BIGINT的主要内容,如果未能解决你的问题,请参考以下文章

尝试在 H2 数据库中创建表时出现 Liquibase 错误

生成 forIndexName 的 Liquibase 差异在 h2 上不起作用

JOOQ 可以为 H2/Postgresql 别名 Liquibase JSONB 数据类型吗

Liquibase 主键在 H2 上创建了两次

使用 Liquibase 为 Spring Boot 应用程序中的单元测试初始化​​内存 H2

如何使用 liquibase 更改架构名称